it-swarm-pt.tech

Como grep fluxo de erro padrão (stderr)?

Estou usando o ffmpeg para obter as informações meta de um clipe de áudio. Mas eu sou incapaz de cumprimentá-lo.

    $ ffmpeg -i 01-Daemon.mp3  |grep -i Duration
    FFmpeg version SVN-r15261, Copyright (c) 2000-2008 Fabrice Bellard, et al.
      configuration: --prefix=/usr --bindir=/usr/bin 
      --datadir=/usr/share/ffmpeg --incdir=/usr/include/ffmpeg --libdir=/usr/lib
      --mandir=/usr/share/man --Arch=i386 --extra-cflags=-O2 
      ...

Eu verifiquei, essa saída ffmpeg é direcionada para stderr.

$ ffmpeg -i 01-Daemon.mp3 2> /dev/null

Então, acho que o grep não consegue ler o fluxo de erros para pegar as linhas correspondentes. Como podemos habilitar o grep para ler o fluxo de erros?

Usando o link nixCraft , redirecionei o fluxo de erro padrão para o fluxo de saída padrão e o grep funcionou.

$ ffmpeg -i 01-Daemon.mp3 2>&1 | grep -i Duration
  Duration: 01:15:12.33, start: 0.000000, bitrate: 64 kb/s

Mas e se não quisermos redirecionar o stderr para o stdout?

90
Andrew-Dufresne

Se você está usando bash por que não empregar tubos anônimos, em essência, abreviação do que phunehehe disse:

ffmpeg -i 01-Daemon.mp3 2> >(grep -i Duration)

66
Jé Queue

Nenhum dos invólucros habituais (mesmo o zsh) permite tubos diferentes de stdout para stdin. Mas todas as conchas no estilo Bourne suportam a reatribuição de descritor de arquivo (como em 1>&2). Portanto, você pode desviar temporariamente o stdout para o fd 3 e o stderr para o stdout e depois colocar o fd 3 novamente no stdout. Se stuff produz alguma saída no stdout e outra no stderr, e você deseja aplicar filter na saída de erro deixando a saída padrão intacta, você pode usar { stuff 2>&1 1>&3 | filter 1>&2; } 3>&1.

$ stuff () {
  echo standard output
  echo more output
  echo standard error 1>&2
  echo more error 1>&2
}
$ filter () {
  grep a
}
$ { stuff 2>&1 1>&3 | filter 1>&2; } 3>&1
standard output
more output
standard error
49

Isso é semelhante ao "truque de arquivo temporário" do phunehehe, mas usa um pipe nomeado, permitindo obter resultados um pouco mais próximos da saída, o que pode ser útil para comandos de longa execução:

$ mkfifo mypipe
$ command 2> mypipe | grep "pattern" mypipe

Nesta construção, o stderr será direcionado para o tubo chamado "mypipe". Como grep foi chamado com um argumento de arquivo, ele não procurará STDIN por sua entrada. Infelizmente, você ainda precisará limpar esse pipe nomeado quando terminar.

Se você estiver usando o Bash 4, há uma sintaxe de atalho para command1 2>&1 | command2, qual é command1 |& command2. No entanto, acredito que este é apenas um atalho de sintaxe, você ainda está redirecionando STDERR para STDOUT.

22
Steven D

As respostas de Gilles e Stefan Lasiewski são boas, mas dessa maneira é mais simples:

ffmpeg -i 01-Daemon.mp3 2>&1 >/dev/null | grep "pattern"

Estou assumindo que você não quer ffmpeg's stdout impresso.

Como funciona:

  • tubos primeiro
    • ffmpeg e grep são iniciados, com o stdout do ffmpeg indo para o stdin do grep
  • redirecionamentos a seguir, da esquerda para a direita
    • o stderr do ffmpeg está definido como o seu stdout (atualmente o pipe)
    • o stdout do ffmpeg está definido como/dev/null
11
Mikel

Veja abaixo o script usado nesses testes.

O Grep pode operar apenas em stdin, portanto, você deve converter o fluxo stderr em um formato que o Grep possa analisar.

Normalmente, stdout e stderr são impressos na tela:

$ ./stdout-stderr.sh
./stdout-stderr.sh: Printing to stdout
./stdout-stderr.sh: Printing to stderr

