Como posso canalizar stderr, e não stdout?

982

Eu tenho um programa que grava informações no stdoute stderr, e preciso passar greppelo que está chegando ao stderr , apesar de desconsiderar o stdout .

Claro que posso fazê-lo em 2 etapas:

command > /dev/null 2> temp.file
grep 'something' temp.file

mas eu preferiria poder fazer isso sem arquivos temporários. Existem truques inteligentes de tubulação?

Peter Mortensen
fonte
Uma pergunta semelhante, mas stdout retenção: unix.stackexchange.com/questions/3514/...
joeytwiddle
Esta pergunta foi feita para o Bash, mas vale a pena mencionar este artigo relacionado para o shell Bourne / Almquist.
Stephen Niedzielski
10
Eu estava esperando algo parecido com isto: command 2| othercommand. O Bash é tão perfeito que o desenvolvimento terminou em 1982, por isso nunca veremos isso no bash, receio.
Rolf

Respostas:

1190

Primeiro redirecione o stderr para o stdout - o pipe; em seguida, redirecione o stdout para /dev/null(sem alterar para onde o stderr está indo):

command 2>&1 >/dev/null | grep 'something'

Para obter detalhes sobre o redirecionamento de E / S em toda a sua variedade, consulte o capítulo Redirecionamentos no manual de referência do Bash.

Observe que a sequência de redirecionamentos de E / S é interpretada da esquerda para a direita, mas os pipes são configurados antes que os redirecionamentos de E / S sejam interpretados. Descritores de arquivo como 1 e 2 são referências para abrir descrições de arquivo. A operação 2>&1faz com que o descritor de arquivo 2 aka stderr se refira à mesma descrição de arquivo aberto que o descritor de arquivo 1 aka stdout se refere atualmente (consulte dup2()e open()). A operação >/dev/nullaltera o descritor de arquivo 1 para que ele se refira a uma descrição de arquivo aberto /dev/null, mas isso não altera o fato de que o descritor de arquivo 2 se refere à descrição do arquivo aberto que o descritor de arquivo 1 estava apontando originalmente - a saber, o canal.

Jonathan Leffler
fonte
44
Eu apenas tropecei em / dev / stdout / dev / stderr / dev / stdin outro dia e fiquei curioso se essas são boas maneiras de fazer a mesma coisa? Eu sempre pensei que 2> & 1 estava um pouco ofuscado. Então algo como: command 2> /dev/stdout 1> /dev/null | grep 'something'
Mike Lyons
17
Você poderia usar /dev/stdoutet al, ou use /dev/fd/N. Eles serão marginalmente menos eficientes, a menos que o shell os trate como casos especiais; a notação numérica pura não envolve acessar arquivos por nome, mas usar os dispositivos significa uma pesquisa de nome de arquivo. Se você pode medir isso é discutível. Gosto da sucessão da notação numérica - mas a uso há tanto tempo (mais de um quarto de século; ai!) Que não estou qualificado para julgar seus méritos no mundo moderno.
Jonathan Leffler
23
@ Jonathan Leffler: Eu tomo um pequeno problema com sua explicação em texto simples 'Redirecionar stderr para stdout e depois stdout para / dev / null' - Como é necessário ler as cadeias de redirecionamento da direita para a esquerda (não da esquerda para a direita), também deve adaptar nossa explicação em texto simples para isso: 'Redirecione stdout para / dev / null e, em seguida, stderr para onde stdout costumava estar' .
Kurt Pfeifle
116
@KurtPfeifle: au contraire! É preciso ler as cadeias de redirecionamento da esquerda para a direita, pois é assim que o shell as processa. A primeira operação é o 2>&1, que significa 'stderr de conexão para o descritor de arquivo que stdout está atualmente indo'. A segunda operação é 'change stdout, para que vá para /dev/null', deixando o stderr indo para o stdout original, o pipe. O shell divide primeiro as coisas no símbolo do canal, portanto, o redirecionamento do canal ocorre antes dos redirecionamentos 2>&1ou >/dev/null, mas é tudo; as outras operações são da esquerda para a direita. (Da direita para a esquerda não iria funcionar.)
Jonathan Leffler
14
O que realmente me surpreende é que ele também funciona no Windows (depois de renomear /dev/nullpara o equivalente do Windows nul).
Michael Burr
364

