Un programma a riga di comando può impedire il reindirizzamento del suo output?


49

Sono diventato così abituato a fare questo: someprogram >output.file

Lo faccio ogni volta che voglio salvare l'output che un programma genera in un file. Sono anche a conoscenza delle due varianti di questo reindirizzamento IO :

  • someprogram 2>output.of.stderr.file (per stderr)
  • someprogram &>output.stderr.and.stdout.file (sia per stdout + stderr combinati)

Oggi ho incontrato una situazione che non pensavo fosse possibile. Uso il seguente comando xinput test 10e come previsto ho il seguente output:

user @ hostname: ~ $ test di input x 10
tasto premere 30 
rilascio chiave 30 
tasto premuto 40 
rilascio chiave 40 
tasto premuto 32 
rilascio chiave 32 
tasto 65 
rilascio chiave 65 
tasto premuto 61 
rilascio chiave 61 
tasto premere 31 
^ C
utente @ hostname: ~ $ 

Mi aspettavo che questo output potesse essere salvato come al solito in un file come l'utilizzo xinput test 10 > output.file. Ma quando è in contrasto con le mie aspettative, il file output.file rimane vuoto. Questo vale anche xinput test 10 &> output.filesolo per essere sicuro di non perdere qualcosa su stdout o stderr.

Sono davvero confuso e quindi chiedo qui se il xinputprogramma potrebbe avere un modo per evitare il reindirizzamento del suo output?

aggiornare

Ho guardato la fonte. Sembra che l'output sia generato da questo codice (vedi lo snippet di seguito). Mi sembra che l'output sarebbe generato da un normale printf

// nel file test.c

