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

76

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

Andrew-Dufresne
fonte
1
Acredito que grepsó possa operar no stdout (embora não seja possível encontrar a fonte canônica para fazer o backup), o que significa que qualquer fluxo precisa ser convertido para o stdout primeiro.
Stefan Lasiewski 26/10/10
9
@ Stefan: grepsó pode operar em stdin. É o pipe criado pelo shell que conecta o stdin do grep ao stdout do outro comando. E o shell pode conectar apenas um stdout a um stdin.
Gilles
Opa, você está certo. Eu acho que é isso que eu realmente queria dizer, simplesmente não pensei nisso. Obrigado @Giles.
Stefan Lasiewski
Deseja que ele ainda imprima stdout?
Mikel

Respostas:

52

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

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

Jé Queue
fonte
3
+1 Bom! Apenas Bash, mas muito mais limpo que as alternativas.
Mikel
1
+1 Eu usei isso para verificar a cpsaídacp -r dir* to 2> >(grep -v "svn")
Betlista
5
Se você quiser a saída redirecionada filtrada no stderr novamente, adicione a >&2, por exemplo,command 2> >(grep something >&2)
tlo
2
Por favor, explique como isso funciona. 2>redireciona stderr para arquivo, eu entendo isso. Isso lê do arquivo >(grep -i Duration). Mas o arquivo nunca é armazenado? Como é chamada essa técnica para que eu possa ler mais sobre ela?
Marko Avlijaš
1
É "açúcar sintático" criar um cachimbo (não lima) e, quando completo, remover esse cachimbo. Eles são efetivamente anônimos porque não recebem um nome no sistema de arquivos. Bash chama essa substituição de processo.
Jé Queue
48

Nenhuma das conchas usuais (mesmo o zsh) permite outros canais além de stdout para stdin. Mas todas as conchas no estilo Bourne suportam a reatribuição de descritor de arquivo (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 novamente no stdout. Se stuffproduz alguma saída no stdout e outra no stderr, e você deseja aplicar filterna saída de erro deixando a saída padrão intacta, você pode usá-lo { 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
Gilles
fonte
Essa abordagem funciona. Infelizmente, no meu caso, se um valor de retorno diferente de zero for retornado, ele será perdido - o valor retornado será 0 para mim. Isso nem sempre pode acontecer, mas acontece no caso em que estou olhando. Existe alguma maneira de salvá-lo?
Faheem Mitha
2
@FaheemMitha Não sei o que você está fazendo, mas talvez pipestatusajudaria
Gilles
1
@FaheemMitha, também set -o pipefailpode ser útil aqui, dependendo do que você deseja fazer com o status do erro. (Por exemplo, se tiver set -eligado a falhar em algum erro, você provavelmente vai querer set -o pipefailtambém.)
Wildcard
@Wildcard Sim, ativei set -ea falha em qualquer erro.
Faheem Mitha
A rccarcaça permite a tubulação stderr. Veja minha resposta abaixo.
Rolf
20

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 grepfoi 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, que é command1 |& command2. No entanto, acredito que este é apenas um atalho de sintaxe, você ainda está redirecionando STDERR para STDOUT.

Steven D
fonte
Veja também: stackoverflow.com/questions/2871233/…
Stefan Lasiewski
1
Essa |&sintaxe é perfeita para limpar os traços de pilha inchados do Ruby stderr. Finalmente posso cumprimentar aqueles sem muito trabalho.
pgr
8

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"

Suponho que você não queira ffmpeg'simprimir stdout.

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 seja o seu stdout (atualmente o pipe)
    • O stdout do ffmpeg está definido como / dev / null
Mikel
fonte
Esta descrição é confusa para mim. Os dois últimos marcadores me fazem pensar em "redirecionar stderr para stdout" e depois "redirecionar stdout (com stderr, agora) para / dev / null". No entanto, não é isso que isso realmente está fazendo. Essas declarações parecem invertidas.
Steve Steve
1
@ Steve Não há "with stderr" na segunda bala. Você já viu unix.stackexchange.com/questions/37660/order-of-redirections ?
Mikel
Não, quero dizer que é a minha interpretação de como você a descreveu em inglês. O link que você forneceu é muito útil.
Steve Steve
Por que foi necessário redirecionar para / dev / null?
Kanwaljeet Singh 30/09
7

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 sua tela:

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

Para ocultar o 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 as linhas que contêm '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

(Note-se que o comando acima é diferente , em seguida ./command >/dev/null 2>&1, o 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
Stefan Lasiewski
fonte
Se você alternar os redirecionamentos, não precisará de todos os aparelhos. Apenas faça ./stdout-stderr.sh 2>&1 >/dev/null | grep err.
Mikel
3

Ao canalizar 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 cumpri-lo

ffmpeg -i 01-Daemon.mp3 2> /tmp/ffmpeg-error
grep -i Duration /tmp/ffmpeg-error
phunehehe
fonte
Obrigado. it does work, though, você quer dizer que está funcionando na sua máquina? Em segundo lugar, como você apontou usando o pipe, só podemos redirecionar o stdout. Estou interessado em algum recurso de comando ou bash que me permitirá redirecionar o stderr. (mas não o truque arquivo temporário)
Andrew-Dufresne
@ Andrew, quero dizer, o comando funciona da maneira que foi projetado para funcionar. Ele simplesmente não funciona do jeito que você quer que ele :)
phunehehe
Não conheço nenhuma maneira que possa redirecionar a saída de erro de um comando para a entrada padrão de outro. Seria interessante se alguém pudesse apontar isso.
Phreinhe
2

Você pode trocar os fluxos. Isso permitiria grepo fluxo de erros padrão original enquanto ainda obtivemos 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 criando primeiro 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

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
Kusalananda
fonte
1

Eu gosto de usar o rcshell neste caso.

Primeiro instale o pacote (é inferior a 1 MB).

Este é um exemplo de como você descartaria stdoute canalizaria stderrpara grep 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
Rolf
fonte
0

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
jmunsch
fonte
-1

tente este comando

  • criar arquivo com nome aleatório
  • enviar saída para o arquivo
  • envia o conteúdo do arquivo para o canal
  • 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;
Rodislav Moldovan
fonte
Eu usaria /tmp/$ceva, melhor do que desarrumar o diretório atual com arquivos temporários - e você está assumindo que o diretório atual é gravável.
Rolf