Como posso enviar stdout para vários comandos?

186

Existem alguns comandos que filtram ou agem na entrada e, em seguida, transmitem-na como saída, penso eu normalmente stdout- mas alguns comandos simplesmente aceitam stdine fazem o que fazem com ela e não produzem nada.

Estou mais familiarizado com o OS X e, portanto, existem dois que vêm à mente imediatamente pbcopye pbpaste- que são meios de acessar a área de transferência do sistema.

De qualquer forma, eu sei que se eu quiser pegar o stdout e cuspir a saída para ir para o stdoutarquivo e para os dois, então posso usar o teecomando E eu sei um pouco xargs, mas acho que não é isso que estou procurando.

Quero saber como posso dividir stdoutpara ir entre dois (ou mais) comandos. Por exemplo:

cat file.txt | stdout-split -c1 pbcopy -c2 grep -i errors

Provavelmente, há um exemplo melhor do que esse, mas estou realmente interessado em saber como posso enviar stdout para um comando que não o retransmite e, ao mesmo tempo, stdoutnão seja "silenciado" - não estou perguntando sobre como catum arquivo e grepparte dele e copie-o para a área de transferência - os comandos específicos não são tão importantes.

Além disso - não estou perguntando como enviar isso para um arquivo e stdout- isso pode ser uma pergunta "duplicada" (desculpe), mas fiz algumas pesquisas e só consegui encontrar perguntas semelhantes que estavam perguntando sobre como dividir entre stdout e um arquivo - e as respostas para essas perguntas pareciam ser tee, o que acho que não funcionará para mim.

Finalmente, você pode perguntar "por que não fazer do pbcopy a última coisa na cadeia de tubos?" e minha resposta é 1) e se eu quiser usá-lo e ainda ver a saída no console? 2) e se eu quiser usar dois comandos que não saem stdoutdepois que eles processam a entrada?

Ah, e mais uma coisa - eu sei que eu poderia usar teee um pipe nomeado ( mkfifo), mas eu esperava uma maneira de que isso pudesse ser feito em linha, de forma concisa, sem uma configuração prévia :)

cwd
fonte
Duplicate possíveis de unix.stackexchange.com/questions/26964/...
Nikhil Mulley

Respostas:

240

Você pode usar teee processar a substituição para isso:

cat file.txt | tee >(pbcopy) | grep errors

Isso enviará toda a saída de cat file.txtpara pbcopye você só obterá o resultado grepno seu console.

Você pode colocar vários processos na teepeça:

