Como um programa sabe se o stdout está conectado a um terminal ou tubo?


12

Estou tendo problemas para depurar um programa de segfaulting porque a saída imediatamente antes do segfault é o que eu preciso, mas isso será perdido se eu estiver canalizando a saída para um arquivo. De acordo com esta resposta: /unix//a/17339/22615 , isso ocorre porque o buffer de saída do programa libera imediatamente quando conectado a um terminal, mas apenas em determinados pontos quando conectado a um tubo. Algumas perguntas aqui:

  • Como um programa determina a que seu stdout está conectado?

  • Como o comando "script" produz o mesmo comportamento de quando o programa grava em um terminal?

  • Isso pode ser alcançado sem o comando de script?


Uma pergunta relacionada é unix.stackexchange.com/q/513926/5132 .
JdeBP

Respostas:


23

Dizer se um descritor de arquivo aponta para um dispositivo terminal

Um programa pode saber se um descritor de arquivo está associado a um dispositivo tty usando a isatty()função C padrão (que geralmente ocorre por baixo de uma ioctl()chamada inócua ao sistema específica de tty que retornaria com um erro quando o fd não aponta para um dispositivo tty) .

o [test utilitário / pode fazer isso com seu -toperador.

if [ -t 1 ]; then
  echo stdout is open to a terminal
fi

Rastreando chamadas da função libc em um sistema GNU / Linux:

$ ltrace [ -t 1 ] | cat
[...]
isatty(1)                                      = 0
[...]

Rastreando chamadas do sistema:

$ strace [ -t 1 ] | cat
[...]
ioctl(1, TCGETS, 0x7fffd9fb3010)        = -1 ENOTTY (Inappropriate ioctl for device)
[...]

Dizer se aponta para um tubo

Para determinar se um fd está associado a um pipe / fifo, pode-se usar a fstat()chamada do sistema , que retorna uma estrutura cujo st_modecampo contém o tipo e as permissões do arquivo aberto nesse fd. oS_ISFIFO() macro C padrão pode ser usada nesse st_modecampo para determinar se o fd é um pipe / fifo.

Não existe um utilitário padrão que possa fazer um fstat(), mas existem várias implementações incompatíveis de um statcomando que podem fazê-lo. zsh's statbuiltin com o stat -sf "$fd" +modequal retorna o modo como uma representação de string cujo primeiro caractere representa o tipo ( ppara pipe). O GNU statpode fazer o mesmo com stat -c %A - <&"$fd", mas também precisa stat -c %F - <&"$fd"reportar o tipo sozinho. Com BSDstat : stat -f %St <&"$fd"ou stat -f %HT <&"$fd".

Dizer se é procurável

Os aplicativos geralmente não se importam se o stdout é um pipe. Eles podem se importar com a possibilidade de procurar (embora geralmente não decida se o buffer deve ou não).

Para testar se um fd é procurável (tubos, soquetes, dispositivos tty não são procuráveis, arquivos regulares e a maioria dos dispositivos de bloco geralmente são), pode-se tentar uma lseek()chamada de sistema relativa com um deslocamento de 0 (tão inócuo). ddé um utilitário padrão que é uma interface paralseek() mas não pode ser usado para esse teste, pois as implementações não seriam necessárias lseek()se você solicitasse um deslocamento de 0.

No entanto, os shells zshe ksh93builtin procuram operadores:

$ strace -e lseek ksh -c ': 1>#((CUR))' | cat
lseek(1, 0, SEEK_CUR)                   = -1 ESPIPE (Illegal seek)
ksh: 1: not seekable
$ strace -e lseek zsh -c 'zmodload zsh/system; sysseek -w current -u 1 0 || syserror'
lseek(1, 0, SEEK_CUR)                   = -1 ESPIPE (Illegal seek)
Illegal seek

Desativando o buffer

o script comando usa um par de pseudo-terminais para capturar a saída de um programa, portanto, o stdout do programa (e stdin e stderr) será um dispositivo de pseudo-terminal.

Quando o stdout é para um dispositivo terminal, geralmente ainda existe algum buffer, mas é baseado em linha. printf/puts e co não escreverá nada até que um caractere de nova linha seja produzido. Para outros tipos de arquivos, o buffer é em blocos (de alguns quilos).

Existem várias opções para desativar o buffer que são discutidas em várias perguntas e respostas aqui (pesquise unbuffer ou stdbuf , Não é possível redirecionar a saída de corte fornece algumas abordagens) usando um pseudo-terminal, como pode ser feito por socat/ script/ expect/ unbuffer(um expectscript) / zsh's zptyou injetando código no executável para desativar o buffer conforme feito pelo GNU ou FreeBSD stdbuf.


1
Resposta incrível, muito obrigado por isso!
precisa saber é o seguinte

Outra abordagem específica do Linux é atravessar o /procdiretório e, para cada /proc/<integer>/diretório, procurar /proc/<integer>/fd/e localizar o descritor de arquivo que possui o mesmo número de inode em pipefs serverfault.com/q/48330/363611 No entanto, isso só é útil em scripts quando não é possível usar os syscalls descritos na resposta de Stephane, e é mais uma solução do que IMHO solução adequada
Sergiy Kolodyazhnyy

No BSD, lseekterá êxito em terminais e outros dispositivos de caracteres e simplesmente redefinirá um contador que será aumentado a cada leitura bem-sucedida (). Não sei se isso os torna "procuráveis".
mosvy

@mowwwalker Se esta resposta resolveu seu problema, reserve um momento e aceite-o clicando na marca de seleção à esquerda. Isso marcará a pergunta como respondida e é assim que os agradecimentos são expressos nos sites do Stack Exchange.
dessert
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.