Un programme de ligne de commande peut-il empêcher sa sortie d'être redirigée?

49

Je suis devenu tellement habitué à faire ceci: someprogram >output.file

Je le fais chaque fois que je veux enregistrer le résultat généré par un programme dans un fichier. Je connais également les deux variantes de cette redirection IO :

  • someprogram 2>output.of.stderr.file (pour stderr)
  • someprogram &>output.stderr.and.stdout.file (pour les deux stdout + stderr combinés)

Aujourd'hui, j'ai rencontré une situation que je n'aurais pas cru possible. J'utilise la commande suivante xinput test 10et comme prévu, j'ai la sortie suivante:

utilisateur @ nomhôte: ~ $ xinput test 10
touche enfoncée 30 
libération de clé 30 
touche 40 
libération de clé 40 
touche 32 
libération de clé 32 
touche 65 
libération de clé 65 
touche 61 
libération de clé 61 
touche 31 
^ C
utilisateur @ nom d'hôte: ~ $ 

Je pensais que cette sortie pourrait, comme d’habitude, être sauvegardée dans un fichier similaire à xinput test 10 > output.file. Mais lorsque cela est contraire à mes attentes, le fichier output.file reste vide. Ceci est également vrai pour xinput test 10 &> output.filem'assurer que je ne manque rien sur stdout ou stderr.

Je suis vraiment confus et demande donc ici si le xinputprogramme aurait un moyen d'éviter que sa sortie soit redirigée?

mise à jour

J'ai regardé la source. Il semble que la sortie soit générée par ce code (voir l'extrait de code ci-dessous). Il me semble que la sortie serait générée par un printf ordinaire

// dans le fichier test.c

