web-dev-qa-db-fra.com

Comment grep flux d'erreur standard (stderr)?

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

    $ 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 ffmpeg est dirigée vers stderr.

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

Je pense donc que grep est incapable de lire le flux d'erreur pour attraper les lignes correspondantes. Comment pouvons-nous permettre à grep de lire le flux d'erreur?

En utilisant nixCraft link, 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 faire si nous ne voulons pas rediriger stderr vers stdout?

90
Andrew-Dufresne

Si vous utilisez bash pourquoi ne pas utiliser des canaux anonymes, essentiellement un raccourci pour ce que Phunehehe a dit:

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

66
Jé Queue

Aucun des shells habituels (même zsh) ne permet des pipes autres que de stdout à stdin. Mais tous les shells de style Bourne prennent en charge la réaffectation des descripteurs de fichiers (comme dans 1>&2). Vous pouvez donc temporairement détourner stdout vers fd 3 et stderr vers stdout, puis remettre fd 3 sur stdout. Si stuff produit une sortie sur stdout et une sortie sur stderr, et que vous souhaitez appliquer filter sur la sortie d'erreur en laissant la sortie standard intacte, vous pouvez 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

Ceci est similaire au "truc de fichier temporaire" de phunehehe, mais utilise un tube nommé à la place, vous permettant d'obtenir des résultats légèrement plus proches 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". Puisque grep a été appelé avec un argument file, il ne recherchera pas STDIN pour son entrée. Malheureusement, vous devrez toujours nettoyer ce tuyau nommé une fois que vous aurez terminé.

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

22
Steven D

Les réponses de Gilles et Stefan Lasiewski sont toutes les deux 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 ffmpeg's stdout imprimé.

Comment ça fonctionne:

  • tuyaux en premier
    • ffmpeg et grep sont démarrés, avec stdout de ffmpeg allant à stdin de grep
  • redirections ensuite, de gauche à droite
    • stderr de ffmpeg est défini sur quelle que soit sa sortie standard (actuellement le tube)
    • la sortie standard de ffmpeg est définie sur/dev/null
11
Mikel

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 vous attendriez à ce que la commande suivante supprime les lignes qui contiennent "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 masquera la sortie vers stdout, mais affichera toujours stderr. D'abord, nous dirigeons stdout vers/dev/null, puis nous convertissons stderr en stdout, car les tuyaux 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 puis ./command >/dev/null 2>&1, qui est une commande très courante).

Voici le script utilisé pour les tests. Ceci imprime 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
8
Stefan Lasiewski

Vous pouvez échanger les flux. Cela vous permettrait de grep le flux d'erreurs standard d'origine tout en obtenant la sortie qui était à l'origine sortie standard dans le terminal:

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

Cela fonctionne en créant d'abord un nouveau descripteur de fichier (3) ouvert pour la sortie et en le définissant sur le flux d'erreur standard (3>&2). Ensuite, nous redirigeons l'erreur standard 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
3
Kusalananda

Lorsque vous dirigez la sortie d'une commande vers une autre (à l'aide de |), vous redirigez uniquement la sortie standard. Cela devrait donc expliquer pourquoi

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

ne produit pas ce que vous vouliez (cela fonctionne, cependant).

Si vous ne voulez pas rediriger la sortie d'erreur vers la sortie standard, vous pouvez rediriger la sortie d'erreur vers un fichier, puis le grep plus tard

ffmpeg -i 01-Daemon.mp3 2> /tmp/ffmpeg-error
grep -i Duration /tmp/ffmpeg-error
3
phunehehe

bash peut rediriger stdout vers le flux stdin via un canal normal - | Il peut également rediriger stdout et stderr vers stdin par |&

1
0x00

J'aime utiliser le shell rc dans ce cas.

Installez d'abord le package (il est inférieur à 1 Mo).

Voici un exemple de la façon dont vous pouvez supprimer stdout et pipe stderr pour 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 ma solution préférée.

Vous pouvez spécifier le descripteur de fichier à insérer entre crochets, juste après le caractère de canal.

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

  • 0: entrée standard
  • 1: sortie standard
  • 2: erreur standard
1
Rolf

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

faire écho deux lignes à stderr et tee stderr à un fichier, grep le tee et le tuyau de retour à stdout

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

stdout:

fff

some.load.errors (par exemple stderr):

asdf
fff
0
jmunsch