Comment puis-je diriger stderr, et non stdout?

982

J'ai un programme qui écrit des informations dans stdoutet stderr, et je dois passer au greptravers de ce qui arrive à stderr , sans tenir compte de stdout .

Je peux bien sûr le faire en 2 étapes:

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

mais je préférerais pouvoir le faire sans fichiers temporaires. Existe-t-il des astuces de tuyauterie intelligentes?

Peter Mortensen
la source
Une question similaire, mais en conservant stdout: unix.stackexchange.com/questions/3514/…
joeytwiddle
Cette question s'adressait à Bash, mais il convient de mentionner cet article connexe pour le shell Bourne / Almquist.
Stephen Niedzielski
10
Je me attendais quelque chose comme ceci: command 2| othercommand. Bash est tellement parfait que le développement a pris fin en 1982, donc nous ne verrons jamais ça en bash, j'ai bien peur.
Rolf

Réponses:

1190

Redirigez d'abord stderr vers stdout - le tuyau; puis redirigez stdout vers /dev/null(sans changer où va stderr):

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

Pour les détails de la redirection d'E / S dans toute sa variété, voir le chapitre sur les redirections dans le manuel de référence de Bash.

Notez que la séquence de redirections d'E / S est interprétée de gauche à droite, mais les canaux sont configurés avant l'interprétation des redirections d'E / S. Les descripteurs de fichiers tels que 1 et 2 sont des références à des descriptions de fichiers ouverts. L'opération 2>&1fait que le descripteur de fichier 2 aka stderr fait référence à la même description de fichier ouverte que le descripteur de fichier 1 aka stdout fait actuellement référence (voir dup2()et open()). L'opération >/dev/nullmodifie ensuite le descripteur de fichier 1 pour qu'il fasse référence à une description de fichier ouvert pour /dev/null, mais cela ne change pas le fait que le descripteur de fichier 2 se réfère à la description de fichier ouvert vers laquelle le descripteur de fichier 1 pointait à l'origine - à savoir, le canal.

