Um programa de linha de comando pode impedir que sua saída seja redirecionada?

49

Eu me acostumei tanto a fazer isso: someprogram >output.file

Eu faço isso sempre que quero salvar a saída que um programa gera em um arquivo. Eu também estou ciente das duas variantes deste redirecionamento IO :

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

Hoje eu me deparei com uma situação que não achei possível. Eu uso o seguinte comando xinput test 10 e como esperado eu tenho a seguinte saída:

user@hostname:~$ xinput test 10
key press   30 
key release 30 
key press   40 
key release 40 
key press   32 
key release 32 
key press   65 
key release 65 
key press   61 
key release 61 
key press   31 
^C
user@hostname:~$ 

Eu esperava que essa saída pudesse, como de costume, ser salva em um arquivo como xinput test 10 > output.file . Mas quando contrair a minha expectativa o arquivo output.file permanece vazio. Isso também é verdade para xinput test 10 &> output.file apenas para garantir que eu não perca nada em stdout ou stderr.

Estou realmente confuso e, portanto, pergunto aqui se o programa xinput pode ter uma maneira de evitar que sua saída seja redirecionada?

atualizar

Eu olhei para a fonte. Parece que a saída é gerada por este código (veja o trecho abaixo). Parece-me que a saída seria gerada por um printf comum

//in file test.c

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

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

    // [... some other event types are omnited here ...]

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

Eu modifiquei a fonte para isso (veja o próximo trecho abaixo), o que me permite ter uma cópia da saída no stderr. Esta saída eu sou capaz de redirecionar:

 //in file test.c

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

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

    // [... some other event types are omnited here ...]

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

Minha ideia no momento é que, talvez, ao fazer o redirecionamento, o programa perde sua capacidade de monitorar os eventos de liberação de teclas pressionados pela tecla.

    
por humanityANDpeace 28.12.2012 / 11:26

5 respostas

55

É apenas que quando o stdout não é um terminal, a saída é armazenada em buffer.

E quando você pressiona Ctrl-C , esse buffer é perdido como / se ainda não foi escrito.

Você obtém o mesmo comportamento com qualquer coisa usando stdio . Tente por exemplo:

grep . > file

Insira algumas linhas não vazias e pressione Ctrl-C , e você verá que o arquivo está vazio.

Por outro lado, digite:

xinput test 10 > file

Digite o suficiente no teclado para que o buffer fique cheio (pelo menos 4k de saída), e você verá o tamanho do arquivo crescer em pedaços de 4k de cada vez.

Com grep , você pode digitar Ctrl-D para que grep seja encerrado normalmente após ter liberado seu buffer. Para xinput , não acho que exista essa opção.

Observe que, por padrão, stderr não é armazenado em buffer, o que explica por que você tem um comportamento diferente com fprintf(stderr)

Se, em xinput.c , você adicionar um signal(SIGINT, exit) , que diga xinput para sair normalmente quando receber SIGINT , verá que file não está mais vazio (supondo que não Não travar, pois chamar funções de biblioteca de manipuladores de sinais não é garantido como seguro: considere o que poderia acontecer se o sinal chegasse enquanto o printf estivesse gravando no buffer).

Se estiver disponível, você pode usar o comando stdbuf para alterar o comportamento do buffer stdio :

stdbuf -oL xinput test 10 > file

Existem muitas perguntas neste site que abrangem a desativação do tipo stdio buffering, onde você encontrará soluções ainda mais alternativas.

    
por 28.12.2012 / 12:47
23

Um comando pode gravar diretamente em /dev/tty , evitando o redirecionamento 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
    
por 28.12.2012 / 11:29
9

Parece que xinput rejeita a saída para um arquivo, mas não rejeita a saída para um terminal. Para conseguir isso, provavelmente xinput usa a chamada do sistema

int isatty(int fd)

para verificar se o filedescriptor a ser aberto se refere a um terminal ou não.

Eu tropecei no mesmo fenômeno há um tempo atrás com um programa chamado dpic . Depois que eu olhei para a fonte e alguma depuração eu removi as linhas relacionadas a isatty e tudo funcionou como esperado novamente.

Mas eu concordo com você que esta experiência é muito perturbadora;)

    
por 28.12.2012 / 11:37
0

Em seu arquivo test.c , você pode liberar os dados em buffer usando (void)fflush(stdout); diretamente após as instruções printf .

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

Na linha de comando, você pode ativar a saída com buffer de linha executando xinput test 10 em um pseudo terminal (pty) com o comando script .

script -q /dev/null xinput test 10 > file      # FreeBSD, Mac OS X
script -c "xinput test 10" /dev/null > file    # Linux
    
por 02.02.2013 / 13:47
-1

Sim. Eu até fiz isso nos tempos do DOS quando eu programava em pascal. Eu acho que o princípio ainda é válido:

  1. Fechar stdout
  2. Reabrir stdout como console
  3. Escreva a saída para stdout

Isso quebrou todos os canos.

    
por 28.12.2012 / 23:39