Comment grep stream standard error (stderr)?

76

J'utilise ffmpeg pour obtenir les méta-informations d'un clip audio. Mais je suis incapable de le savoir.

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

J'ai vérifié, cette sortie de ffmpeg est dirigée vers stderr.

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

Donc, je pense que grep est incapable de lire le flux d'erreur pour intercepter les lignes correspondantes. Comment pouvons-nous activer grep pour lire le flux d'erreur?

En utilisant le lien nixCraft , j'ai redirigé le flux d'erreur standard vers le flux de sortie standard, puis grep a fonctionné.

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

Mais que se passe-t-il si nous ne voulons pas rediriger stderr vers stdout?

Andrew-Dufresne
la source
1
Je crois que grepcela ne peut fonctionner que sur stdout (bien que je ne puisse pas trouver la source canonique pour la sauvegarder), ce qui signifie que tout flux doit d'abord être converti en stdout.
Stefan Lasiewski
9
@Stefan: grepne peut fonctionner que sur stdin. C'est le canal créé par le shell qui relie stdin de grep à la sortie standard de l'autre commande. Et le shell ne peut connecter qu'un stdout à un stdin.
Gilles, arrête de faire le mal '26
Oups, vous avez raison. Je pense que c'est ce que je voulais vraiment dire, je n'y pensais tout simplement pas. Merci @Giles.
Stefan Lasiewski
Voulez-vous qu'il imprime toujours stdout?
Mikel

Réponses:

52

Si vous utilisez, bashpourquoi ne pas utiliser des pipes anonymes, en résumé, ce que phunehehe a dit:

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

Jé Queue
la source
3
+1 Nice! Bash seulement, mais bien plus propre que les alternatives.
Mikel
1
+1 j'ai utilisé cela pour vérifier la cpsortiecp -r dir* to 2> >(grep -v "svn")
Betlista
5
Si vous voulez que la sortie redirigée filtrée soit à nouveau sur stderr, ajoutez un >&2command 2> >(grep something >&2)
message
2
S'il vous plaît expliquer comment cela fonctionne. 2>redirige stderr vers un fichier, je comprends ça. Cela lit à partir du fichier >(grep -i Duration). Mais le fichier n'est jamais stocké? Comment appelle-t-on cette technique pour en savoir plus à ce sujet?
Marko Avlijaš
1
Il s'agit d'un "sucre syntaxique" pour créer un tuyau (pas un fichier) et, une fois l'opération terminée, retirer ce tuyau. En réalité, ils sont anonymes car ils ne reçoivent pas de nom dans le système de fichiers. Bash appelle cette substitution de processus.
Jé Queue
48

Aucun des shells habituels (même zsh) n'autorise les pipes autres que de stdout à stdin. Mais tous les shells de style Bourne prennent en charge la réaffectation de descripteur de fichier (comme dans 1>&2). Vous pouvez donc temporairement renvoyer stdout sur fd 3 et stderr sur stdout, puis remettre fd 3 sur stdout. Si stuffproduit une sortie sur stdout et une sortie sur stderr, et que vous souhaitez appliquer filtersur la sortie d'erreur sans modifier la sortie standard, vous pouvez l'utiliser { 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, arrête de faire le mal
la source
Cette approche fonctionne. Malheureusement, dans mon cas, si une valeur de retour non nulle est renvoyée, elle est perdue - la valeur renvoyée est 0 pour moi. Cela peut ne pas toujours arriver, mais cela arrive dans le cas que je suis en train de regarder. Y a-t-il un moyen de le sauver?
Faheem Mitha
2
@FaheemMitha Je ne sais pas ce que vous faites, mais pipestatuscela aiderait peut - être
Gilles 'SO - arrête d'être méchant'
1
@FaheemMitha, set -o pipefailpeut également être utile ici, selon ce que vous voulez faire avec le statut d'erreur. (Par exemple, si vous avez set -eactivé l'échec pour toute erreur, vous voudrez probablement set -o pipefailaussi.)
Wildcard
@Wildcard Oui, j'ai set -eactivé pour échouer sur les erreurs.
Faheem Mitha
La rccoquille permet la tuyauterie stderr. Voir ma réponse ci-dessous.
Rolf
20

Ceci est similaire à "l'astuce de fichier temp" de phunehehe, mais utilise plutôt un canal nommé, ce qui vous permet d'obtenir des résultats un peu plus proches du moment de leur sortie, ce qui peut être pratique pour les commandes de longue durée:

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

Dans cette construction, stderr sera dirigé vers le tuyau nommé "mypipe". Comme il grepa été appelé avec un argument de fichier, STDIN ne sera pas saisi. Malheureusement, vous devrez toujours nettoyer le tuyau nommé une fois que vous aurez terminé.

Si vous utilisez Bash 4, il existe une syntaxe de raccourci pour command1 2>&1 | command2, qui est command1 |& command2. Cependant, je pense que ceci est purement un raccourci de syntaxe, vous redirigez toujours STDERR vers STDOUT.

Steven D
la source
Connexes: stackoverflow.com/questions/2871233/…
Stefan Lasiewski
1
Cette |&syntaxe est parfaite pour nettoyer les traces de pile Ruby stderr gonflées. Je peux enfin grep ceux sans trop de soucis.
pgr
8

Les réponses de Gilles et Stefan Lasiewski sont bonnes, mais cette méthode est plus simple:

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

Je suppose que vous ne voulez pas que ffmpeg'sstdout soit imprimé.

Comment ça fonctionne:

  • tuyaux d'abord
    • ffmpeg et grep sont lancés, avec le stdout de ffmpeg allant sur le stdin de grep
  • redirections ensuite, de gauche à droite
    • Le paramètre stderr de ffmpeg est défini quelle que soit sa stdout (actuellement le canal)
    • La sortie standard de ffmpeg est définie sur / dev / null
