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

69

Estou usando o ffmpeg para obter as meta-informações de um clipe de áudio. Mas eu não consigo entender.

    $ 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, esta saída do ffmpeg é direcionada para stderr.

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

Portanto, acho que o grep não consegue ler o fluxo de erros para capturar 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, em seguida, grep trabalhado.

$ 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?

    
por Andrew-Dufresne 26.10.2010 / 03:42

9 respostas

46

Nenhum dos shells usuais (mesmo zsh) permite que os pipes não sejam stdout stdin. Mas todas as shells no estilo Bourne suportam a reatribuição de descritores de arquivos (como em 1>&2 ). Assim, você pode desviar temporariamente o stdout para o fd 3 e o stderr para o stdout, e depois colocar o fd 3 de volta no stdout. Se stuff produzir alguma saída no stdout e alguma saída no stderr e você desejar aplicar filter na saída de erro deixando a saída padrão intacta, será possível 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
    
por 26.10.2010 / 21:15
42

Se você estiver usando bash , por que não usar canais anônimos, em resumo, o que phunehehe disse:

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

    
por 31.10.2010 / 04:35
19

Isso é semelhante ao "truque de arquivo temporário" de phunehehe, mas usa um canal nomeado, permitindo que você obtenha resultados um pouco mais próximos de quando são produzidos, o que pode ser útil para comandos de longa execução:

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

Nesta construção, stderr será direcionado para o pipe chamado "mypipe". Como grep foi chamado com um argumento de arquivo, ele não irá procurar STDIN por sua entrada. Infelizmente, você ainda terá que limpar o pipe nomeado assim que estiver pronto.

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

    
por 26.10.2010 / 06:25
7

Veja abaixo o script usado nesses testes.

O grep só pode operar 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 o stdout, mas ainda imprima o stderr faça isso:

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

Mas o grep não funciona no stderr! Você esperaria que o comando a seguir suprimisse as linhas que contêm 'err', mas isso não acontece.

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

Aqui está a solução.

A sintaxe Bash a seguir irá esconder a saída para stdout, mas ainda mostrará stderr. Primeiro nós direcionamos o stdout para / dev / null, então convertemos stderr para stdout, porque os pipes Unix irão operar somente no stdout. Você ainda pode usar o texto.

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

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

Aqui está o script usado para testes. 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
    
por 26.10.2010 / 20:18
6

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

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

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

Como funciona:

  • canos primeiro
    • O ffmpeg e o 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á configurado para qualquer stdout (atualmente o pipe)
    • O stdout do ffmpeg está definido como / dev / null
por 08.02.2011 / 02:42
3

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

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

não produz o que você queria (funciona, no entanto).

Se você não quiser redirecionar a saída de erro para a saída padrão, você pode redirecionar a saída de erro para um arquivo e, em seguida, aplicá-lo posteriormente

ffmpeg -i 01-Daemon.mp3 2> /tmp/ffmpeg-error
grep -i Duration /tmp/ffmpeg-error
    
por 26.10.2010 / 05:55
1

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

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

Isso funciona primeiro criando um novo descritor de arquivo (3) aberto para saída e configurando-o para o fluxo de erro 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

Teste:

$ ( 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
    
por 29.01.2017 / 19:21
0

Uma variação no exemplo do subprocesso bash:

echo duas linhas para stderr e tee stderr para um arquivo, grep o tee e o pipe 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
    
por 02.11.2016 / 22:08
-1

experimente este comando

  • crie um arquivo com nome aleatório
  • envia saída para o arquivo
  • envia conteúdo do arquivo para pipe
  • grep

ceva - > apenas um nome de variável (english = something)

ceva=$RANDOM$RANDOM$RANDOM; ffmpeg -i qwerty_112_0_0_record.flv 2>$ceva; cat $ceva | grep Duration; rm $ceva;
    
por 18.07.2012 / 08:44