static void print_events (Display * dpy)
{
    Evento XEvent;

    while (1) {
    XNextEvent (dpy, & Event);

    // [... alcuni altri tipi di eventi sono omessi qui ...]

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

        printf ("chiave% s% d", (Event.type == key_release_type)? "release": "premi", chiave-> codice chiave);

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

Ho modificato la fonte in questo (vedi il prossimo frammento di seguito), che mi permette di avere una copia dell'output su stderr. Questo output sono in grado di reindirizzare:

 // nel file test.c

static void print_events (Display * dpy)
{
    Evento XEvent;

    while (1) {
    XNextEvent (dpy, & Event);

    // [... alcuni altri tipi di eventi sono omessi qui ...]

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

        printf ("chiave% s% d", (Event.type == key_release_type)? "release": "premi", chiave-> codice chiave);
        fprintf (stderr, "chiave% s% d", (Event.type == key_release_type)? "release": "premi", chiave-> codice chiave);

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

La mia idea al momento è che forse eseguendo il reindirizzamento il programma perde la sua capacità di monitorare gli eventi di rilascio dei tasti.

Risposte:


55

È solo che quando stdout non è un terminale, l'output è bufferizzato.

E quando si preme Ctrl-C, quel buffer si perde come / se non è stato ancora scritto.

Ottieni lo stesso comportamento con qualsiasi cosa utilizzi stdio. Prova ad esempio:

grep . > file

Inserisci alcune righe non vuote e premi Ctrl-C, e vedrai che il file è vuoto.

D'altra parte, digitare:

xinput test 10 > file

E digita abbastanza sulla tastiera da riempire il buffer (almeno 4k di valore di output), e vedrai crescere la dimensione del file per blocchi di 4k alla volta.

Con grep, puoi digitare Ctrl-Dfor grepper uscire con garbo dopo aver scaricato il suo buffer. Perché xinputnon penso che ci sia una tale opzione.

Si noti che per impostazione predefinita stderrnon è bufferizzato, il che spiega perché si ottiene un comportamento diversofprintf(stderr)

Se, in xinput.c, aggiungi un signal(SIGINT, exit), che dice xinputdi uscire con garbo quando riceve SIGINT, vedrai che filenon è più vuoto (supponendo che non si blocchi, dato che chiamare le funzioni di libreria dai gestori di segnale non è garantito sicuro: considera cosa potrebbe accadere se il segnale arriva mentre printf sta scrivendo nel buffer).

Se è disponibile, è possibile utilizzare il stdbufcomando per modificare il stdiocomportamento del buffering:

stdbuf -oL xinput test 10 > file

Ci sono molte domande su questo sito che riguardano la disabilitazione del buffering di tipo stdio in cui troverai soluzioni ancora più alternative.


2
WOW :) che ha fatto il trucco. grazie. Quindi alla fine la mia percezione del problema era sbagliata. Non c'era nulla in atto per inibire il reindirizzamento, era semplice che Ctrl-C lo fermasse prima che i dati fossero scaricati. grazie
umanità e

Ci sarebbe stato un modo per impedire il buffering di stdout?
umanità e

1
@Stephane Chazelas: grazie mille per la spiegazione dettagliata. Oltre a quello che hai già detto, ho scoperto che è possibile impostare il buffer su senza buffer setvbuf(stdout, (char *) NULL, _IONBF, NULL). Forse anche questo è interessante !?
user1146332

4
@utente1146332, sì, sarebbe quello che stdbuf -o0fa, mentre stdbug -oLripristina il buffering di linea come quando l'uscita va su un terminale. stdbufforza l'applicazione a chiamare setvbufusando un LD_PRELOADtrucco.
Stéphane Chazelas,

un altro workaroudn: unbuffer test 10 > file( unbufferfa parte degli expectstrumenti)
Olivier Dulac il

23

Un comando può scrivere direttamente per /dev/ttyimpedire il reindirizzamento regolare.

$ 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

Il tuo esempio fa il punto + risponde alla domanda. Sì, è possibile. Naturalmente è "inaspettato" e insolito che i programmi lo facciano, il che almeno mi ha ingannato nel non considerare una cosa del genere possibile. La risposta dell'utente1146332 sembra anche un modo convincente per evitare il reindirizzamento. Per essere onesti e poiché entrambe le risposte sono ugualmente possibili modi per evitare il reindirizzamento dell'output del programma della riga di comando su un file non posso selezionare nessuna delle risposte che immagino :(. Dovrei essere autorizzato a selezionare due risposte giuste. Ottimo lavoro, Grazie!
umanità e

1
FTR, se si desidera acquisire l'output scritto /dev/ttysu un sistema Linux, utilizzare script -c ./demo demo.log(da util-linux).
ndim,

Se non stai eseguendo in un tty, ma invece in un pty, puoi trovarlo guardando procfs (/ proc / $ PID / fd / 0 ecc.). Per scrivere nel pty appropriato, vai alla directory fd del tuo processo genitore e vedi se è un link simbolico a / dev / pts / [0-9] +. Quindi scrivi su quel dispositivo (o ripeti se non è un punto).
Dhasenan,

9

Sembra che xinputrifiuta l'output su un file ma non rifiuta l'output su un terminale. Per ottenere ciò, probabilmente xinputusa la chiamata di sistema

int isatty(int fd)

per verificare se il filedescriptor da aprire fa riferimento a un terminale o meno.

Mi sono imbattuto nello stesso fenomeno qualche tempo fa con un programma chiamato dpic. Dopo aver esaminato la fonte e un po 'di debug ho rimosso le linee relative isattye tutto ha funzionato di nuovo come previsto.

Ma sono d'accordo con te che questa esperienza è molto inquietante;)


Pensavo davvero di avere la mia esplorazione. Ma (1) guardando il sorgente (il file test.c nel pacchetto sorgente di xinput) non si vede alcun isattytest fatto. L'output è generato dalla printffunzione (penso che sia una C standard). Ne ho aggiunti alcuni fprintf(stderr,"output")e questo è possibile reindirizzare + dimostra che l'intero codice è realmente eseguito nel caso di xinput. Grazie per il suggerimento dopo tutto è stata la prima traccia qui.
umanità e

0

Nel tuo test.cfile potresti scaricare i dati bufferizzati (void)fflush(stdout);direttamente dopo le tue printfdichiarazioni.

    // 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);

Sulla riga di comando è possibile abilitare l'output con buffer di linea eseguendo xinput test 10in un pseudo terminale (pty) con il scriptcomando.

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

-1

Sì. L'ho fatto anche in DOS-volte quando ho programmato in Pascal. Immagino che il principio valga ancora:

  1. Chiudi stdout
  2. Riapri stdout come console
  3. Scrivi l'output su stdout

Questo ha rotto tutti i tubi.


"Riapri stdout": stdout è definito come descrittore di file 1. È possibile riaprire il descrittore di file 1, ma quale file aprire? Probabilmente intendi aprire il terminale, nel qual caso non importa se il programma sta scrivendo su fd 1.
Gilles 'SO- smetti di essere malvagio' il

@Gilles il file era "con:" per quanto mi ricordo - ma sì, ho perfezionato il punto 2 in quella direzione.
Nils,

conè il nome DOS per ciò che chiama unix /dev/tty, ovvero il terminale (di controllo).
Gilles 'SO-smetti di essere malvagio' il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.