Ou, para trocar a saída do erro padrão e da saída padrão, use:

command 3>&1 1>&2 2>&3

Isso cria um novo descritor de arquivo (3) e o atribui ao mesmo local que 1 (saída padrão), depois atribui o fd 1 (saída padrão) ao mesmo local que o fd 2 (erro padrão) e, finalmente, atribui o fd 2 (erro padrão) ) para o mesmo local que fd 3 (saída padrão).

O erro padrão agora está disponível como saída padrão e a saída padrão antiga é preservada no erro padrão. Isso pode ser um exagero, mas espero que dê mais detalhes sobre os descritores de arquivos Bash (há nove disponíveis para cada processo).

Kramish
fonte
100
Um ajuste final seria 3>&-para fechar o descritor de reposição que você criou a partir stdout
Jonathan Leffler
1
Podemos criar um descritor de arquivo que tenha stderre outro que tenha a combinação de stderre stdout? Em outras palavras, pode stderrir para dois arquivos diferentes ao mesmo tempo?
Stuart
O seguinte ainda imprime erros no stdout. o que estou perdendo? Você pode usar o seguinte comando: ls -l not_a_file 3> & 1 1> & 2 2> & 3> errors.txt
user48956
1
@ JonasDahlbæk: o ajuste é principalmente uma questão de arrumação. Em situações verdadeiramente misteriosas, pode fazer a diferença entre um processo de detecção e não detecção de EOF, mas isso requer circunstâncias muito peculiares.
Jonathan Leffler
1
Cuidado : isso pressupõe que o FD 3 ainda não esteja em uso, não o fecha e não desfaz a troca dos descritores de arquivo 1 e 2; portanto, você não pode passar isso para outro comando. Veja esta resposta para obter mais detalhes e soluções alternativas. Para uma sintaxe muito mais limpa para {ba, z} sh, consulte esta resposta .
Tom Hale
218

No Bash, você também pode redirecionar para um subshell usando a substituição de processo :

command > >(stdlog pipe)  2> >(stderr pipe)

Para o caso em questão:

command 2> >(grep 'something') >/dev/null
Rich Johnson
fonte
1
Funciona muito bem para saída para a tela. Você tem alguma idéia de por que o conteúdo não agrupado aparece novamente se eu redirecionar a saída grep para um arquivo? Depois que o command 2> >(grep 'something' > grep.log)grep.log contenha o mesmo resultado que o ungrepped.log decommand 2> ungrepped.log
Tim
9
Use 2> >(stderr pipe >&2). Caso contrário, a saída do "stderr pipe" passará pelo "stdlog pipe".
ceving 28/10/19
sim ! 2> >(...), funciona, eu tentei, 2>&1 > >(...)mas não o fez
datdinhquoc 4/18
Aqui está um pequeno exemplo que pode me ajudar na próxima vez que pesquisar como fazer isso. Considere o seguinte ... awk -f /new_lines.awk <in-content.txt > out-content.txt 2> >(tee new_lines.log 1>&2 ) Nesse caso, eu também queria ver o que estava saindo como erros no meu console. Mas STDOUT estava indo para o arquivo de saída. Portanto, dentro do sub-shell, você precisa redirecionar esse STDOUT de volta para STDERR dentro dos parênteses. Enquanto isso funciona, a saída STDOUT do teecomando é encerrada no final do out-content.txtarquivo. Isso parece inconsistente para mim.
será
@datdinhquoc Eu fiz isso de alguma forma, como2>&1 1> >(dest pipe)
Alireza Mohamadi
195

Combinando as melhores respostas, se você:

command 2> >(grep -v something 1>&2)

... todo stdout é preservado como stdout e todo stderr é preservado como stderr, mas você não verá nenhuma linha no stderr que contenha a string "something".

Isso tem a vantagem exclusiva de não reverter ou descartar stdout e stderr, nem esmagá-los juntos, nem usar arquivos temporários.