Para ocultar stdout, mas ainda assim imprimir stderr, faça o seguinte:

$ ./stdout-stderr.sh >/dev/null
./stdout-stderr.sh: Printing to stderr

Mas o grep não funcionará no stderr! Você esperaria que o seguinte comando suprimisse linhas que contenham 'err', mas não contém.

$ ./stdout-stderr.sh >/dev/null |grep --invert-match err
./stdout-stderr.sh: Printing to stderr

Aqui está a solução.

A seguinte sintaxe do Bash oculta a saída para stdout, mas ainda mostra o stderr. Primeiro canalizamos o stdout para/dev/null, depois convertemos o stderr para o stdout, porque os pipes do Unix operam apenas no stdout. Você ainda pode grep o texto.

$ ./stdout-stderr.sh 2>&1 >/dev/null | grep err
./stdout-stderr.sh: Printing to stderr

(Observe que o comando acima é diferente então ./command >/dev/null 2>&1, que é um comando muito comum).

Aqui está o script usado para o teste. Isso imprime uma linha para stdout e uma linha para stderr:

#!/bin/sh

# Print a message to stdout
echo "$0: Printing to stdout"
# Print a message to stderr
echo "$0: Printing to stderr" >&2

exit 0
8
Stefan Lasiewski

Você pode trocar os fluxos. Isso permitiria grep o fluxo de erro padrão original enquanto ainda obtinha a saída que originalmente foi para a saída padrão no terminal:

somecommand 3>&2 2>&1 1>&3- | grep 'pattern'

Isso funciona criando primeiro um novo descritor de arquivo (3) aberto para saída e configurando-o para o fluxo de erros padrão (3>&2). Em seguida, redirecionamos o erro padrão para a saída padrão (2>&1). Finalmente, a saída padrão é redirecionada para o erro padrão original e o novo descritor de arquivo é fechado (1>&3-).

No seu caso:

ffmpeg -i 01-Daemon.mp3 3>&2 2>&1 1>&3- | grep -i Duration

Testando:

$ ( echo "error" >&2; echo "output" ) 3>&2 2>&1 1>&3- | grep "error"
output
error

$ ( echo "error" >&2; echo "output" ) 3>&2 2>&1 1>&3- | grep -v "error"
output
3
Kusalananda

Quando você canaliza a saída de um comando para outro (usando |), você está redirecionando apenas a saída padrão. Então, isso deve explicar o porquê

ffmpeg -i 01-Daemon.mp3 | grep -i Duration

não gera o que você queria (mas funciona).

Se você não deseja redirecionar a saída de erro para a saída padrão, pode redirecionar a saída de erro para um arquivo e depois cumprimentá-lo mais tarde

ffmpeg -i 01-Daemon.mp3 2> /tmp/ffmpeg-error
grep -i Duration /tmp/ffmpeg-error
3
phunehehe

bash pode redirecionar stdout para o fluxo stdin por meio de canal regular - | Ele também pode redirecionar stdout e stderr para stdin por |&

1
0x00

Eu gosto de usar o rc Shell neste caso.

Primeiro instale o pacote (é menor que 1 MB).

Este é um exemplo de como você descartaria stdout e pipe stderr para grep em rc:

find /proc/ >[1] /dev/null |[2] grep task

Você pode fazer isso sem sair do Bash:

rc -c 'find /proc/ >[1] /dev/null |[2] grep task'

Como você deve ter notado, a sintaxe é direta, o que torna essa a minha solução preferida.

Você pode especificar qual descritor de arquivo você deseja canalizar entre colchetes, logo após o caractere de barra vertical.

Os descritores de arquivo padrão são numerados da seguinte forma:

  • 0: entrada padrão
  • 1: Saída padrão
  • 2: erro padrão
1
Rolf

Uma variação no exemplo do subprocesso do bash:

eco duas linhas para stderr e tee stderr para um arquivo, grep o tee e canalize de volta para stdout

(>&2 echo -e 'asdf\nfff\n') 2> >(tee some.load.errors | grep 'fff' >&1)

stdout:

fff

some.load.errors (por exemplo, stderr):

asdf
fff
0
jmunsch