Como 'colar' stdin ao lado de stderr?

3

Geralmente, paste imprime dois arquivos nomeados (ou equivalentes) em colunas adjacentes como esta:

paste <(printf '%s\n' a b) <(seq 2)

Saída:

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 b falta b caixa programa que gera duas linhas em saída padrão e duas linhas em 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 em 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 a paste :

bb | paste /dev/stdin /dev/stderr

Saída:

1   e1
e2
^C

Ele trava - ^C significa pressionar Control-C para sair.

Alterar o | para um ; também não funciona:

bb ; paste /dev/stdin /dev/stderr

Saída:

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 agc 09.10.2018 / 14:08

3 respostas

3

Por que você não pode usar o / dev / stderr como um pipeline

O problema não está com paste e nem com /dev/stdin . Está 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). Geralmente, eles podem ser acessados com os nomes /dev/stdin , /dev/stdout e /dev/stderr , mas veja Como portáteis são / dev / stdin, / dev / stdout e / dev / stderr? . Muitos comandos, incluindo paste , também interpretarão o nome do arquivo - para significar STDIN.

Quando você executa bb sozinho, tanto STDOUT quanto STDERR são o console, onde a saída do comando geralmente aparece. As linhas passam por diferentes descritores (como mostrado pelo seu annotate-output ), mas acabam no mesmo lugar.

Quando você adiciona um | e um segundo comando, fazendo um pipeline ...

bb | paste /dev/stdin /dev/stderr

o | diz ao shell para conectar a saída de bb à entrada de paste . paste primeiro tenta ler a partir de /dev/stdin , que (via alguns symlinks) resolve seu próprio descritor de entrada padrão (que o shell acabou de conectar) para que a linha 1 apareça.

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 o mesmo console, que trava (até que você digite algo).

Seu link Por que não consigo read / dev / stdout com um editor de texto? ainda é relevante aqui porque essas mesmas restrições se aplicam a /dev/stderr .

Como fazer um segundo pipeline

Você tem um comando que produz saída padrão e erro padrão e deseja paste dessas duas linhas próximas umas das outras. Isso significa dois canais simultâneos, um para cada coluna. O pipeline de shell ... | ... fornece um desses, e você precisará criar o segundo você mesmo e redirecionar o STDERR para ele usando 2>filename .

mkfifo RHS
bb 2>RHS | paste /dev/stdin RHS

Se isso for para uso em um script, você pode preferir fazer o FIFO em um diretório temporário e removê-lo após o uso.

    
por 09.10.2018 / 19:30
1

annotate-output é capaz de fazer isso porque está fazendo algo especial (isto é, ele redireciona o stderr de um comando para um fifo), algo que paste tem absolutamente nenhum modo de fazer - simplesmente porque paste é não executando em si o comando (s) do qual recebe 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ções está usando:

pasteout(){ f='mktemp -u'; mkfifo $f; "$@" 2>$f | paste - $f; rm $f; }
pasteout bb
    
por 11.10.2018 / 00:31
1

Existem alguns problemas com toda a linha que precisamos analisar, ou seja:

seq 2 | tee >(sed 's/^/e/' > /dev/stderr) | paste /dev/stdin /dev/stderr

stderr

Primeiro, o último comando. Somente stdout pode passar por um canal:

$ seq2 | paste -
1
2

$ seq2 | paste - -
1 2

Não há nada para ler em stderr :

$ seq 2 | paste - /dev/stderr 
1   ^C

Você precisa ^C porque bloqueia, não há nada para ler em stderr .
Mesmo se você criar alguma saída para stderr , ela não percorrerá um canal:

$ { seq 2; seq 3 4 >/dev/stderr; } | paste - /dev/stderr
1   3
4

Exatamente como antes, o 1 é impresso e os blocos paste aguardam stderr .
Os outros 2 números foram diretamente para o console e foram impressos (independentemente).

Você poderia dar alguma entrada para stderr no último comando do canal:

$ { seq 2; seq 3 4 >/dev/stderr; } | paste - /dev/stderr 2</dev/null
1
2
3
4

Qual é exatamente o mesmo que 2>/dev/null , a propósito, para evitar o bloqueio do segundo descritor de arquivo usado no comando paste . Mas os valores impressos vêm diretamente do seq 3 4 redirecionado para o console, não de paste . Isso faz o mesmo:

$ { seq 2; seq 3 4 >/dev/tty; } | paste - /dev/stderr 2</dev/null
1   
2   
3
4

E isso não bloqueia:

$ seq 2 | tee >(sed 's/^/e/' > /dev/stderr) | 
  paste /dev/stdin /dev/stderr 2</dev/null
1   
2   
e1
e2

encomenda

Em segundo lugar, a saída de tee não precisa estar "em ordem". ordem de substituição do processo 'tee' e 'bash'

E, na verdade: a saída de uma substituição de processo não precisa estar "em ordem": A saída de substituição do processo está fora da ordem

$ echo one; echo two > >(cat); echo three;
one
three
two

De fato, em alguns exemplos, se você tentar várias vezes, poderá obter pedidos diferentes. saída não determinista de processos independentes executados simultaneamente por substituição de processos

$ printf '%s\n' {0..1000} | tee >(head -n2) >(sort -grk1,1 | head -n3) >/dev/null
1000
999
998
0
1

Portanto, não, isso não poderia ser feito com a substituição e colagem de processos.
Você precisa dar alguma ordem para a execução:

$ seq 2 | { while read a; do printf "%s %s\n" "$a" "e$a" ; done; }
1 e1
2 e2

bb

Então, sua função bb, que (basicamente) contém:

| tee >(sed 's/^/e/')

Qual poderia ser testado com:

$ printf '%s\n' {0..1000} | tee >(sort -grk1,1 | head -n3 >&2) | head -n 2
0
1
291
290
289

Deve imprimir 0, 1, 1000, 999, 998, nessa ordem, mas muitas vezes não.
Ou seja: é intrinsecamente in-stable.

Solução real estável.

A única solução segura para bb é evitar qualquer substituição de processo.
E aproveitando que {…} captura stdout e stderr, por exemplo:

$ bash -c '{ echo test-str >/dev/stderr; }' 2>/dev/null

Sem saída, remova o 2 para confirmar.

Isso funcionará para bb:

$ bb() { seq 5 | tee /dev/stderr | sed 's/^/e/'; }

E use um fifo para colar:

$ mkfifo out2
$ bb 2>out2  | paste out2 -
1   e1
2   e2
3   e3
4   e4
5   e5

Você precisará definir uma armadilha para remover o arquivo fifo e testar se o arquivo fifo existe antes de criá-lo.

Parece funcionar portável em todas as shells (compatível com a sintaxe Almquist) que testei. Não totalmente testado, pedir confirmação de outros usuários, pode haver algumas surpresas ainda desconhecidas.

    
por 11.10.2018 / 03:03