Pinko
fonte
Não é command 2> >(grep -v something)(sem 1>&2) o mesmo?
Francesc Rosas
11
Não, sem isso, o stderr filtrado acaba sendo roteado para stdout.
quer
1
Era disso que eu precisava - o arquivo tar output "sempre foi alterado conforme o lemos" para um diretório, portanto, basta filtrar essa linha, mas ver se há outros erros. Então tar cfz my.tar.gz mydirectory/ 2> >(grep -v 'changed as we read it' 1>&2)deve funcionar.
razzed
é errado dizer "começando com a string". Não há nada sobre a sintaxe apresentada do grep que faça com que ele exclua apenas linhas que começam com a sequência especificada. As linhas serão excluídas se elas contiverem a sequência especificada em qualquer lugar nelas.
9788 Mike Nakis
@MikeNakis thanks - fixed! (Isso foi o que sobrou do meu rascunho de resposta original, no qual fazia sentido ...)
Pinko
102

É muito mais fácil visualizar as coisas se você pensar sobre o que realmente está acontecendo com "redirecionamentos" e "pipes". Redirecionamentos e canais no bash fazem uma coisa: modifique para onde os descritores de arquivo de processo 0, 1 e 2 apontam para (consulte / proc / [pid] / fd / *).

Quando um tubo ou "|" Se o operador estiver presente na linha de comando, a primeira coisa a acontecer é que o bash cria um fifo e aponta o FD 1 do comando do lado esquerdo para este fifo e aponta o FD 0 do comando do lado direito para o mesmo fifo.

Em seguida, os operadores de redirecionamento de cada lado são avaliados da esquerda para a direita e as configurações atuais são usadas sempre que ocorre a duplicação do descritor. Isso é importante porque, desde que o tubo foi configurado primeiro, o FD1 (lado esquerdo) e o FD0 (lado direito) já foram alterados do que normalmente poderiam ter sido, e qualquer duplicação destes refletirá esse fato.

Portanto, quando você digita algo como o seguinte:

command 2>&1 >/dev/null | grep 'something'

Aqui está o que acontece, em ordem:

  1. um tubo (fifo) é criado. "comando FD1" está apontado para este canal. "grep FD0" também é apontado para este canal
  2. "comando FD2" é apontado para onde "comando FD1" aponta atualmente (o canal)
  3. "comando FD1" está apontado para / dev / null

Portanto, toda saída que esse "comando" grava em seu FD 2 (stderr) segue para o canal e é lida por "grep" no outro lado. Toda saída que "comando" grava em seu FD 1 (stdout) chega a / dev / null.

Em vez disso, você executa o seguinte:

command >/dev/null 2>&1 | grep 'something'

Aqui está o que acontece:

  1. um pipe é criado e "command FD 1" e "grep FD 0" são apontados para ele
  2. "comando FD 1" está apontado para / dev / null
  3. "comando FD 2" é apontado para onde o FD 1 aponta atualmente (/ dev / null)

Portanto, todos os stdout e stderr do "comando" vão para / dev / null. Nada vai para o canal e, assim, o "grep" será fechado sem exibir nada na tela.

Observe também que os redirecionamentos (descritores de arquivo) podem ser somente leitura (<), somente gravação (>) ou leitura e gravação (<>).

Uma nota final. Se um programa grava algo em FD1 ou FD2, depende inteiramente do programador. As boas práticas de programação determinam que as mensagens de erro devem ir para o FD 2 e a saída normal para o FD 1, mas muitas vezes você encontrará uma programação desleixada que mistura os dois ou ignora a convenção.

Michael Martinez
fonte
6
Resposta muito boa. Minha sugestão seria substituir o seu primeiro uso de "fifo" por "fifo (um pipe nomeado)". Estou usando o Linux há algum tempo, mas de alguma forma nunca consegui aprender que esse é outro termo para pipe nomeado. Isso teria me poupado de procurar, mas, novamente, eu não teria aprendido as outras coisas que vi quando descobri isso!
precisa
3
@ MarkEdington Observe que FIFO é apenas outro termo para pipe nomeado no contexto de pipes e IPC . Em um contexto mais geral, FIFO significa Primeiro a entrar, Primeiro a sair, que descreve a inserção e remoção de uma estrutura de dados da fila.
Loomchild
5
@Loomchild Claro. O ponto do meu comentário foi que, mesmo como desenvolvedor experiente, eu nunca tinha visto o FIFO usado como sinônimo de pipe nomeado. Em outras palavras, eu não sabia disso: en.wikipedia.org/wiki/FIFO_(computing_and_electronics)#Pipes - Esclarecendo que na resposta eu teria economizado tempo.
Mark Edington
39

