¿Puede un programa de línea de comando evitar que su salida sea redirigida?

49

Me he acostumbrado tanto a hacer esto: someprogram >output.file

Lo hago cada vez que quiero guardar la salida que genera un programa en un archivo. También conozco las dos variantes de esta redirección de IO :

  • someprogram 2>output.of.stderr.file (para stderr)
  • someprogram &>output.stderr.and.stdout.file (para ambos stdout + stderr combinados)

Hoy me he encontrado con una situación que no creía posible. Uso el siguiente comando xinput test 10y, como esperaba, tengo el siguiente resultado:

usuario @ nombre de host: ~ $ xinput test 10
pulsación de tecla 30 
lanzamiento clave 30 
pulsación de tecla 40 
lanzamiento clave 40 
pulsación de tecla 32 
lanzamiento clave 32 
pulsación de tecla 65 
lanzamiento clave 65 
pulsación de tecla 61 
lanzamiento clave 61 
pulsación de tecla 31 
^ C
usuario @ nombre de host: ~ $ 

Esperaba que esta salida se pudiera guardar como de costumbre en un archivo como el que se usa xinput test 10 > output.file. Pero cuando contradice mis expectativas, el archivo output.file permanece vacío. Esto también es cierto para xinput test 10 &> output.fileasegurarse de que no me pierda algo en stdout o stderr.

Estoy realmente confundido y, por lo tanto, pregunto aquí si el xinputprograma podría tener una manera de evitar que su salida sea redirigida.

actualizar

He mirado la fuente. Parece que la salida es generada por este código (ver fragmento a continuación). Me parece que la salida sería generada por una impresión ordinaria

// en el archivo test.c

eventos_impresión de vacío estático (Display * dpy)
{
    Evento XEvent;

    mientras que (1) {
    XNextEvent (dpy, y Evento);

    // [... algunos otros tipos de eventos se omiten aquí ...]

        if ((Event.type == key_press_type) ||
           (Event.type == key_release_type)) {
        int loop;
        XDeviceKeyEvent * key = (XDeviceKeyEvent *) & Event;

        printf ("key% s% d", (Event.type == key_release_type)? "release": "press", key-> keycode);

        for (loop = 0; loopaxes_count; loop ++) {
        printf ("a [% d] =% d", key-> first_axis + loop, key-> axis_data [loop]);
        }
        printf ("\ n");
    } 
    }
}

Modifiqué la fuente a esto (vea el siguiente fragmento a continuación), lo que me permite tener una copia de la salida en stderr. Esta salida puedo redirigir:

 // en el archivo test.c

eventos_impresión de vacío estático (Display * dpy)
{
    Evento XEvent;

    mientras que (1) {
    XNextEvent (dpy, y Evento);

    // [... algunos otros tipos de eventos se omiten aquí ...]

        if ((Event.type == key_press_type) ||
           (Event.type == key_release_type)) {
        int loop;
        XDeviceKeyEvent * key = (XDeviceKeyEvent *) & Event;

        printf ("key% s% d", (Event.type == key_release_type)? "release": "press", key-> keycode);
        fprintf (stderr, "key% s% d", (Event.type == key_release_type)? "release": "presione", key-> keycode);

        for (loop = 0; loopaxes_count; loop ++) {
        printf ("a [% d] =% d", key-> first_axis + loop, key-> axis_data [loop]);
        }
        printf ("\ n");
    } 
    }
}

Mi idea en este momento es que tal vez al hacer la redirección, el programa pierde su capacidad de monitorear los eventos de pulsación de teclas.

humanidad y paz
fuente

Respuestas:

55

Es solo que cuando stdout no es un terminal, la salida se almacena en búfer.

Y cuando presiona Ctrl-C, ese búfer se pierde como / si aún no se ha escrito.

Obtiene el mismo comportamiento con cualquier cosa que use stdio. Prueba por ejemplo:

grep . > file

Ingrese algunas líneas no vacías y presione Ctrl-C, y verá que el archivo está vacío.

Por otro lado, escriba:

xinput test 10 > file

Y escriba lo suficiente en el teclado para que el búfer se llene (al menos 4k de salida), y verá que el tamaño del archivo crece en trozos de 4k a la vez.

Con grep, puede escribir Ctrl-Dpara grepsalir con gracia después de haber vaciado su búfer. Porque xinput, no creo que haya tal opción.

Tenga en cuenta que, de forma predeterminada, stderrno está almacenado, lo que explica por qué obtiene un comportamiento diferente confprintf(stderr)

Si, en xinput.c, agrega un signal(SIGINT, exit), que le indica xinputque salga con gracia cuando lo recibe SIGINT, verá que ya fileno está vacío (suponiendo que no se bloquee, ya que llamar a las funciones de la biblioteca desde los controladores de señal no está garantizado como seguro: considere qué podría suceder si la señal entra mientras printf está escribiendo en el búfer).

Si está disponible, puede usar el stdbufcomando para alterar el stdiocomportamiento del almacenamiento en búfer:

stdbuf -oL xinput test 10 > file

Hay muchas preguntas en este sitio que cubren la desactivación del almacenamiento en búfer de tipo stdio donde encontrará aún más soluciones alternativas.