cat file.txt | tee >(pbcopy) >(do_stuff) >(do_more_stuff) | grep errors
Esteira
fonte
21
Não é uma preocupação pbcopy, mas vale a pena mencionar em geral: qualquer que seja a saída de substituição do processo, também é vista pelo próximo segmento de tubulação, após a entrada original; por exemplo: seq 3 | tee >(cat -n) | cat -e( cat -nnumera as linhas de entrada, cat -emarca novas linhas $; você verá que cat -eé aplicado à entrada original (primeiro) e (depois) à saída de cat -n). A saída de várias substituições de processo chegará em ordem não determinística.
usar o seguinte comando
49
O >(único funciona bash. Se você tentar usar isso, por exemplo sh, não funcionará. É importante fazer esse aviso.
AAlvz
10
@AAlvz: Bom ponto: a substituição do processo não é um recurso do POSIX; dash, que atua como shno Ubuntu, não é compatível e até o próprio Bash desativa o recurso quando invocado como shou quando set -o posixestá em vigor. No entanto, não é apenas Bash que suporta substituições de processos: kshe zshapoiá-los também (não tenho certeza sobre outros).
precisa saber é o seguinte
2
@ mklement0 que não parece ser verdade. No zsh (Ubuntu 14.04), sua linha é impressa: 1 1 2 2 3 3 1 $ 2 $ 3 $ O que é triste, porque eu realmente queria que a funcionalidade fosse como você diz.
Aktau
2
@Aktau: Na verdade, o meu comando de exemplo só funcionam conforme descrito no bashe ksh- zshaparentemente não enviar a saída de substituições de processo de saída através do pipeline (sem dúvida, é preferível , porque não poluem o que é enviado para o segmento de pipeline próxima - embora ainda imprime ). Em todas as shells mencionadas, no entanto, geralmente não é uma boa idéia ter um único pipeline no qual a saída stdout e a saída regulares de substituições de processo sejam misturadas - a ordem da saída não será previsível, de uma maneira que só possa aparecer com pouca frequência ou com grande volume. conjuntos de dados de saída.
mklement0
124

Você pode especificar vários nomes de arquivos teee, além disso, a saída padrão pode ser canalizada em um comando. Para despachar a saída para vários comandos, você precisa criar vários pipes e especificar cada um deles como uma saída de tee. Existem várias maneiras de fazer isso.

Substituição de processo

Se seu shell for ksh93, bash ou zsh, você poderá usar a substituição de processo. Essa é uma maneira de passar um canal para um comando que espera um nome de arquivo. O shell cria o canal e passa um nome de arquivo como /dev/fd/3para o comando O número é o descritor de arquivo ao qual o canal está conectado. Algumas variantes do unix não suportam /dev/fd; nesses, um pipe nomeado é usado (veja abaixo).

tee >(command1) >(command2) | command3

Descritores de arquivo

Em qualquer shell POSIX, você pode usar vários descritores de arquivo explicitamente. Isso requer uma variante unix compatível /dev/fd, pois todas, exceto uma das saídas de, teedevem ser especificadas pelo nome.

{ { { tee /dev/fd/3 /dev/fd/4 | command1 >&9;
    } 3>&1 | command2 >&9;
  } 4>&1 | command3 >&9;
} 9>&1

Canais nomeados

O método mais básico e portátil é usar pipes nomeados . A desvantagem é que você precisa encontrar um diretório gravável, criar os pipes e limpar depois.

tmp_dir=$(mktemp -d)
mkfifo "$tmp_dir/f1" "$tmp_dir/f2"
command1 <"$tmp_dir/f1" & pid1=$!
command2 <"$tmp_dir/f2" & pid2=$!
tee "$tmp_dir/f1" "$tmp_dir/f2" | command3
rm -rf "$tmp_dir"
wait $pid1 $pid2
Gilles
fonte
10
Muito obrigado por fornecer as duas versões alternativas para aqueles que não querem confiar no bash ou em um determinado ksh.
trr
tee "$tmp_dir/f1" "$tmp_dir/f2" | command3certamente deve ser command3 | tee "$tmp_dir/f1" "$tmp_dir/f2", como você quer que seja command3canalizado tee, não? Testei sua versão dashe teebloqueia indefinidamente a espera de entrada, mas alternar a ordem produziu o resultado esperado.
Adrian Günter
1
@ AdrianGünter Não. Todos os três exemplos leem dados da entrada padrão e os enviam para cada um dos command, command2e command3.
Gilles
@ Gilles, eu entendi mal a intenção e tentei usar o trecho incorretamente. Obrigado pelo esclarecimento!
Adrian Günter
Se você não tem controle sobre o shell usado, mas pode usar o bash explicitamente, pode fazê-lo <command> | bash -c 'tee >(command1) >(command2) | command3'. Isso ajudou no meu caso.
Gc5 # 13/18
16

Basta jogar com a substituição do processo.

mycommand_exec |tee >(grep ook > ook.txt) >(grep eek > eek.txt)

grepsão dois binários que têm a mesma saída da mycommand_execentrada específica do processo.

Nikhil Mulley
fonte
16

Se você estiver usando zsh, poderá tirar proveito do poder do MULTIOSrecurso, ou seja, livrar-se teecompletamente do comando:

uname >file1 >file2

apenas gravará a saída de unamedois arquivos diferentes: file1e file2, o que é equivalente auname | tee file1 >file2

Da mesma forma, o redirecionamento de entradas padrão

wc -l <file1 <file2

é equivalente a cat file1 file2 | wc -l(observe que isso não é o mesmo que wc -l file1 file2, mais tarde conta o número de linhas em cada arquivo separadamente).

Obviamente, você também pode usar MULTIOSpara redirecionar a saída não para arquivos, mas para outros processos, usando a substituição de processos, por exemplo:

echo abc > >(grep -o a) > >(tr b x) > >(sed 's/c/y/')
jimmij
fonte
3
Bom saber. MULTIOSé uma opção que está ativada por padrão (e pode ser desativada com unsetopt MULTIOS).
usar o seguinte comando
6

Para uma saída razoavelmente pequena produzida por um comando, podemos redirecionar a saída para um arquivo temporário e enviar esse arquivo temporário para comandos em loop. Isso pode ser útil quando a ordem dos comandos executados for importante.

O script a seguir, por exemplo, pode fazer isso:

#!/bin/sh

temp=$( mktemp )
cat /dev/stdin > "$temp"

for arg
do
    eval "$arg" < "$temp"
done
rm "$temp"

Teste executado no Ubuntu 16.04 com /bin/shcomo dashshell:

$ cat /etc/passwd | ./multiple_pipes.sh  'wc -l'  'grep "root"'                                                          
48
root:x:0:0:root:/root:/bin/bash
Sergiy Kolodyazhnyy
fonte
5

Capture o comando STDOUTpara uma variável e reutilize-o quantas vezes quiser:

commandoutput="$(command-to-run)"
echo "$commandoutput" | grep -i errors
echo "$commandoutput" | pbcopy

Se você precisar capturar STDERRtambém, use 2>&1no final do comando, assim:

commandoutput="$(command-to-run 2>&1)"
laebshade
fonte
3
Onde as variáveis ​​são armazenadas? Se você estivesse lidando com um arquivo grande ou algo desse tipo, isso não consumiria muita memória? As variáveis ​​são limitadas em tamanho?
Cwd
1
e se $ commandoutput for enorme ?, é melhor usar pipes e substituir processos.
Nikhil Mulley
4
Obviamente, essa solução é possível apenas quando você sabe que o tamanho da saída cabe facilmente na memória e você está bem com o buffer de toda a saída antes de executar os próximos comandos nela. As tubulações resolvem esses dois problemas, permitindo dados arbitrários de tamanho e transmitindo-os em tempo real para o receptor, à medida que são gerados.
trr
2
Esta é uma boa solução se você tiver uma saída pequena e você souber que a saída será texto e não binária. (variáveis do shell muitas vezes não são seguros binário)
Rucent88
1
Não consigo fazer isso funcionar com dados binários. Eu acho que é algo com eco tentando interpretar bytes nulos ou alguns outros dados não característicos.
Rolf
1

Isso pode ser útil: http://www.spinellis.gr/sw/dgsh/ (shell de gráfico direcionado) Parece uma substituição do bash que suporta uma sintaxe mais fácil dos comandos "multipipe".

sivann
fonte
0

Aqui está uma solução parcial rápida e suja, compatível com qualquer shell, inclusive busybox.

O problema mais restrito que ele resolve é: imprima o completo stdoutem um console e filtre-o em outro, sem arquivos temporários ou pipes nomeados.

  • Inicie outra sessão no mesmo host. Para descobrir seu nome TTY, digite tty. Vamos assumir /dev/pty/2.
  • Na primeira sessão, execute the_program | tee /dev/pty/2 | grep ImportantLog:

Você obtém um log completo e um filtrado.

Victor Sergienko
fonte