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 :)


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

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

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.


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/')

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

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)"

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


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.

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.