Como uso o tee para redirecionar para grep

13

Não tenho muita experiência em usar tee, então espero que isso não seja muito básico.

Depois de ver uma das respostas a essa pergunta, me deparei com um estranho comportamento tee.

Para que eu produza a primeira linha e uma linha encontrada, eu posso usar isso:

ps aux | tee >(head -n1) | grep syslog
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
syslog     806  0.0  0.0  34600   824 ?        Sl   Sep07   0:00 rsyslogd -c4

No entanto, na primeira vez em que executei isso (em zsh), o resultado estava na ordem errada, os cabeçalhos das colunas estavam abaixo dos resultados do grep (no entanto, isso não aconteceu novamente), então tentei trocar os comandos:

ps aux | tee >(grep syslog) | head -n1
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND

Somente a primeira linha é impressa e nada mais! Posso usar tee para redirecionar para grep, ou estou fazendo isso da maneira errada?

Enquanto escrevia essa pergunta, o segundo comando realmente funcionou uma vez para mim, executei-o novamente cinco vezes e depois voltei ao resultado de uma linha. Este é apenas o meu sistema? (Estou executando o zsh no tmux).

Finalmente, por que com o primeiro comando "grep syslog" não é mostrado como resultado (existe apenas um resultado)?

Para controle aqui está o grep sem o tee

ps aux | grep syslog
syslog     806  0.0  0.0  34600   824 ?        Sl   Sep07   0:00 rsyslogd -c4
henry    2290  0.0  0.1  95220  3092 ?        Ssl  Sep07   3:12 /usr/bin/pulseaudio --start --log-target=syslog
henry   15924  0.0  0.0   3128   824 pts/4    S+   13:44   0:00 grep syslog

Atualização: parece que o cabeçote está causando o comando inteiro truncar (conforme indicado na resposta abaixo); o comando abaixo agora está retornando o seguinte:

ps aux | tee >(grep syslog) | head -n1
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
syslog     806
Rqomey
fonte
Não é uma resposta direta à sua pergunta, mas seria muito mais limpo fazer algo assim ps aux | sed -n -e '1p' -e '/syslog/p'.
Jw013 12/12/12
Eu nunca pensei em sed, acho que pode ser uma resposta adequada para a pergunta relacionada aqui, mas na verdade estou procurando informações sobre o comportamento inconsistente desses comandos!
RQomey # 12/12

Respostas:

19
$ ps aux | tee >(head -n1) | grep syslog
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND 
syslog     806  0.0  0.0  34600   824 ?        Sl   Sep07   0:00 rsyslogd -c4

Os comandos grepe headiniciam quase ao mesmo tempo e ambos recebem os mesmos dados de entrada como quiserem, mas geralmente à medida que os dados se tornam disponíveis. Existem algumas coisas que podem introduzir a saída 'não sincronizada' que inverte as linhas; por exemplo:

  1. Os dados multiplexados são teerealmente enviados para um processo antes do outro, dependendo principalmente da implementação de tee. Uma teeimplementação simples terá readalguma quantidade de entrada e, em seguida write, duas vezes: uma vez para stdout e outra para seu argumento. Isso significa que um desses destinos receberá os dados primeiro.

    No entanto, todos os tubos são armazenados em buffer. É provável que esses buffers tenham 1 linha cada, mas podem ser maiores, o que pode fazer com que um dos comandos de recebimento veja tudo o que é necessário para a saída (ou seja, a greplinha de ped) antes que o outro comando ( head) tenha recebido qualquer dado em todos.

  2. Não obstante o acima exposto, também é possível que um desses comandos receba os dados, mas não consiga fazer nada com eles a tempo, e o outro comando receba mais dados e os processe rapidamente.

    Por exemplo, mesmo se heade grepsão enviados os dados uma linha de cada vez, se headnão sabe como lidar com ele (ou fica adiada por agendamento kernel), greppode mostrar seus resultados antes headmesmo tem a chance de. Para demonstrar, tente adicionar um atraso: ps aux | tee >(sleep 1; head -n1) | grep syslogIsso quase certamente produzirá a grepsaída primeiro.

$ ps aux | tee >(grep syslog) | head -n1
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND

Acredito que muitas vezes você só obtém uma linha aqui, porque headrecebe a primeira linha de entrada e depois fecha seu stdin e sai. Quando teevê que seu stdout foi fechado, ele fecha seu próprio stdin (saída de ps) e sai. Isso pode depender da implementação.

Efetivamente, os únicos dados psa serem enviados são a primeira linha (definitivamente, porque headestá controlando isso), e talvez algumas outras linhas antes heade teefechem seus descritores stdin.

