Kann ein Befehlszeilenprogramm verhindern, dass seine Ausgabe umgeleitet wird?

49

Ich habe mich daran gewöhnt: someprogram >output.file

Ich mache das immer dann, wenn ich die von einem Programm generierte Ausgabe in einer Datei speichern möchte. Mir sind auch die beiden Varianten dieser E / A-Umleitung bekannt :

  • someprogram 2>output.of.stderr.file (für stderr)
  • someprogram &>output.stderr.and.stdout.file (für beide stdout + stderr kombiniert)

Heute bin ich auf eine Situation gestoßen, die ich nicht für möglich gehalten habe. Ich benutze den folgenden Befehl xinput test 10und wie erwartet habe ich die folgende Ausgabe:

user @ hostname: ~ $ xinput test 10
Tastendruck 30 
Schlüsselfreigabe 30 
Tastendruck 40 
Schlüsselfreigabe 40 
Tastendruck 32 
Schlüsselfreigabe 32 
Tastendruck 65 
Schlüsselfreigabe 65 
Tastendruck 61 
Schlüsselfreigabe 61 
Tastendruck 31 
^ C
user @ hostname: ~ $ 

Ich habe erwartet, dass diese Ausgabe wie gewohnt in einer Datei gespeichert werden kann xinput test 10 > output.file. Entgegen meiner Erwartung bleibt die Datei output.file jedoch leer. Dies gilt auch xinput test 10 &> output.filenur, um sicherzustellen, dass ich auf stdout oder stderr nichts verpasse.

Ich bin wirklich verwirrt und frage daher hier, ob das xinputProgramm eine Möglichkeit hat, die Umleitung der Ausgabe zu vermeiden.

aktualisieren

Ich habe die Quelle angeschaut. Es scheint, dass die Ausgabe von diesem Code generiert wird (siehe Ausschnitt unten). Es scheint mir, dass die Ausgabe von einem normalen printf generiert würde

// in der Datei test.c

statische void print_events (Display * dpy)
{
    XEvent Event;

    während (1) {
    XNextEvent (dpy & Event);

    // [... einige andere Ereignistypen sind hier weggelassen ...]

        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");
    } 
    }
}

Ich habe die Quelle dahingehend geändert (siehe nächster Ausschnitt unten), dass ich eine Kopie der Ausgabe auf stderr haben kann. Diese Ausgabe kann ich umleiten:

 // in der Datei test.c

