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

49

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

Faço isso sempre que quero salvar a saída que um programa gera em um arquivo. Também estou ciente das duas variantes desse redirecionamento de entrada / saída :

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

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

user @ hostname: ~ $ xinput test 10
pressionar a tecla 30 
liberação da chave 30 
pressionar a tecla 40 
liberação da chave 40 
pressionar a tecla 32 
liberação da chave 32 
pressionar a tecla 65 
liberação da tecla 65 
pressionar a tecla 61 
liberação da tecla 61 
pressionar a tecla 31 
^ C
usuário @ hostname: ~ $ 

Eu esperava que essa saída pudesse, como sempre, ser salva em um arquivo como o uso xinput test 10 > output.file. Mas, ao contrário da minha expectativa, o arquivo output.file permanece vazio. Isso também é válido xinput test 10 &> output.fileapenas para garantir que eu não perca algo no stdout ou stderr.

Estou realmente confuso e, portanto, pergunto aqui se o xinputprograma 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 esse código (veja o trecho abaixo). Parece-me que a saída seria gerada por um printf comum

// no arquivo test.c

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

    enquanto (1) {
    XNextEvent (dpy, & Evento);

    // [... alguns outros tipos de eventos são omitidos aqui ...]

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

        printf ("chave% s% d", (Event.type == key_release_type)? "release": "pressione", tecla-> código da tecla);

        for (loop = 0; loopaxes_count; loop ++) {
        printf ("a [% d] =% d", chave-> primeiro_axis + loop, chave-> eixo_dados [loop]);
        }
        printf ("\ n");
    } 
    }
}

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:

 // no arquivo test.c

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

    enquanto (1) {
    XNextEvent (dpy, & Evento);

    // [... alguns outros tipos de eventos são omitidos aqui ...]

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

        printf ("chave% s% d", (Event.type == key_release_type)? "release": "pressione", tecla-> código da tecla);
        fprintf (stderr, "chave% s% d", (Event.type == key_release_type)? "release": "pressione", tecla-> código da tecla);

        for (loop = 0; loopaxes_count; loop ++) {
        printf ("a [% d] =% d", chave-> primeiro_axis + loop, chave-> eixo_dados [loop]);
        }
        printf ("\ n");
    } 
    }
}

Minha idéia no momento é que, talvez, ao redirecionar o programa perca a capacidade de monitorar os eventos de liberação de tecla.

humanidade e paz
fonte

Respostas:

55

É que quando 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 gravado.

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

grep . > file

Digite algumas linhas não vazias e pressione Ctrl-Ce você verá que o arquivo está vazio.

Por outro lado, digite:

xinput test 10 > file

E 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 por vez.

Com grep, você pode digitar Ctrl-Dpara grepsair normalmente depois de liberar seu buffer. Pois xinput, eu não acho que exista essa opção.

Observe que, por padrão, stderrnão é armazenado em buffer, o que explica por que você obtém um comportamento diferente comfprintf(stderr)

Se, em xinput.c, você adicionar um signal(SIGINT, exit), que é solicitado xinputa sair normalmente quando receber SIGINT, verá fileque ele não está mais vazio (supondo que ele não trava, pois as funções de chamada da biblioteca dos manipuladores de sinal não são garantidas com segurança: considere o que pode acontecer se o sinal chegar enquanto printf estiver gravando no buffer).

Se estiver disponível, você pode usar o stdbufcomando para alterar o stdiocomportamento do buffer:

stdbuf -oL xinput test 10 > file

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

Stéphane Chazelas
fonte
2
WOW :) que fez o truque. obrigado. Então, no final, minha percepção do problema estava errada. Não havia nada no local para inibir o redirecionamento, era simples que o Ctrl-C o interrompeu antes que os dados fossem liberados. obrigado
humanANDpeace
Teria havido uma maneira de impedir o armazenamento em buffer do stdout?
Humanandpeace
11
@ Stephane Chazelas: muito obrigado pela sua explicação detalhada. Além do que você já disse, descobri que é possível definir o buffer como inalterado setvbuf(stdout, (char *) NULL, _IONBF, NULL). Talvez isso também seja interessante !?
user1146332
4
@ user1146332, sim, seria o que stdbuf -o0faz, enquanto stdbug -oLrestaura o buffer de linha como quando a saída vai para um terminal. stdbufforça o aplicativo a chamar setvbufusando um LD_PRELOADtruque.
Stéphane Chazelas
outra workaroudn: unbuffer test 10 > file( unbufferfaz parte das expectferramentas)
Olivier Dulac
23

Um comando pode gravar diretamente para /dev/ttyimpedir 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
jlliagre
fonte
Seu exemplo faz o ponto + responde à pergunta. Sim, é possível. É claro que é "inesperado" e desagradável que os programas o façam, o que pelo menos me enganou ao não considerar isso possível. A resposta do usuário1146332 também parece uma maneira convincente de evitar o redirecionamento. Para ser justo, e como as duas respostas dadas são maneiras igualmente possíveis de evitar o redirecionamento da saída do programa de linha de comando para um arquivo, não consigo selecionar nenhuma das respostas que acho :(. Eu precisaria ter permissão para selecionar duas respostas corretamente. Bom trabalho, Obrigado!
humanidadeANDpeace
11
FTR, se você deseja capturar a saída gravada /dev/ttyem um sistema Linux, use script -c ./demo demo.log(from util-linux).
Ndim 28/12/12
Se você não está executando em um tty, mas em um pty, pode encontrar isso consultando procfs (/ proc / $ PID / fd / 0 etc). Para escrever no pty apropriado, vá para o diretório fd do processo pai e veja se é um link simbólico para / dev / pts / [0-9] +. Então você escreve para esse dispositivo (ou recursa se não for um pts).
dhasenan
9

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

int isatty(int fd)

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

Encontrei o mesmo fenômeno há algum tempo com um programa chamado dpic. Depois que eu olhei para a fonte e alguma depuração, removi as linhas relacionadas isattye tudo funcionou como esperado novamente.

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

user1146332
fonte
Eu realmente pensei que tinha minha explicação. Mas (1) olhando para a fonte (o arquivo test.c no pacote fonte xinput) não há aparência de isattyteste feito. A saída é gerada pela printffunção (eu acho que é o C padrão). Eu adicionei alguns fprintf(stderr,"output")e isso é possível redirecionar + prova que todo o código é realmente executado no caso do xinput. Obrigado pela sugestão, afinal foi a primeira trilha aqui.
HumanANDpeace
0

No seu test.carquivo, você pode liberar os dados em buffer usando (void)fflush(stdout);diretamente após suas printfinstruções.

    // 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 habilitar a saída com buffer de linha executando xinput test 10em um pseudo terminal (pty) com o scriptcomando

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

Sim. Até fiz isso no DOS quando programava em pascal. Eu acho que o princípio ainda vale:

  1. Fechar stdout
  2. Reabra o stdout como console
  3. Escreva a saída em stdout

Isso quebrou qualquer cano.

Nils
fonte
“Re-Open stdout”: stdout é definido como o descritor de arquivo 1. Você pode reabrir o descritor de arquivo 1, mas que arquivo você abriria? Você provavelmente quer dizer abrir o terminal; nesse caso, não importa se o programa está gravando para o código 1.
Gilles 'SO- deixa de ser mau'
@Gilles, o arquivo era "con:" tanto quanto me lembro - mas sim, refinei o ponto 2 nessa direção.
Nils
coné o nome do DOS para o que o unix chama /dev/tty, ou seja, o terminal (controlador).
Gilles 'SO- stop be evil'