Mikel
la source
Cette description est déroutante pour moi. Les deux dernières puces me font penser "rediriger stderr vers stdout", puis "rediriger stdout (avec stderr, maintenant) vers / dev / null". Cependant, ce n'est pas ce que cela fait vraiment. Ces déclarations semblent être inversées.
Steve
1
@Steve Il n'y a pas de "avec stderr" dans la deuxième puce. Avez-vous vu unix.stackexchange.com/questions/37660/order-of-redirections ?
Mikel
Non, je veux dire que c'est mon interprétation de la façon dont vous l'avez décrite en anglais. Le lien que vous avez fourni est très utile, cependant.
Steve
Pourquoi devait-il être redirigé vers / dev / null?
Kanwaljeet Singh le
7

Voir ci-dessous pour le script utilisé dans ces tests.

Grep ne peut fonctionner que sur stdin, vous devez donc convertir le flux stderr sous une forme que Grep peut analyser.

Normalement, stdout et stderr sont tous deux imprimés sur votre écran:

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

Pour masquer stdout, mais toujours imprimer stderr, procédez comme suit:

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

Mais grep ne fonctionnera pas sur stderr! Vous pouvez vous attendre à ce que la commande suivante supprime les lignes contenant «err», mais ce n'est pas le cas.

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

Voici la solution.

La syntaxe Bash suivante cachera la sortie sur stdout, mais affichera toujours stderr. Nous lançons d’abord stdout dans / dev / null, puis nous convertissons stderr en stdout, car les pipes Unix ne fonctionneront que sur stdout. Vous pouvez toujours grep le texte.

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

(Notez que la commande ci-dessus est différente alors ./command >/dev/null 2>&1, ce qui est une commande très courante).

Voici le script utilisé pour les tests. Ceci affiche une ligne sur stdout et une ligne sur 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
la source
Si vous inversez les redirections, vous n'avez pas besoin de toutes les accolades. Faites juste ./stdout-stderr.sh 2>&1 >/dev/null | grep err.
Mikel
3

Lorsque vous dirigez la sortie d'une commande vers une autre (en utilisant |), vous ne faites que rediriger la sortie standard. Cela devrait donc expliquer pourquoi

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

ne sort pas ce que vous vouliez (ça marche quand même).

Si vous ne souhaitez pas rediriger la sortie d'erreur vers la sortie standard, vous pouvez rediriger la sortie d'erreur vers un fichier, puis corrigez-la ultérieurement.

ffmpeg -i 01-Daemon.mp3 2> /tmp/ffmpeg-error
grep -i Duration /tmp/ffmpeg-error
phunehehe
la source
Merci. it does work, though, vous voulez dire que cela fonctionne sur votre machine? Deuxièmement, comme vous l'avez fait remarquer en utilisant un tuyau, nous ne pouvons que rediriger stdout. Je suis intéressé par une fonctionnalité de commande ou bash qui me permettra de rediriger stderr. (mais pas l'astuce de fichier temporaire)
Andrew-Dufresne
@ Andrew je veux dire, la commande fonctionne comme elle a été conçue. Cela ne fonctionne tout simplement pas comme vous le souhaitez :)
phunehehe
Je ne connais aucun moyen de rediriger la sortie d'erreur d'une commande vers l'entrée standard d'une autre. Ce serait intéressant si quelqu'un pouvait le signaler.
Phunehehe
2

Vous pouvez échanger les flux. Cela vous permettrait d' grepaccéder au flux d'erreur standard d'origine tout en obtenant la sortie qui était à l'origine en sortie standard dans le terminal:

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

Cela fonctionne en créant tout d'abord un nouveau descripteur de fichier (3) ouvert pour la sortie et en le configurant sur le flux d'erreur standard ( 3>&2). Ensuite, nous redirigeons l'erreur type vers la sortie standard ( 2>&1). Enfin, la sortie standard est redirigée vers l'erreur standard d'origine et le nouveau descripteur de fichier est fermé ( 1>&3-).

Dans ton cas:

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

Le tester:

$ ( 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
la source
1

J'aime utiliser la rccoquille dans ce cas.

Installez d'abord le paquet (moins de 1 Mo).

Voici un exemple de la façon dont vous écarteriez stdoutet dirigeriez stderrvers 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é, la syntaxe est simple, ce qui en fait la solution que je préfère.

Vous pouvez spécifier le descripteur de fichier que vous souhaitez transférer entre crochets, juste après le caractère de canal.

Les descripteurs de fichier standard sont numérotés comme tels:

  • 0: entrée standard
  • 1: Ouptut standard
  • 2: erreur type
Rolf
la source
0

Une variante de l'exemple de sous-processus bash:

faire écho deux lignes à stderr et tee stderr à un fichier, grepez le tee et redirigez-vous vers stdout

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

stdout:

fff

des.erreurs (par exemple, stderr):

asdf
fff
Jmunsch
la source
-1

essayez cette commande

  • créer un fichier avec un nom aléatoire
  • envoyer la sortie au fichier
  • envoyer le contenu du fichier à pipe
  • grep

ceva -> juste un nom de variable (anglais = quelque chose)

ceva=$RANDOM$RANDOM$RANDOM; ffmpeg -i qwerty_112_0_0_record.flv 2>$ceva; cat $ceva | grep Duration; rm $ceva;
Rodislav Moldovan
la source
J'utiliserais /tmp/$cevamieux que de jeter des fichiers temporaires dans le répertoire actuel - et vous supposez que le répertoire actuel est accessible en écriture.
Rolf