Czy program wiersza poleceń może zapobiec przekierowaniu danych wyjściowych?

49

Przywykłem do tego: someprogram >output.file

Robię to za każdym razem, gdy chcę zapisać dane wyjściowe generowane przez program do pliku. Zdaję sobie również sprawę z dwóch wariantów przekierowania IO :

  • someprogram 2>output.of.stderr.file (dla stderr)
  • someprogram &>output.stderr.and.stdout.file (dla obu stdout + stderr łącznie)

Dzisiaj natknąłem się na sytuację, która nie wydawała mi się możliwa. Używam następującego polecenia xinput test 10i zgodnie z oczekiwaniami mam następujące dane wyjściowe:

użytkownik @ nazwa hosta: ~ $ xinput test 10
naciśnij klawisz 30 
wydanie klucza 30 
naciśnij klawisz 40 
wydanie klucza 40 
naciśnij klawisz 32 
wydanie klucza 32 
naciśnij klawisz 65 
wydanie klucza 65 
naciśnij klawisz 61 
wydanie klucza 61 
naciśnij klawisz 31 
^ C
użytkownik @ nazwa hosta: ~ $ 

Spodziewałem się, że dane wyjściowe można jak zwykle zapisać w pliku takim jak using xinput test 10 > output.file. Ale kiedy sprzeczne z moimi oczekiwaniami plik output.file pozostaje pusty. Dotyczy to również xinput test 10 &> output.fileupewnienia się, że nie umknie mi coś na stdout lub stderr.

Jestem naprawdę zdezorientowany i dlatego pytam tutaj, czy xinputprogram może mieć sposób na uniknięcie przekierowania danych wyjściowych?

aktualizacja

Spojrzałem na źródło. Wygląda na to, że dane wyjściowe są generowane przez ten kod (patrz fragment kodu poniżej). Wydaje mi się, że wynik byłby generowany przez zwykły printf

// w pliku test.c

static void print_events (Wyświetl * dpy)
{
    Wydarzenie XEvent;

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

    // [... niektóre inne typy zdarzeń są tutaj pomijane ...]

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

        printf („klucz% s% d”, (Event.type == key_release_type)? ”release”: „naciśnij”, key-> kod dostępu);

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

Zmodyfikowałem do tego źródło (patrz następny fragment poniżej), co pozwala mi mieć kopię wyjścia na stderr. To wyjście jestem w stanie przekierować:

 // w pliku test.c

static void print_events (Wyświetl * dpy)
{
    Wydarzenie XEvent;

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

    // [... niektóre inne typy zdarzeń są tutaj pomijane ...]

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

        printf („klucz% s% d”, (Event.type == key_release_type)? ”release”: „naciśnij”, key-> kod dostępu);
        fprintf (stderr, „klucz% s% d”, (Event.type == key_release_type)? „release”: „naciśnij”, key-> kod dostępu);

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

Obecnie moim pomysłem jest to, że być może po przekierowaniu program traci zdolność monitorowania zdarzeń zwolnienia klawisza.

ludzkośćANDpeace
źródło

Odpowiedzi:

55

Po prostu gdy standardowe wyjście nie jest terminalem, wyjście jest buforowane.

A kiedy naciśniesz Ctrl-C, bufor ten zostanie utracony, ponieważ nie został jeszcze zapisany.

Otrzymujesz takie samo zachowanie przy każdym użyciu stdio. Spróbuj na przykład:

grep . > file

Wpisz kilka niepustych linii i naciśnij Ctrl-C, a zobaczysz, że plik jest pusty.

Z drugiej strony wpisz:

xinput test 10 > file

I wpisz wystarczająco dużo na klawiaturze, aby bufor się zapełnił (co najmniej 4k wartości wyjściowej), a zobaczysz, że rozmiar pliku rośnie o porcje 4k na raz.

Dzięki grep, można wpisać Ctrl-Ddo grepaby wyjść z wdziękiem po spłukuje jego bufor. Bo xinputnie sądzę, że jest taka opcja.

Zauważ, że domyślnie stderrnie jest buforowane, co wyjaśnia, dlaczego masz inne zachowaniefprintf(stderr)

Jeśli xinput.cdodasz signal(SIGINT, exit), który mówi, xinputaby wyjść z wdziękiem, gdy odbierze SIGINT, zobaczysz, że filenie jest już pusty (zakładając, że się nie zawiesi, ponieważ wywoływanie funkcji bibliotecznych z procedur obsługi sygnałów nie jest bezpieczne): zastanów się, co może się zdarzyć, jeśli sygnał pojawi się, gdy printf zapisuje do bufora).

Jeśli jest dostępny, możesz użyć stdbufpolecenia, aby zmienić stdiozachowanie buforowania:

stdbuf -oL xinput test 10 > file

Na tej stronie jest wiele pytań, które dotyczą wyłączenia buforowania typu stdio, gdzie znajdziesz jeszcze więcej alternatywnych rozwiązań.

Stéphane Chazelas
źródło
2
WOW :) to załatwiło sprawę. Dziękuję Ci. Tak więc ostatecznie moje postrzeganie problemu było błędne. Nie było nic, co mogłoby powstrzymać przekierowanie, po prostu Ctrl-C zatrzymał je, zanim dane zostały usunięte. dziękuję
ludzkośćANDpeace
Czy byłby sposób, aby zapobiec buforowaniu standardowego wyjścia?
humanityANDpeace,
1
@Stephane Chazelas: wielkie dzięki za szczegółowe wyjaśnienie. Oprócz tego, co już powiedziałeś, odkryłem, że można ustawić bufor na niebuforowany setvbuf(stdout, (char *) NULL, _IONBF, NULL). Może to również jest interesujące !?
user1146332,
4
@ user1146332, tak, że byłoby to, co stdbuf -o0robi, gdy stdbug -oLprzywraca linia buforowanie jak gdy wyjście idzie do terminala. stdbufwymusza na aplikacji wywołanie setvbufprzy użyciu LD_PRELOADlewy.
Stéphane Chazelas,
inne obejście: unbuffer test 10 > file( unbufferjest częścią expectnarzędzi)
Olivier Dulac
23