A inconsistência em saber se a segunda linha aparece é introduzida por tempo: headfecha stdin, mas psainda está enviando dados. Esses dois eventos não estão bem sincronizados, portanto a linha que contém syslogainda tem chance de chegar ao teeargumento de (o grepcomando). Isso é semelhante às explicações acima.

Você pode evitar esse problema completamente usando comandos que aguardam todas as entradas antes de fechar stdin / exit. Por exemplo, use em awkvez de head, que lerá e processará todas as suas linhas (mesmo que não causem saída):

ps aux | tee >(grep syslog) | awk 'NR == 1'

Mas observe que as linhas ainda podem aparecer fora de ordem, como acima, o que pode ser demonstrado por:

ps aux | tee >(grep syslog) | (sleep 1; awk 'NR == 1')

Espero que não tenham sido muitos detalhes, mas há muitas coisas simultâneas interagindo entre si. Processos separados são executados simultaneamente sem nenhuma sincronização, portanto, suas ações em qualquer execução específica podem variar; às vezes, ajuda a aprofundar os processos subjacentes para explicar o porquê.

mrb
fonte
1
Excelente resposta! Na verdade, perguntei porque estou interessado nos processos subjacentes. Quando as coisas são inconstantes, acho interessante. ps aux | tee >(grep syslog) | head -n1Haveria uma maneira melhor de executar que pararia de headfechar o stdout. Uau, este comando já começou a dar saída agora, mas como aconteceria em linha com a sua resposta, parece ser truncadoUSER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND syslog 806
Rqomey
1
Você pode usar algo que não fecha stdin em vez de head. Atualizei a resposta com este exemplo:ps aux | tee >(grep syslog) | awk 'NR == 1'
mrb 12/12/12
1
@KrzysztofAdamski, quando você usa >(cmd), o shell cria um pipe nomeado e passa isso como argumento para o comando ( tee). Então teeestá escrevendo para stdout (canalizado para awk) e também para esse argumento. É o mesmo que mkfifo a_fifo ; grep ... a_fifoem um shell e ps | tee a_fifo | awk ...em outro.
mrb 12/09/12
1
@KrzysztofAdamski gnu.org/software/bash/manual/html_node/… - Experimente echo >(exit 0), o que ecoará o argumento real transmitido pelo shell (no meu caso, ele se torna /dev/fd/63). Isso deve funcionar da mesma maneira no bash e no zsh.
Mrb 13/09/12
1
@ MRB: é uma característica muito interessante que eu não conhecia antes, obrigado. Está funcionando de alguma maneira estranha no bash, no entanto, consulte pastebin.com/xFgRcJdF . Infelizmente, não tenho tempo para investigar isso agora, mas farei isso amanhã.
Krzysztof Adamski
2

grep syslognem sempre é mostrado, pois depende do tempo. Ao usar o pipeline de shell, você está executando comandos quase simultaneamente. Mas a principal coisa aqui é a palavra "quase". Se psconcluir a verificação de todos os processos antes do grep ser lançado, ele não estará na lista. Você pode obter resultados aleatórios dependendo da carga do sistema etc.

O mesmo acontece com o seu tee. É executado em segundo plano no subshell e pode ser disparado antes ou depois do grep. É por isso que a ordem de saída é inconsistente.

Quanto à questão do tee, seu comportamento é bastante estranho. Isso ocorre porque não é usado da maneira normal. Ele é executado sem argumentos, o que significa que ele deve apenas copiar dados do stdin para o stdout. Mas o stdout é redirecionado para subshell running head (no primeiro caso) ou grep (segundo caso). Mas também é canalizado para o próximo comando. Eu acho que o que acontece neste caso é realmente dependente da implementação. Por exemplo, no meu bash 4.2.28, nada é escrito para subshell stdin. No zsh, ele é confiável da maneira que você deseja (imprimindo a primeira linha do ps e as linhas pesquisadas), cada vez que tento,

Krzysztof Adamski
fonte
Isso explica uma coisa de qualquer maneira, estou surpreso que os atrasos do tee funcionem de maneira notável!
RQomey # 12/12
0

Um pouco hackish, mas aqui está a minha solução, na forma de uma psgrep()função shell que eu uso:

Redirecione a pslinha do cabeçalho para STDERR, grepem seguida STDOUT, remova, mas primeiro remova o greppróprio comando, para evitar a linha de "ruído" decorrente de grepsi mesma:

psgrep() { ps aux | tee >(head -1>&2) | grep -v " grep $@" | grep "$@" -i --color=auto; }
fnl
fonte