static void print_events (Display * dpy)
{
    Événement XEvent;

    tandis que (1) {
    XNextEvent (dpy, & Event);

    // [... d'autres types d'événements sont omniprés ici ...]

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

        printf ("clé% s% d", (Event.type == key_release_type)? "release": "appuyez sur", clé-> code de touche);

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

J'ai modifié le source pour cela (voir l'extrait suivant ci-dessous), ce qui me permet d'avoir une copie de la sortie sur stderr. Cette sortie, je suis capable de rediriger:

 // dans le fichier test.c

static void print_events (Display * dpy)
{
    Événement XEvent;

    tandis que (1) {
    XNextEvent (dpy, & Event);

    // [... d'autres types d'événements sont omniprés ici ...]

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

        printf ("clé% s% d", (Event.type == key_release_type)? "release": "appuyez sur", clé-> code de touche);
        fprintf (stderr, "clé% s% d", (type d'événement == clé_release_type)? "release": "presse", clé-> code clé);

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

Mon idée actuelle est que, peut-être qu'en faisant la redirection, le programme perd sa capacité à surveiller les événements d'activation de touche.

humanitéet paix
la source

Réponses:

55

C'est simplement que lorsque stdout n'est pas un terminal, la sortie est mise en mémoire tampon.

Et lorsque vous appuyez sur Ctrl-C, ce tampon est perdu en tant que / s'il n'a pas encore été écrit.

Vous obtenez le même comportement avec n'importe quoi en utilisant stdio. Essayez par exemple:

grep . > file

Entrez quelques lignes non vides et appuyez sur Ctrl-C, vous verrez que le fichier est vide.

D'autre part, tapez:

xinput test 10 > file

Et tapez suffisamment sur le clavier pour que la mémoire tampon soit pleine (au moins 4 Ko de sortie), et vous verrez la taille du fichier augmenter de 4 k à la fois.

Avec grep, vous pouvez taper Ctrl-Dfor greppour sortir normalement après avoir vidé son tampon. Car xinput, je ne pense pas qu'il existe une telle option.

Notez que, par défaut, stderrn’est pas tamponné, ce qui explique pourquoi vous obtenez un comportement différent avecfprintf(stderr)

Si, dans xinput.c, vous ajoutez un signal(SIGINT, exit), c'est-à-dire que vous dites xinputde quitter normalement lorsqu'il reçoit SIGINT, vous verrez qu'il filen'est plus vide (en supposant qu'il ne se bloque pas, car appeler des fonctions de bibliothèque à partir de gestionnaires de signaux n'est pas garanti. Cela peut arriver si le signal arrive pendant que printf écrit dans le tampon).

Si elle est disponible, vous pouvez utiliser la stdbufcommande pour modifier le stdiocomportement de mise en mémoire tampon:

stdbuf -oL xinput test 10 > file

Il existe de nombreuses questions sur ce site qui traitent de la désactivation de la mise en mémoire tampon de type stdio où vous trouverez encore plus de solutions alternatives.

Stéphane Chazelas
la source
2
WOW :) qui a fait le tour. Merci. En fin de compte, ma perception du problème était erronée. Il n'y avait rien en place pour empêcher la redirection, c'était simple, Ctrl-C l'arrêtait avant que les données ne soient effacées. merci
humanitepeace
Y aurait-il un moyen d'empêcher la mise en mémoire tampon de stdout?
humanite et
1
@ Stéphane Chazelas: merci beaucoup pour votre explication détaillée. En plus de ce que vous avez déjà dit, j'ai découvert que l'on peut définir le tampon sur non tamponné setvbuf(stdout, (char *) NULL, _IONBF, NULL). C'est peut-être aussi intéressant !?
user1146332
4
@ user1146332, oui, ce serait ce stdbuf -o0fait, tout en stdbug -oLrestaure ligne en mémoire tampon comme lorsque la sortie passe à un terminal. stdbufforce l'application à appeler en setvbufutilisant une LD_PRELOADastuce.
Stéphane Chazelas
un autre workaroudn: unbuffer test 10 > file( unbufferfait partie des expectoutils)
Olivier Dulac
23

Une commande peut directement écrire pour /dev/ttyempêcher la redirection habituelle.

$ 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
la source
Votre exemple fait le point + répond à la question. Oui c'est possible. Il est bien entendu "inattendu" et inhabituel pour les programmes de le faire, ce qui au moins m'a trompé en ne considérant pas une telle chose possible. La réponse de user1146332 semble également un moyen convaincant d'éviter la redirection. Pour être juste et puisque les deux réponses données sont des moyens non moins évitables de rediriger la sortie du programme en ligne de commande vers un fichier, je ne peux sélectionner aucune des réponses, je suppose :(. J'aurais besoin de pouvoir sélectionner deux réponses correctement. Excellent travail, Merci!
humanitepublique
1
FTR, si vous voulez capturer la sortie écrite /dev/ttysur un système Linux, utilisez script -c ./demo demo.log(depuis util-linux).
Ndim
Si vous ne travaillez pas dans un tty, mais plutôt dans un pty, vous pouvez le trouver en consultant procfs (/ proc / $ PID / fd / 0, etc.). Pour écrire sur le pty approprié, allez dans le répertoire fd de votre processus parent et voyez s'il s'agit d'un lien symbolique vers / dev / pts / [0-9] +. Ensuite, vous écrivez sur ce périphérique (ou recurse si ce n’est pas un pts).
Dhasenan
9

Il semble que la xinputsortie d'un fichier soit rejetée, mais pas celle d'un terminal. Pour ce faire, xinpututilisez probablement l'appel système

int isatty(int fd)

vérifier si le FileScriptor à ouvrir fait référence à un terminal ou non.

Je suis tombé sur le même phénomène il y a un moment avec un programme appelé dpic. Après avoir jeté un œil sur le code source et un peu de débogage, j’ai supprimé les lignes associées isattyet tout a fonctionné comme prévu à nouveau.

Mais je suis d'accord avec vous que cette expérience est très dérangeante;)

utilisateur1146332
la source
Je pensais vraiment avoir mon explication. Mais (1) en regardant la source (le fichier test.c du paquet source xinput), il n’ya aucune apparence de isattytest effectué. La sortie est générée par une printffonction (je pense que c’est un C standard). J'en ai ajouté quelques fprintf(stderr,"output")- unes et il est possible de les rediriger + prouve que tout le code est vraiment exécuté dans le cas de xinput. Merci pour la suggestion après tout c'était la première piste ici.
humanite et
0

Dans votre test.cfichier, vous pouvez vider les données mises en mémoire tampon (void)fflush(stdout);directement après vos printfdéclarations.

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

Sur la ligne de commande, vous pouvez activer la sortie avec mise xinput test 10en tampon de ligne en exécutant la commande dans un pseudo-terminal (pty) script.

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

Oui. Je l'ai même fait dans DOS-times quand j'ai programmé en pascal. Je suppose que le principe est toujours valable:

  1. Fermer la sortie
  2. Rouvrez stdout en tant que console
  3. Ecrire la sortie sur stdout

Cela a cassé des tuyaux.

Nils
la source
“Re-Open stdout”: stdout est défini comme descripteur de fichier 1. Vous pouvez rouvrir le descripteur de fichier 1, mais quel fichier ouvririez-vous? Vous voulez probablement dire ouvrir le terminal, auquel cas le programme n'a pas d'importance si fd 1.
SO 'arrête de faire le mal de Gilles'
@Gilles, le fichier était "con:" autant que je m'en souvienne - mais oui, j'ai raffiné le point 2 dans cette direction.
Nils
conest le nom DOS de ce que appelle unix /dev/tty, c’est-à-dire le terminal (de contrôle).
Gilles, arrête d'être méchant