Jonathan Leffler
la source
44
je suis juste tombé sur / dev / stdout / dev / stderr / dev / stdin l'autre jour, et j'étais curieux de savoir si ce sont de bonnes façons de faire la même chose? J'ai toujours pensé que 2> & 1 était un peu obscurci. Donc quelque chose comme: command 2> /dev/stdout 1> /dev/null | grep 'something'
Mike Lyons
17
Vous pouvez utiliser /dev/stdoutet al, ou utiliser /dev/fd/N. Ils seront légèrement moins efficaces à moins que la coque ne les traite comme des cas spéciaux; la notation numérique pure n'implique pas l'accès aux fichiers par nom, mais l'utilisation des périphériques signifie une recherche de nom de fichier. Que vous puissiez mesurer cela est discutable. J'aime la brièveté de la notation numérique - mais je l'utilise depuis si longtemps (plus d'un quart de siècle; aïe!) Que je ne suis pas qualifié pour juger de ses mérites dans le monde moderne.
Jonathan Leffler
23
@Jonathan Leffler: Je prends un petit problème avec votre explication en clair "Rediriger stderr vers stdout puis stdout vers / dev / null" - Puisqu'il faut lire les chaînes de redirection de droite à gauche (pas de gauche à droite), nous devrait également adapter notre explication en texte clair à ceci: «Rediriger stdout vers / dev / null, puis stderr à l'endroit où se trouvait stdout» .
Kurt Pfeifle
116
@KurtPfeifle: au contraire! Il faut lire les chaînes de redirection de gauche à droite car c'est ainsi que le shell les traite. La première opération est la 2>&1, ce qui signifie «connecter stderr au descripteur de fichier vers lequel stdout va actuellement ». La deuxième opération consiste à «changer la /dev/nullsortie standard pour qu'elle passe à », en laissant stderr se diriger vers la sortie standard, le tuyau. Le shell divise tout d'abord le symbole du tuyau, donc la redirection du tuyau se produit avant les redirections 2>&1ou >/dev/null, mais c'est tout; les autres opérations sont de gauche à droite. (De droite à gauche ne fonctionnerait pas.)
Jonathan Leffler
14
La chose qui me surprend vraiment à ce sujet est que cela fonctionne aussi sur Windows (après avoir renommé /dev/nulll'équivalent de Windows nul).
Michael Burr
364

Ou pour permuter la sortie de l'erreur standard et de la sortie standard, utilisez:

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

Cela crée un nouveau descripteur de fichier (3) et l'affecte au même endroit que 1 (sortie standard), puis attribue fd 1 (sortie standard) au même endroit que fd 2 (erreur standard) et affecte finalement fd 2 (erreur standard ) au même endroit que fd 3 (sortie standard).

L'erreur standard est désormais disponible en sortie standard et l'ancienne sortie standard est conservée dans l'erreur standard. Cela peut être exagéré, mais cela donne, espérons-le, plus de détails sur les descripteurs de fichiers Bash (neuf sont disponibles pour chaque processus).

Kramish
la source
100
Un dernier ajustement serait 3>&-de fermer le descripteur de rechange que vous avez créé à partir de stdout
Jonathan Leffler
1
Pouvons-nous créer un descripteur de fichier qui a stderret un autre qui a la combinaison de stderret stdout? En d'autres termes, peut stderr- on accéder à deux fichiers différents à la fois?
Stuart
Ce qui suit imprime toujours des erreurs sur stdout. Qu'est-ce que je rate? ls -l not_a_file 3> & 1 1> & 2 2> & 3> errors.txt
user48956
1
@ JonasDahlbæk: le tweak est avant tout une question de propreté. Dans des situations vraiment obscures, cela peut faire la différence entre un processus détectant et ne détectant pas EOF, mais cela nécessite des circonstances très particulières.
Jonathan Leffler
1
Attention : cela suppose que FD 3 n'est pas déjà utilisé, ne le ferme pas et n'annule pas l'échange des descripteurs de fichiers 1 et 2, vous ne pouvez donc pas continuer à rediriger cela vers une autre commande. Voir cette réponse pour plus de détails et de solution . Pour une syntaxe beaucoup plus nette pour {ba, z} sh, consultez cette réponse .
Tom Hale
218

Dans Bash, vous pouvez également rediriger vers un sous-shell en utilisant la substitution de processus :

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

Pour le cas en question:

command 2> >(grep 'something') >/dev/null
Rich Johnson
la source
1
Fonctionne très bien pour la sortie à l'écran. Avez-vous une idée de la raison pour laquelle le contenu non intégré réapparaît si je redirige la sortie grep dans un fichier? Après que command 2> >(grep 'something' > grep.log)grep.log contienne la même sortie que ungrepped.log decommand 2> ungrepped.log
Tim
9
Utilisez 2> >(stderr pipe >&2). Sinon, la sortie du "stderr pipe" passera par le "stdlog pipe".
ceving
ouais ! 2> >(...), 2>&1 > >(...)ça marche, j'ai essayé mais ça n'a pas marché
datdinhquoc
Voici un petit exemple qui pourrait m'aider la prochaine fois que je chercherai comment faire cela. Considérez ce qui suit ... awk -f /new_lines.awk <in-content.txt > out-content.txt 2> >(tee new_lines.log 1>&2 ) Dans ce cas, je voulais également voir ce qui sortait comme des erreurs sur ma console. Mais STDOUT allait vers le fichier de sortie. Donc, à l'intérieur du sous-shell, vous devez rediriger ce STDOUT vers STDERR à l'intérieur des parenthèses. Pendant que cela fonctionne, la sortie STDOUT de la teecommande se termine à la fin du out-content.txtfichier. Cela me semble incohérent.
le
@datdinhquoc Je l'ai fait en quelque sorte comme2>&1 1> >(dest pipe)
Alireza Mohamadi
195

En combinant la meilleure de ces réponses, si vous le faites:

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

... alors tout stdout est préservé comme stdout et tout stderr est préservé comme stderr, mais vous ne verrez aucune ligne dans stderr contenant la chaîne "quelque chose".

Cela a l'avantage unique de ne pas inverser ou supprimer stdout et stderr, ni de les mélanger ensemble, ni d'utiliser de fichiers temporaires.

Pinko
la source
N'est-ce pas command 2> >(grep -v something)(sans 1>&2) la même chose?
Francesc Rosas
11
Non, sans cela, le stderr filtré finit par être routé vers stdout.
Pinko
1
C'est ce dont j'avais besoin - les sorties tar "le fichier a changé au fur et à mesure que nous le lisons" pour un répertoire toujours, donc je veux juste filtrer cette ligne mais voir si d'autres erreurs se produisent. Cela tar cfz my.tar.gz mydirectory/ 2> >(grep -v 'changed as we read it' 1>&2)devrait donc fonctionner.
ébloui
il est faux de dire "en commençant par la chaîne". Il n'y a rien dans la syntaxe présentée de grep qui ne fera exclure que les lignes commençant par la chaîne donnée. Les lignes seront exclues si elles contiennent la chaîne donnée n'importe où en elles.
Mike Nakis
@MikeNakis thanks - fixed! (C'était le reste de mon brouillon de réponse original, dans lequel cela avait du sens ...)
Pinko
102

Il est beaucoup plus facile de visualiser les choses si vous pensez à ce qui se passe réellement avec les "redirections" et les "tuyaux". Les redirections et les canaux dans bash font une chose: modifier où les descripteurs de fichiers de processus 0, 1 et 2 pointent (voir / proc / [pid] / fd / *).

Quand une pipe ou "|" est présent sur la ligne de commande, la première chose à faire est que bash crée un fifo et pointe le FD 1 de la commande de gauche sur ce fifo, et pointe le FD 0 de la commande de droite sur le même fifo.

Ensuite, les opérateurs de redirection pour chaque côté sont évalués de gauche à droite et les paramètres actuels sont utilisés chaque fois que la duplication du descripteur se produit. Ceci est important car depuis que le tuyau a été installé en premier, le FD1 (côté gauche) et FD0 (côté droit) sont déjà modifiés par rapport à ce qu'ils auraient pu être normalement, et toute duplication de ceux-ci reflétera ce fait.

Par conséquent, lorsque vous tapez quelque chose comme ceci:

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

Voici ce qui se passe, dans l'ordre:

  1. un tuyau (fifo) est créé. "commande FD1" pointe vers ce tuyau. "grep FD0" est également pointé vers ce tuyau
  2. "commande FD2" pointe vers l'endroit où "commande FD1" pointe actuellement (le tuyau)
  3. "commande FD1" pointe vers / dev / null

Ainsi, toutes les sorties que "command" écrit sur son FD 2 (stderr) se dirigent vers le tuyau et sont lues par "grep" de l'autre côté. Toutes les sorties que la "commande" écrit dans son FD 1 (stdout) se dirigent vers / dev / null.

Si à la place, vous exécutez ce qui suit:

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

Voici ce qui se passe:

  1. un tuyau est créé et "commande FD 1" et "grep FD 0" sont pointés vers lui
  2. "commande FD 1" pointe vers / dev / null
  3. "commande FD 2" pointe vers où FD 1 pointe actuellement (/ dev / null)

Ainsi, tous les stdout et stderr de la "commande" vont dans / dev / null. Rien ne va dans le tuyau, et donc "grep" se fermera sans afficher quoi que ce soit à l'écran.

Notez également que les redirections (descripteurs de fichiers) peuvent être en lecture seule (<), en écriture seule (>) ou en lecture-écriture (<>).

Une note finale. Qu'un programme écrive quelque chose sur FD1 ou FD2, cela dépend entièrement du programmeur. Les bonnes pratiques de programmation dictent que les messages d'erreur doivent aller à FD 2 et la sortie normale à FD 1, mais vous trouverez souvent une programmation bâclée qui mélange les deux ou ignore la convention.

Michael Martinez
la source
6
Réponse vraiment sympa. Ma seule suggestion serait de remplacer votre première utilisation de "fifo" par "fifo (un tube nommé)". J'utilise Linux depuis un certain temps, mais je n'ai jamais réussi à apprendre qu'il s'agit d'un autre terme pour le canal nommé. Cela m'aurait évité de le chercher, mais là encore je n'aurais pas appris les autres trucs que j'ai vus quand j'ai découvert ça!
Mark Edington
3
@MarkEdington Veuillez noter que FIFO n'est qu'un autre terme pour le canal nommé dans le contexte des canaux et IPC . Dans un contexte plus général, FIFO signifie Premier entré, premier sorti, qui décrit l'insertion et la suppression d'une structure de données de file d'attente.
Loomchild
5
@Loomchild Bien sûr. Le point de mon commentaire était que même en tant que développeur chevronné, je n'avais jamais vu FIFO utilisé comme synonyme de pipe nommée. En d'autres termes, je ne le savais pas: en.wikipedia.org/wiki/FIFO_(computing_and_electronics)#Pipes - Clarifier cela dans la réponse m'aurait fait gagner du temps.
Mark Edington
39

Si vous utilisez Bash, utilisez:

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

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

Ken Sharp
la source
4
Non, |&est égal à 2>&1qui combine stdout et stderr. La question demandait explicitement une sortie sans sortie standard.
Profpatsch
3
„Si '| &' est utilisé, l'erreur standard de command1 est connectée à l'entrée standard de command2 via le tuyau; c'est un raccourci pour 2> & 1 | ” Extrait mot pour mot du quatrième paragraphe de votre lien.
Profpatsch
9
@Profpatsch: La réponse de Ken est correcte, regardez qu'il redirige stdout vers null avant de combiner stdout et stderr, donc vous n'entrerez dans pipe que stderr, car stdout a été précédemment déposé dans / dev / null.
Luciano
3
Mais j'ai toujours trouvé que votre réponse était fausse, >/dev/null |&développez >/dev/null 2>&1 | et signifie que l'inode stdout est vide dans le tuyau car personne (# 1 # 2 à la fois lié à / dev / null inode) n'est lié à l'inode stdout (par exemple ls -R /tmp/* >/dev/null 2>&1 | grep i, donnera vide, mais ls -R /tmp/* 2>&1 >/dev/null | grep ilaissera # 2 qui est lié à l'inode standard sortira).
Fruit
3
Ken Sharp, j'ai testé, et ( echo out; echo err >&2 ) >/dev/null |& grep "."ne donne aucune sortie (là où on veut "errer"). man bashindique que si | & est utilisé… est un raccourci pour 2> & 1 |. Cette redirection implicite de l'erreur standard vers la sortie standard est effectuée après toutes les redirections spécifiées par la commande. Donc, nous redirigeons d'abord le FD1 de la commande vers null, puis nous redirigeons le FD2 de la commande vers FD1, c'est-à-dire. null, donc le FD0 de grep ne reçoit aucune entrée. Voir stackoverflow.com/a/18342079/69663 pour une explication plus approfondie.
unhammer
11

Pour ceux qui veulent rediriger stdout et stderr en permanence vers des fichiers, grep sur stderr, mais gardez la stdout pour écrire des messages sur un 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
la source
6

Cela redirigera command1 stderr vers command2 stdin, tout en laissant command1 stdout tel quel.

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

Extrait du LDP

le dauphin
la source
2

Je viens de trouver une solution pour envoyer stdoutà une commande et stderrà une autre, en utilisant des canaux nommés.

Voici.

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

C'est probablement une bonne idée de supprimer les tuyaux nommés par la suite.

Tripp Kinetics
la source
0

Vous pouvez utiliser le shell rc .

Installez d'abord le package (c'est moins de 1 Mo).

Voici un exemple de la façon dont vous pouvez ignorer la sortie standard et l'erreur standard de canal vers grep dans rc:

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

Vous pouvez le faire sans quitter Bash:

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

Comme vous l'avez peut-être remarqué, vous pouvez spécifier le descripteur de fichier que vous souhaitez canaliser en utilisant des crochets après le tube.

Les descripteurs de fichiers standard sont numérotés comme suit:

  • 0: entrée standard
  • 1: sortie standard
  • 2: erreur standard
Rolf
la source
-3

J'essaie de suivre, je trouve que ça marche aussi,

command > /dev/null 2>&1 | grep 'something'
lasteye
la source
Ça ne marche pas. Il envoie simplement stderr au terminal. Ignore le tuyau.
Tripp Kinetics