statische void print_events (Display * dpy)
{
    XEvent Event;

    während (1) {
    XNextEvent (dpy & Event);

    // [... einige andere Ereignistypen sind hier weggelassen ...]

        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": "press", key-> keycode);

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

Meine derzeitige Idee ist, dass das Programm durch die Weiterleitung möglicherweise nicht mehr in der Lage ist, die Ereignisse beim Drücken und Loslassen von Tasten zu überwachen.

Mensch und Frieden
quelle

Antworten:

55

Es ist nur so, dass die Ausgabe gepuffert wird, wenn stdout kein Terminal ist.

Und wenn Sie drücken Ctrl-C, geht dieser Puffer verloren, als wäre er noch nicht geschrieben worden.

Sie erhalten das gleiche Verhalten mit allem, was verwendet wird stdio. Versuchen Sie zum Beispiel:

grep . > file

Geben Sie ein paar nicht leere Zeilen ein und drücken Sie Ctrl-C, und Sie werden sehen, dass die Datei leer ist.

Geben Sie andererseits Folgendes ein:

xinput test 10 > file

Wenn Sie auf der Tastatur so viel eingeben, dass der Puffer voll ist (mindestens 4 KByte), wächst die Dateigröße um jeweils 4 KByte .

Mit grepkönnen Sie Ctrl-Dfor grepeingeben, um ordnungsgemäß zu beenden, nachdem der Puffer geleert wurde. Denn xinputich glaube nicht, dass es eine solche Option gibt.

Beachten Sie, dass standardmäßig stderrnicht gepuffert ist, was erklärt, warum Sie mit ein anderes Verhalten erhaltenfprintf(stderr)

Wenn in xinput.c, Sie hinzufügen signal(SIGINT, exit), also sagen , xinputanmutig zu verlassen , wenn es empfängt SIGINT, werden Sie sehen, die filenicht mehr leer ist (vorausgesetzt , es stürzt nicht, wie der Aufruf von Bibliotheksfunktionen von Signal - Handler ist nicht garantiert sicher: überlegen , was Dies kann passieren, wenn das Signal eingeht, während printf in den Puffer schreibt.

Wenn es verfügbar ist, können Sie stdbufdas stdioPufferverhalten mit dem Befehl ändern :

stdbuf -oL xinput test 10 > file

Auf dieser Website gibt es viele Fragen zum Deaktivieren der Stdio- Pufferung, in denen Sie noch mehr alternative Lösungen finden.

Stéphane Chazelas
quelle
2
WOW :) das hat den Trick gemacht. Dankeschön. Am Ende war meine Wahrnehmung des Problems also falsch. Es war nichts vorhanden, um die Umleitung zu unterbinden. Es war einfach, Strg-C stoppte es, bevor die Daten gelöscht wurden. danke
humanityANDpeace
Hätte es eine Möglichkeit gegeben, das Puffern von stdout zu verhindern?
humanityANDpeace
1
@Stephane Chazelas: vielen Dank für deine ausführliche Erklärung. Zusätzlich zu dem, was Sie bereits gesagt haben, habe ich herausgefunden, dass man den Puffer auf ungepuffert setzen kann setvbuf(stdout, (char *) NULL, _IONBF, NULL). Vielleicht ist das auch von Interesse !?
user1146332
4
@ user1146332, ja, das wäre was stdbuf -o0, während stdbug -oLdie Zeilenpufferung wiederhergestellt wird , wie wenn die Ausgabe an ein Terminal geht. stdbufzwingt die Anwendung, setvbufmit einem LD_PRELOADTrick aufzurufen .
Stéphane Chazelas
Ein weiteres Workaroudn: unbuffer test 10 > file( unbufferist Teil der expectTools)
Olivier Dulac
23

Ein Befehl kann direkt schreiben, um /dev/ttyeine regelmäßige Umleitung zu verhindern.

$ 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
quelle
Ihr Beispiel macht den Punkt + beantwortet die Frage. Ja, es ist möglich. Es ist natürlich "unerwartet" und für Programme ungeeignet, dies zu tun, was mich zumindest getäuscht hat, so etwas nicht für möglich zu halten. Die Antwort von user1146332 scheint auch ein überzeugender Weg zu sein, um eine Umleitung zu vermeiden. Um fair zu sein und da beide gegebenen Antworten gleichermaßen Möglichkeiten sind, die Umleitung der Befehlszeilenprogrammausgabe in eine Datei zu vermeiden, kann ich keine der Antworten auswählen, die ich denke :(. Ich müsste zwei Antworten auswählen dürfen, richtig. Vielen Dank!
humanityANDpeace
1
FTR, wenn Sie die /dev/ttyauf einem Linux-System geschriebene Ausgabe erfassen möchten , verwenden Sie script -c ./demo demo.log(from util-linux).
28.
Wenn Sie nicht in einem tty, sondern in einem pty laufen, können Sie dies anhand von procfs (/ proc / $ PID / fd / 0 usw.) feststellen. Um in das entsprechende pty zu schreiben, gehen Sie in das fd-Verzeichnis Ihres übergeordneten Prozesses und prüfen Sie, ob es sich um einen Symlink zu / dev / pts / [0-9] + handelt. Dann schreibst du auf dieses Gerät (oder rekursiv, wenn es kein Punkt ist).
Dhasenan
9

Es sieht so aus, als würde xinputdie Ausgabe in eine Datei abgelehnt, aber nicht in ein Terminal. xinputVerwenden Sie dazu wahrscheinlich den Systemaufruf

int isatty(int fd)

um zu prüfen, ob der zu öffnende Filedescriptor auf ein Terminal verweist oder nicht.

Ich bin vor einiger Zeit mit einem aufgerufenen Programm auf dasselbe Phänomen gestoßen dpic. Nachdem ich mir den Quellcode angesehen und einige Fehler behoben hatte, entfernte ich die dazugehörigen Zeilen isattyund alles funktionierte wieder wie erwartet.

Aber ich stimme dir zu, dass diese Erfahrung sehr beunruhigend ist;)

user1146332
quelle
Ich dachte wirklich, ich hätte meine Explosion. Aber (1) wenn man sich die Quelle ansieht (die Datei test.c im xinput-Quellpaket), sieht man, dass keine isattyTests durchgeführt wurden. Die Ausgabe wird durch die printfFunktion (ich denke, es ist ein Standard-C) erzeugt. Ich habe einige hinzugefügt fprintf(stderr,"output")und dies ist möglich, um + umzuleiten, um zu beweisen, dass der gesamte Code im Fall von xinput wirklich ausgeführt wird. Vielen Dank für den Vorschlag, es war doch der erste Trail hier.
humanityANDpeace
0

In Ihrer test.cDatei können Sie die gepufferten Daten mit (void)fflush(stdout);direkt nach Ihren printfAnweisungen leeren.

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

In der Befehlszeile können Sie die xinput test 10zeilengepufferte Ausgabe aktivieren, indem Sie den Befehl in einem Pseudoterminal (pty) ausführen script.

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

Ja. Ich habe das sogar in DOS-Zeiten gemacht, als ich in Pascal programmiert habe. Ich denke, das Prinzip gilt immer noch:

  1. Stdout schließen
  2. Öffnen Sie stdout als Konsole erneut
  3. Schreiben Sie die Ausgabe in stdout

Dies hat keine Rohre gebrochen.

Nils
quelle
"Stdout erneut öffnen": stdout ist als Dateideskriptor 1 definiert. Sie können Dateideskriptor 1 erneut öffnen, aber welche Datei würden Sie öffnen? Sie meinen wahrscheinlich, das Terminal zu öffnen. In diesem Fall spielt es keine Rolle, ob das Programm auf fd 1 schreibt.
Gilles 'SO - hör auf, böse zu sein'
@ Gilles die Datei war "con:" soweit ich mich erinnere - aber ja, ich habe Punkt 2 in diese Richtung verfeinert.
Nils
conist der DOS-Name für das, was Unix aufruft /dev/tty, dh das (steuernde) Terminal.
Gilles 'SO- hör auf böse zu sein'