Stéphane Chazelas
fuente
2
WOW :) eso hizo el truco. gracias. Así que al final mi percepción del problema fue incorrecta. No había nada en el lugar para inhibir la redirección, era simple que Ctrl-C lo detuviera antes de que se borraran los datos. gracias
humanityANDpeace
¿Habría habido una manera de evitar el almacenamiento en búfer de stdout?
humanityANDpeace
1
@Stephane Chazelas: muchas gracias por su explicación detallada. Además de lo que ya ha dicho, descubrí que se puede configurar el búfer sin búfer setvbuf(stdout, (char *) NULL, _IONBF, NULL). ¿Quizás esto también sea de interés?
user1146332
44
@ user1146332, sí, eso sería lo que stdbuf -o0hace, mientras stdbug -oLrestaura el almacenamiento en línea como cuando la salida va a un terminal. stdbufobliga a la aplicación a llamar setvbufusando un LD_PRELOADtruco.
Stéphane Chazelas
otro workaroudn: unbuffer test 10 > file( unbufferes parte de las expectherramientas)
Olivier Dulac
23

Un comando puede escribir directamente para /dev/ttyevitar que se produzca una redirección regular.

$ cat demo
#!/bin/ksh
LC_ALL=C TZ=Z date > /dev/tty
$ ./demo >demo.out 2>demo.err
Fri Dec 28 10:31:57  2012
$ ls -l demo*
-rwxr-xr-x 1 jlliagre jlliagre 41 2012-12-28 11:31 demo
-rw-r--r-- 1 jlliagre jlliagre  0 2012-12-28 11:31 demo.err
-rw-r--r-- 1 jlliagre jlliagre  0 2012-12-28 11:31 demo.out
jlliagre
fuente
Su ejemplo hace que el punto + responda la pregunta. Sí, es posible. Por supuesto, es "inesperado" y poco común que los programas lo hagan, lo que al menos me engañó al no considerar tal cosa posible. La respuesta del usuario 1146332 también parece una forma convincente de evitar la redirección. Para ser justos y dado que ambas respuestas dadas son formas igualmente posibles de evitar la redirección de la salida del programa de línea de comandos a un archivo, no puedo seleccionar ninguna de las respuestas, supongo :(. Necesitaría que se me permitiera seleccionar dos respuestas correctas. Gran trabajo, Gracias!
humanityANDpeace
1
FTR, si desea capturar la salida escrita /dev/ttyen un sistema Linux, use script -c ./demo demo.log(desde util-linux).
ndim
Si no está ejecutando en un tty, sino en un pty, puede encontrarlo mirando procfs (/ proc / $ PID / fd / 0, etc.). Para escribir en el pty apropiado, vaya al directorio fd de su proceso padre y vea si es un enlace simbólico a / dev / pts / [0-9] +. Luego escribe en ese dispositivo (o recurse si no es un pts).
dhasenan
9

Parece que xinputrechaza la salida a un archivo pero no rechaza la salida a un terminal. Para lograr esto, probablemente xinputuse la llamada al sistema

int isatty(int fd)

para verificar si el descriptor de archivo a abrir se refiere a un terminal o no.

Me topé con el mismo fenómeno hace un tiempo con un programa llamado dpic. Después de buscar en la fuente y algunas depuraciones, eliminé las líneas relacionadas isattyy todo volvió a funcionar como se esperaba.

Pero estoy de acuerdo con usted en que esta experiencia es muy inquietante;)

usuario1146332
fuente
Realmente pensé que tenía mi explicación. Pero (1) mirando la fuente (el archivo test.c en el paquete fuente xinput) no parece haber ninguna isattyprueba realizada. La salida se genera por printffunción (creo que es una C estándar). Agregué algunos fprintf(stderr,"output")y esto es posible redirigir + prueba que todo el código realmente se ejecuta en el caso de xinput. Gracias por la sugerencia después de todo, fue el primer sendero aquí.
humanityANDpeace
0

En su test.carchivo, puede vaciar los datos almacenados en búfer (void)fflush(stdout);directamente después de sus printfdeclaraciones.

    // in test.c
    printf("key %s %d ", (Event.type == key_release_type) ? "release" : "press  ", key->keycode);
    //fprintf(stderr,"key %s %d ", (Event.type == key_release_type) ? "release" : "press  ", key->keycode);
    //(void)fflush(NULL);
    (void)fflush(stdout);

En la línea de comando, puede habilitar la salida con buffer de línea ejecutando xinput test 10en un pseudo terminal (pty) con el scriptcomando.

script -q /dev/null xinput test 10 > file      # FreeBSD, Mac OS X
script -c "xinput test 10" /dev/null > file    # Linux
kabu
fuente
-1

Si. Incluso hice esto en DOS veces cuando programé en pascal. Supongo que el principio aún se mantiene:

  1. Cerrar stdout
  2. Reabrir stdout como consola
  3. Escribe la salida en stdout

Esto rompió cualquier tubería.

Nils
fuente
“Volver a abrir stdout”: stdout se define como el descriptor de archivo 1. Puede volver a abrir el descriptor de archivo 1, pero ¿qué archivo abriría? Probablemente te refieres a abrir la terminal, en cuyo caso no importa si el programa está escribiendo en fd 1.
Gilles 'SO- deja de ser malvado'
@Gilles, el archivo estaba "con:" por lo que recuerdo, pero sí, refiné el punto 2 en esa dirección.
Nils
cones el nombre de DOS para lo que llama unix /dev/tty, es decir, el terminal (de control).
Gilles 'SO- deja de ser malvado'