GNU/Linux >> Tutoriales Linux >  >> Linux

¿Por qué stdout necesita un vaciado explícito cuando se redirige al archivo?

Está combinando incorrectamente funciones de E/S con búfer y sin búfer. Esta combinación debe hacerse con mucho cuidado, especialmente cuando el código tiene que ser portátil. (y es malo escribir código no portátil...)
Ciertamente es mejor evitar combinar E/S con búfer y sin búfer en el mismo descriptor de archivo.

IO con búfer: fprintf() , fopen() , fclose() , freopen() ...

E/S sin búfer: write() , open() , close() , dup() ...

Cuando usas dup2() para redirigir la salida estándar. La función no reconoce el búfer que fue llenado por fprintf() . Así que cuando dup2() cierra el antiguo descriptor 1, no vacía el búfer y el contenido podría ser vaciado a una salida diferente. En tu caso 2a fue enviado a /dev/null .

La solución

En tu caso lo mejor es usar freopen() en lugar de dup2() . Esto resuelve todos tus problemas:

  1. Vacía los búferes del FILE original corriente. (caso 2a)
  2. Establece el modo de almacenamiento en búfer de acuerdo con el archivo recién abierto. (caso 3)

Aquí está la implementación correcta de su función:

void RedirectStdout2File(const char* log_path) {
    if(freopen(log_path, "a+", stdout) == NULL) err(EXIT_FAILURE, NULL);
}

Desafortunadamente, con IO almacenado en búfer no puede establecer directamente los permisos de un archivo recién creado. Tiene que usar otras llamadas para cambiar los permisos o puede usar extensiones glibc no portátiles. Ver el fopen() man page .


Enjuague para stdout está determinada por su comportamiento amortiguador. El almacenamiento en búfer se puede configurar en tres modos:_IOFBF (búfer completo:espera hasta fflush() si es posible), _IOLBF (búfer de línea:la nueva línea activa el vaciado automático), y _IONBF (escritura directa siempre se usa). "El soporte para estas características está definido por la implementación y puede verse afectado a través de setbuf() y setvbuf() funciones." [C99:7.19.3.3]

"Al iniciar el programa, se predefinen tres flujos de texto y no es necesario abrirlos explícitamente:entrada estándar (para leer la entrada convencional), salida estándar (para escribir la salida convencional) y error estándar (para escribir la salida de diagnóstico). Como se abrió inicialmente, el error estándar El flujo no está completamente almacenado en el búfer; los flujos de entrada estándar y de salida estándar están completamente almacenados en el búfer si y solo si se puede determinar que el flujo no hace referencia a un dispositivo interactivo". [C99:7.19.3.7]

Explicación del comportamiento observado

Entonces, lo que sucede es que la implementación hace algo específico de la plataforma para decidir si stdout va a tener un búfer de línea. En la mayoría de las implementaciones de libc, esta prueba se realiza cuando se usa la transmisión por primera vez.

  1. El comportamiento n.º 1 se explica fácilmente:cuando la transmisión es para un dispositivo interactivo, se almacena en línea y el printf() se enjuaga automáticamente.
  2. El caso n.º 2 ahora también se espera:cuando redirigimos a un archivo, la secuencia se almacena en búfer por completo y no se vaciará excepto con fflush() , a menos que escriba montones de datos en él.
  3. Finalmente, entendemos el caso n.° 3 también para las implementaciones que solo realizan la verificación en el fd subyacente una vez. Porque forzamos que el búfer de stdout se inicializara en el primer printf() , stdout adquirió el modo de búfer de línea. Cuando intercambiamos el fd para ir al archivo, todavía tiene un búfer de línea, por lo que los datos se vacían automáticamente.

Algunas implementaciones reales

Cada libc tiene latitud en cómo interpreta estos requisitos, ya que C99 no especifica qué es un "dispositivo interactivo", ni la entrada stdio de POSIX amplía esto (más allá de requerir que stderr esté abierto para lectura).

  1. Glibc. Ver archivodoalloc.c:L111. Aquí usamos stat() para probar si el fd es un tty, y configure el modo de almacenamiento en búfer en consecuencia. (Esto se llama desde fileops.c.) stdout inicialmente tiene un búfer nulo y se asigna en el primer uso de la transmisión en función de las características de fd 1.

  2. BSD libc. Código muy similar, pero mucho más limpio a seguir. Ver esta línea en makebuf.c


Linux
  1. ¿Por qué cambia el valor de inodo cuando editamos en el editor "vi"?

  2. ¿Por qué la opción Ssh -t agrega Cr y Lf en la salida redirigida?

  3. ¿Por qué `zip` en un bucle For funciona cuando el archivo existe, pero no cuando no existe?

  4. ¿Por qué ENOENT significa No existe tal archivo o directorio?

  5. ¿Por qué necesitamos el archivo .so.1 en Linux?

¿Por qué el usuario raíz necesita permiso de Sudo?

¿Por qué sale esta canalización de shell?

¿Adónde van los metadatos cuando guarda un archivo?

¿Por qué Linux usa una partición de intercambio en lugar de un archivo?

¿Por qué rm manual dice que podemos ejecutarlo sin ningún argumento, cuando esto no es cierto?

¿Por qué es necesario inicializar un dispositivo raid 10?