$ seq 10 | unbuffer -p od -vtc
0000000 1 \n 2 \n 3 \n 4 \n 5 \n 6 \n 7 \n 8 \n
Para onde foi 9
e 10
foi?
$ printf '\r' | unbuffer -p od -An -w1 -vtc
\n
Por que foi \r
alterado para \n
?
$ : | unbuffer -p printf '\n' | od -An -w1 -vtc
\r
\n
$ unbuffer -p printf '\n' | od -An -w1 -vtc
\r
\n
O que é isso?
$ printf foo | unbuffer -p cat
$
Por que nenhuma saída (e um atraso de um segundo)?
$ printf '\1\2\3foo bar\n' | unbuffer -p od -An -w1 -vtc
$
Por que não há saída?
$ (printf '\23'; seq 10000) | unbuffer -p cat
Por que ele trava sem saída?
$ unbuffer -p sleep 10
Por que não consigo ver o que digito (e por que é descartado mesmo sleep
sem ter lido)?
Aliás, também:
$ echo test | unbuffer -p grep foo && echo found foo
found foo
Como foi grep
encontrado foo
, mas não imprimiu as linhas que o contêm?
$ unbuffer -p ls /x 2> /dev/null
ls: cannot access '/x': No such file or directory
Por que o erro não foi para /dev/null
?
Veja também Unbuffer convertendo todos os caracteres em sino?
$ echo ${(l[10000][foo])} | unbuffer -p cat | wc -c
4095
Isso é com:
$ lsb_release -a
No LSB modules are available.
Distributor ID: Debian
Description: Debian GNU/Linux trixie/sid
Release: n/a
Codename: trixie
$ uname -rsm
Linux 6.5.0-3-amd64 x86_64
$ expect -c 'puts "expect [package require Expect] tcl [info patchlevel]"'
expect 5.45.4 tcl 8.6.13
$ /proc/self/exe --version
zsh 5.9 (x86_64-debian-linux-gnu)
O mesmo no Ubuntu 22.04 ou no FreeBSD 12.4-RELEASE-p5 (exceto que os od
comandos precisam ser adaptados lá, e eu recebo 2321 (todos os caracteres BEL lá) em vez de 4095 acima).
unbuffer
é uma ferramenta para desabilitar o buffer que alguns comandos fazem quando sua saída não vai para um dispositivo terminal.Quando a saída vai para um dispositivo terminal, os comandos assumem que há um usuário real olhando ativamente para a saída, então eles a enviam assim que ela estiver disponível. Bem, não exatamente, eles enviam com base em linhas, ou seja, enviam linhas concluídas assim que estiverem prontas para saída.
Quando não vai para um dispositivo terminal, como quando stdout é um arquivo normal ou um pipe, como otimização, eles o enviam em blocos. Isso significa menos
write()
s e, no caso de um pipe, significa que o leitor do outro lado não precisa ser acordado com tanta frequência, o que significa menos mudanças de contexto.No entanto, isso significa que em:
executado em um terminal, onde
other-cmd
há algum tipo de comando de filtragem/transformação,other-cmd
o stdout de é com buffer de linha, mascmd
o de é com buffer completo, o que significa que o usuário interativo não vê a saída decmd
(conforme transformada porother-cmd
) assim que pois está disponível, mas atrasado e em grandes lotes.Ajuda porque restaura um buffer baseado em linha,
cmd
mesmo que seu stdout esteja indo para um canal.Para isso, ele inicia
cmd
em um pseudoterminal e encaminha o que vem desse pseudoterminal para o pipe. Entãocmd
pensa que está conversando com um usuário novamente e faz o buffer de linha.unbuffer
na verdade está escrito emexpect
. É um exemplo de script noexpect
código-fonte , geralmente incluído noexpect
pacote fornecido pelos sistemas operacionais.expect
é uma ferramenta usada para realizar interações automáticas com aplicativos de terminal usando pseudo-terminais, de modo queunbuffer
o comando é trivial para escreverexpect
. Brincando, a seção BUGSunbuffer
da página de manual do tem: A página de manual é mais longa que o programa. E, de fato, o programa é apenas:Como você pode ver e confirmado pela página de manual,
unbuffer
também suporta uma-p
opção.No
unbuffer cmd
, o pseudoterminal não está conectado apenas ao stdout do cmd, mas também ao seu stdin e stderr (lembre-se queexpect
é uma ferramenta destinada a interagir com comandos):Isso explica por que
unbuffer ls /x 2> /dev/null
não enviou os erros para/dev/null
, o stderr foi mesclado com o stdout.Agora,
unbuffer
não lê nada do seu próprio stdin e não envia nada para o stdin docmd
.Isso significa que
A | unbuffer cmd | B
não vai funcionar.É aí que entra a opção
-p
(forp
ipe). Como visto no código, with-p
,unbuffer
usainteract
em vez deexpect
como o loop ativo que processa os dados provenientes dos diferentes canais.Somente com a
expect
instrução,expect
(o programa/biblioteca TCL) lê o que vem do pseudoterminal (ou seja, o quecmd
escreve no lado escravo por meio de seu stdout ou stderr, por exemplo) e apenas envia para seu próprio stdout.Com
interact
,expect
faz isso, mas também:cmd
possa ler lá)unbuffer
stdin for um dispositivo terminal,interact
coloca-o noraw
modo com localecho
desabilitado.Isso é bom porque a saída de in
A | unbuffer -p cmd | B
, pode ser lida como entrada , mas significa algumas coisas:A
cmd
unbuffer
configura o pseudoterminal interno comset stty_init "-echo"
, mas não noraw
modo. Em particular,isig
(o tratamento de^C
(\3
) /^Z
/^\
),ixon
(controle de fluxo,^Q
/^S
(\23
)) não estão desativados. Quando a entrada é um dispositivo terminal (que é comoexpect
'sinteract
deve ser usado, mas nãounbuffer
), tudo bem, pois o host é colocado emraw
modo, o que significa apenas que o processamento é movido do terminal host para o pseudo-incorporado. terminal, exceto pelo fato deecho
estar desabilitado em ambos para que você não possa ver o que digita. Mas quando não é um dispositivo terminal, isso significa que, por exemplo, qualquer byte 0x3 (^C
) na entrada (como ao processar a saída deprintf '\3'
) aciona um SIGINT e finaliza o comando, qualquer byte 0x19 (printf '\23'
) interrompe o fluxo.icrnl
não estar desabilitado explica por que\r
's foram alterados para\n
's.Ele não faz o
stty -opost
que faria sem-p
. Isso explica por que a\n
saída de bycmd
foi alterada para\r\n
. E quando a entrada é um dispositivo terminal, o fato de colocá-lo emraw
, portanto, comopost
desativado explica a saída do terminal desconfigurada quando os caracteres de nova linha gerados porod
não são transformados em\r\n
.o pseudo-terminal interno ainda tem o editor de linha habilitado, então nada será enviado
cmd
a menos que haja um caractere\r
or\n
vindo da entrada, o que explica por queprintf foo | unbuffer -p cat
não imprime nada.E como esse editor de linha tem um limite no tamanho da linha que pode editar ( 4095 no meu sistema (Linux) , um quinto da velocidade tty ¹ no FreeBSD), você acaba com o tipo de problema no Unbuffer ao converter todos os caracteres tocar a campainha? : acontece a mesma coisa que quando você tenta inserir uma linha muito longa no teclado em um aplicativo idiota como
cat
. No Linux, todos os caracteres após o 4094 são ignorados, mas\n
são aceitos e submetem a linha; no FreeBSD, após a inserção de 38400/5 caracteres, qualquer extra é recusado (mesmo\n
) e faz com que um BEL seja enviado ao terminal². O que explica por que você obtém 2.321 BELs lá (10.001 - 38.400/5).O manuseio de EOF é complicado com dispositivos pseudoterminais. Quando EOF é visto no
unbuffer
stdin, ele não pode encaminhar essas informações paracmd
. Então inseq 10 | od -vtc
, depoisseq
de terminar,od
ainda está aguardando mais entradas do pseudoterminal que nunca chegarão. Em vez disso, nesse ponto, tudo é demolido eod
eliminado (a página de manual menciona essa limitação).Para seu próprio propósito, seria muito melhor
unbuffer
colocar o pseudo-tty incorporado noraw -echo
modo e deixar o dispositivo terminal host (se houver) sozinho. No entantoexpect
, não suporta realmente esse modo de operação, não foi projetado para isso.Agora, se
unbuffer
se trata de remover stdout do buffer, não há razão para que ele toque em stdin e stderr.Na verdade, podemos contornar isso fazendo:
Isso é usado
sh
para restaurar o stdin e o stderr originais (transmitidos pelo shell de chamada via fds 4 e 5; não usando fd 3 comoexpect
acontece com o uso explícito daquele internamente).Então:
Apenas o stdout vai para o pseudo-terminal para ser removido do buffer.
E todos os outros problemas desaparecem:
Além disso, a instalação
expect
(que requer um interpretador TCL) parece um pouco exagerada quando tudo o que você precisa é fazer com que o stdout passecmd
por um pseudo-terminal.socat
também pode fazer isso:(ele registra o status de saída de falha, mas por outro lado não propaga o status de saída do comando).
O
zsh
shell ainda possui suporte integrado para pseudo-ttys, e umaunbuffer
função pode ser escrita com pouco esforço com:Cuidado, todos eles acabam rodando em um novo terminal e exceto pela
socat
abordagem (a menos que você use as opçõesctty
esetid
) em uma nova sessão. Portanto, agora, se esses "fixos"unbuffer
forem iniciados em segundo plano na sessão do terminal host, acmd
leitura do terminal host não será interrompida. Por exemplo,unbuffer cat&
acabará com uma leitura de trabalho em segundo plano em seu terminal, causando estragos.¹ Limitado a 65536. A velocidade de um pseudo-terminal é irrelevante, mas deve haver um anunciado e acho que é 38400 por padrão no sistema FreeBSD em que testei. Como a velocidade é copiada daquela do terminal que controla
expect
, pode-se fazer umstty speed 115200
(o valor máximo AFAICT) antes de chamarunbuffer
para ampliar esse buffer. Mas você pode descobrir que ainda não obteve a linha grande completa de 10.000 caracteres. Isso é explicado no código do driver . Você encontraráunbuffer -p cat
retornos de apenas 4.096 bytes porque isso é o máximocat
solicitado em sua primeiraread()
chamada, e o driver tty retornou a mesma quantidade da linha de entrada, mas descartou o restante (!). Se você substituir porunbuffer -p dd bs=65536
, você obterá a linha completa (bem, até 115200/5 bytes).² você pode evitar esses BELs substituindo
set stty_init "-echo"
porset stty_init "-echo -imaxbel"
nounbuffer
script, mas isso não ajudará você a obter os dados.