Geralmente paste
imprime dois arquivos nomeados (ou equivalentes) em colunas adjacentes como esta:
paste <(printf '%s\n' a b) <(seq 2)
Resultado:
a 1
b 2
Mas quando os dois arquivos são /dev/stdin
e /dev/stderr
, não parece funcionar da mesma maneira.
Suponha que temos um programa b falta b box que gera duas linhas na saída padrão e duas linhas no erro padrão . Para fins de ilustração, isso pode ser simulado com uma função:
bb() { seq 2 | tee >(sed 's/^/e/' > /dev/stderr) ; }
Agora execute annotate-output
, (no pacote devscripts no Debian/Ubuntu/etc. ), para mostrar que funciona:
annotate-output bash -c 'bb() { seq 2 | tee >(sed 's/^/e/' > /dev/stderr) ; }; bb'
22:06:17 I: Started bash -c bb() { seq 2 | tee >(sed s/^/e/ > /dev/stderr) ; }; bb
22:06:17 O: 1
22:06:17 E: e1
22:06:17 O: 2
22:06:17 E: e2
22:06:17 I: Finished with exitcode 0
Então funciona. Alimentar bb
para paste
:
bb | paste /dev/stdin /dev/stderr
Resultado:
1 e1
e2
^C
Ele trava - ^C
significa pressionar Control-C para sair.
Alterar o |
para a ;
também não funciona:
bb ; paste /dev/stdin /dev/stderr
Resultado:
1
2
e1
e2
^C
Também trava -- ^C
significa pressionar Control-C para sair.
Saída desejada:
1 e1
2 e2
Pode ser feito usando paste
? Se não, por que não?
Por que você não pode usar /dev/stderr como pipeline
O problema não é com
paste
, e nem com/dev/stdin
. É com/dev/stderr
.Todos os comandos são criados com um descritor de entrada aberto (0: entrada padrão) e duas saídas (1: saída padrão e 2: erro padrão). Eles normalmente podem ser acessados com os nomes
/dev/stdin
,/dev/stdout
e/dev/stderr
respectivamente, mas veja Quão portáteis são /dev/stdin, /dev/stdout e /dev/stderr? . Muitos comandos, incluindopaste
, também interpretarão o nome do arquivo-
como STDIN.Quando você executa
bb
sozinho, STDOUT e STDERR são o console, onde a saída do comando geralmente aparece. As linhas passam por diferentes descritores (como mostrado por seuannotate-output
), mas acabam no mesmo lugar.Quando você adiciona um
|
e um segundo comando, criando um pipeline...o
|
diz ao shell para conectar a saída debb
à entrada depaste
.paste
primeiro tenta ler de/dev/stdin
, que (através de alguns links simbólicos) resolve para seu próprio descritor de entrada padrão (que o shell acabou de conectar) para que a linha1
passe.Mas o shell/pipeline não faz nada para STDERR.
bb
ainda envia isso (e1
e2
etc.) para o console. Enquanto isso,paste
tenta ler a partir do mesmo console, que trava (até você digitar algo).Seu link Por que não consigo ler /dev/stdout com um editor de texto? ainda é relevante aqui porque essas mesmas restrições se aplicam ao
/dev/stderr
.Como fazer um segundo pipeline
Você tem um comando que produz tanto a saída padrão quanto o erro padrão e deseja
paste
essas duas linhas próximas uma da outra. Isso significa dois tubos simultâneos, um para cada coluna. O pipeline do shell... | ...
fornece um desses, e você precisará criar o segundo você mesmo e redirecionar o STDERR para ele usando2>filename
.Se for para uso em um script, você pode preferir fazer esse FIFO em um diretório temporário e removê-lo após o uso.
annotate-output
é capaz de fazer isso porque está fazendo algo especial (ou seja, redireciona o stderr de um comando para um fifo), algo que nãopaste
tem absolutamente nenhuma maneira de fazer - simplesmente porque nãopaste
está executando o comando (s) do qual obtém sua entrada , e não tem como redirecionar sua entrada ou sua saída.Mas você pode escrever um wrapper que use exatamente o mesmo truque que a saída de anotação está usando:
Observe, no entanto, que é propenso a bloqueios. Se, por exemplo,
bb
produzir mais saída padrão do que pode caber no tubo mais a quantidade extra inicialmente lida,paste
mas não produzir nenhuma saída de erro,paste
será bloqueado aguardando entrada no fifo e não esvaziará o tubo quebb
está alimentando seu stdout, fazendo com quebb
os write()s do pipe também travem.Existem alguns problemas com toda a linha que precisamos analisar, ou seja:
stderr
Primeiro, o último comando. Apenas stdout pode passar por um pipe:
Não há nada para ler
stderr
:Você precisa
^C
dele porque ele bloqueia, não há nada para lerstderr
.Mesmo se você criar alguma saída para
stderr
ela, ela não percorre um tubo:Exatamente como antes, o
1
é impresso e ospaste
blocos esperando porstderr
.Os outros 2 números foram diretamente para o console e foram impressos (independentemente).
Você poderia dar alguma entrada
stderr
no último comando do pipe:Que é exatamente o mesmo que
2>/dev/null
, aliás, para evitar bloquear o segundo descritor de arquivo usado nopaste
comando. Mas os valores impressos vêm diretamente doseq 3 4
redirecionado para o console, não dopaste
. Isso faz o mesmo:E isso não bloqueia:
ordem
Segundo, a saída de
tee
não precisa estar "em ordem". Ordem de substituição do processo `tee` e `bash`E, de fato: A saída de uma substituição de processo não precisa estar "em ordem": A saída de substituição de processo está fora de ordem
De fato, em alguns exemplos, se você tentar várias vezes, poderá obter pedidos diferentes. saída não determinística de processos independentes executados simultaneamente por substituição de processo
Então, não, não poderia ser feito com substituição de processo e pasta.
Você precisa dar alguma ordem para a execução:
bb
Então, sua função bb, que (basicamente) contém:
Que poderia ser testado com:
Deve imprimir 0, 1, 1000, 999, 998, nessa ordem, mas muitas vezes não.
Ou seja: é intrinsecamente instável.
Solução real estável.
A única solução segura para bb é evitar qualquer substituição de processo.
E, aproveitando que
{…}
captura tanto stdout quanto stderr, exemplo:Sem saída, remova o 2 para confirmar.
Isso funcionará para bb:
E use um fifo para colar:
Você precisará definir um trap para remover o arquivo fifo e testar se o arquivo fifo existe antes de criá-lo.
Parece funcionar de forma portátil em todos os shells (compatíveis com a sintaxe Almquist) que testei. Não totalmente testado, peça confirmação de outros usuários, pode haver algumas surpresas ainda desconhecidas.