Se você estiver usando o Bash, use:

command >/dev/null |& grep "something"

http://www.gnu.org/software/bash/manual/bashref.html#Pipelines

Ken Sharp
fonte
4
Não, |&é igual ao 2>&1que combina stdout e stderr. A pergunta solicitou explicitamente a saída sem stdout.
Profpatsch
3
„Se '| &' for usado, o erro padrão do comando1 é conectado à entrada padrão do comando2 através do tubo; é uma abreviação de 2> & 1 | ” Tomado literalmente do quarto parágrafo no seu link.
precisa saber é o seguinte
9
@Profpatsch: A resposta de Ken está correta, veja que ele redireciona o stdout para null antes de combinar stdout e stderr, então você entrará no pipe apenas o stderr, porque o stdout foi colocado anteriormente em / dev / null.
Luciano
3
Mas ainda achei sua resposta errada, >/dev/null |&expanda para >/dev/null 2>&1 | e significa que o stode stdout está vazio para canalizar porque ninguém (# 1 # 2 ambos vinculado ao / dev / null inode) está vinculado ao stode stdout (por exemplo ls -R /tmp/* >/dev/null 2>&1 | grep i, dará vazio, mas ls -R /tmp/* 2>&1 >/dev/null | grep ipermite # 2, que está vinculado ao inode stdout, será canalizado).
Fruit
3
Ken Sharp, eu testei, e ( echo out; echo err >&2 ) >/dev/null |& grep "."não dá saída (onde queremos "errar"). man bashdiz Se | & é usado ... é uma abreviação de 2> & 1 |. Esse redirecionamento implícito do erro padrão para a saída padrão é executado após qualquer redirecionamento especificado pelo comando. Então, primeiro redirecionamos o FD1 do comando para nulo, depois redirecionamos o FD2 do comando para onde o FD1 apontou, ou seja. nulo, então o FD0 do grep não recebe entrada. Consulte stackoverflow.com/a/18342079/69663 para obter uma explicação mais detalhada.
unhammer 22/09/16
11

Para aqueles que desejam redirecionar stdout e stderr permanentemente para arquivos, grep no stderr, mas mantenha o stdout para escrever mensagens em um tty:

# save tty-stdout to fd 3
exec 3>&1
# switch stdout and stderr, grep (-v) stderr for nasty messages and append to files
exec 2> >(grep -v "nasty_msg" >> std.err) >> std.out
# goes to the std.out
echo "my first message" >&1
# goes to the std.err
echo "a error message" >&2
# goes nowhere
echo "this nasty_msg won't appear anywhere" >&2
# goes to the tty
echo "a message on the terminal" >&3
JBD
fonte
6

Isso redirecionará command1 stderr para command2 stdin, deixando o command1 stdout como está.

exec 3>&1
command1 2>&1 >&3 3>&- | command2 3>&-
exec 3>&-

Retirado do LDP

O golfinho
fonte
2

Acabei de criar uma solução para enviar stdoutpara um comando e stderrpara outro, usando pipes nomeados.

Aqui vai.

mkfifo stdout-target
mkfifo stderr-target
cat < stdout-target | command-for-stdout &
cat < stderr-target | command-for-stderr &
main-command 1>stdout-target 2>stderr-target

Provavelmente, é uma boa idéia remover os pipes nomeados posteriormente.

Cinética Tripp
fonte
0

Você pode usar o shell rc .

Primeiro instale o pacote (tem menos de 1 MB).

Este um exemplo de como você iria descartar saída padrão e tubo de erro padrão 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, é possível especificar qual descritor de arquivo você deseja canalizar usando colchetes após o canal.

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
-3

Eu tento seguir, acho que funciona também,

command > /dev/null 2>&1 | grep 'something'
olho de peixe
fonte
Não funciona Apenas envia stderr para o terminal. Ignora o tubo.
Tripp Kinetics