Polecenie może bezpośrednio pisać, aby /dev/ttyzapobiec regularnemu przekierowaniu.

$ 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
źródło
W twoim przykładzie punkt + odpowiada na pytanie. Tak to mozliwe. Oczywiście jest to „nieoczekiwane” i rzadkie, aby programy to robiły, co przynajmniej oszukało mnie, że nie uważałem takiej rzeczy za możliwą. Odpowiedź użytkownika 1111332 wydaje się również przekonującym sposobem na uniknięcie przekierowania. Aby być uczciwym i ponieważ obie podane odpowiedzi są w równym stopniu możliwe, aby uniknąć przekierowania wyjścia programu wiersza poleceń do pliku, nie mogę wybrać żadnej odpowiedzi, jak sądzę :(. Musiałbym mieć prawo wyboru dwóch odpowiedzi. Świetna robota, Dziękuję!
ludzkośćANDpeace
1
FTR, jeśli chcesz przechwycić dane wyjściowe zapisane /dev/ttyw systemie Linux, użyj script -c ./demo demo.log(od util-linux).
ndim
Jeśli nie korzystasz z tty, ale zamiast pty, możesz to znaleźć, patrząc na procfs (/ proc / $ PID / fd / 0 itp.). Aby napisać do odpowiedniego pty, przejdź do katalogu fd procesu nadrzędnego i sprawdź, czy jest to dowiązanie symboliczne do / dev / pts / [0-9] +. Następnie piszesz na to urządzenie (lub powtarza się, jeśli nie jest to punkt).
dhasenan
9

Wygląda na to, że xinputodrzuca dane wyjściowe do pliku, ale nie odrzuca danych wyjściowych do terminala. Aby to osiągnąć, prawdopodobnie xinputużyj wywołania systemowego

int isatty(int fd)

sprawdzanie, czy skrypt filedescriptor, który ma zostać otwarty, odnosi się do terminala, czy nie.

Natknąłem się na to samo zjawisko jakiś czas temu w programie o nazwie dpic. Po przejrzeniu źródła i debugowaniu usunąłem powiązane wiersze isattyi wszystko działało zgodnie z oczekiwaniami.

Ale zgadzam się z tobą, że to doświadczenie jest bardzo niepokojące;)

użytkownik1146332
źródło
Naprawdę myślałem, że mam swoje wyjaśnienie. Ale (1) patrząc na źródło (plik test.c w pakiecie źródłowym xinput) nie ma wyglądu isattyprzeprowadzonych testów. Wyjście jest generowane przez printffunkcję (myślę, że jest to standardowa C). Dodałem trochę fprintf(stderr,"output")i jest to możliwe, aby przekierować + dowodzi, że cały kod jest naprawdę uruchamiany w przypadku xinput. Dziękuję za sugestię, przecież był to pierwszy szlak tutaj.
ludzkośćANDpeace
0

W twoim test.cpliku możesz opróżnić buforowane dane używając (void)fflush(stdout);bezpośrednio po twoich printfinstrukcjach.

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

W wierszu poleceń możesz włączyć wyjście buforowane wierszem, uruchamiając xinput test 10pseudo terminal (pty) za pomocą scriptpolecenia.

script -q /dev/null xinput test 10 > file      # FreeBSD, Mac OS X
script -c "xinput test 10" /dev/null > file    # Linux
kabu
źródło
-1

Tak. Zrobiłem to nawet w czasach DOS, kiedy programowałem w pascal. Myślę, że zasada ta nadal obowiązuje:

  1. Zamknij stdout
  2. Ponownie otwórz stdout jako konsolę
  3. Zapisz dane wyjściowe na standardowe wyjście

To zepsuło wszelkie rury.

Nils
źródło
„Ponownie otwórz stdout”: stdout jest zdefiniowany jako deskryptor pliku 1. Możesz ponownie otworzyć deskryptor pliku 1, ale jaki plik chciałbyś otworzyć? Prawdopodobnie masz na myśli otwarcie terminala, w którym to przypadku nie ma znaczenia, czy program pisze na fd 1.
Gilles „SO- przestań być zły”
@Gilles, o ile pamiętam, plik był „con:” - ale tak, dopracowałem punkt 2 w tym kierunku.
Nils
conto nazwa DOS dla tego, co wywołuje unix /dev/tty, tj. terminal (kontrolujący).
Gilles „SO- przestań być zły”