Astuces pour gérer les redirections d’erreurs en Bash

Ça faisait bien longtemps que je n'avais rien posté de constructif. Alors pour les débutants que ça intéresse, voici un petit baratin sur les redirections d'erreurs en bash. Je ne vais pas traiter tous les types de redirections mais uniquement certains cas appliqués aux erreurs. Pour obtenir quelque chose d'exhaustif je vous conseille comme d'habitude l'advanced bash scripting guide.

Les cas simples.

Par défaut, les shells comme bash affichent la sortie standard (stdout) et la sortie d'erreur (stderr) dans la fenêtre de terminal où tourne le process bavard. L'utilisateur n'a donc pas, a priori, de moyen de discerner l'une de l'autre, sauf à connaître les formes respectives d'une erreur et d'une sortie normale :

$ touch existant
$ ls inexistant existant
ls: inexistant: No such file or directory   # sortie stderr
existant                                    # sortie stdout

Les entrées et sorties correspondent à des descripteurs de fichiers. L'entrée standard porte le descripteur de fichier 0, la sortie stdout le 1, et stderr le 2. Ce sont ces descripteurs de fichiers qui permettent d'utiliser ces entrées-sorties.
Ainsi, pour un usage tout à fait basique, je peux vouloir enregistrer les erreurs dans un fichier, et n'afficher que les résultats "normaux" de stdout dans mon terminal.
En reprenant l'exemple ci-dessus, cela donne :

$ ls inexistant existant 2>/tmp/ls-erreur
existant 

le descripteur de fichier 2 est dirigé vers le fichier /tmp/ls-erreur, et seule la sortie standard est affichée dans le shell. On peut vérifier que le fichier /tmp/ls-erreur contient bien l'erreur qu'on a constatée lors du test précédent :

$ cat /tmp/ls-erreur
ls: inexistant: No such file or directory

Quand on veut simplement se débarrasser des erreurs, on peut utiliser /dev/null comme déversoir. C'est le trou noir habituel Unix, ce qui y entre n'en sort jamais.
On peut aussi choisir de diriger stderr vers stdout. C'est très utile si on souhaite enchaîner des commandes via des pipes ou des redirections, et que l'on souhaite traiter les erreurs aussi bien que les résultats de stdout. Le résultat de cette manipulation, c'est que nos erreurs ne s'affichent plus dans stderr, mais bien dans stdout. On peut donc les filtrer avec grep, les manipuler avec awk, sed, ... Notez la différence de comportement :

$ ls inexistant existant | awk '/No such/ {print "un fichier absent !"}'
ls: inexistant: No such file or directory
$ ls inexistant existant 2>&1 | awk '/No such/ {print "un fichier absent !"}'
un fichier absent !

Dans le premier cas, stderr n'est pas redirigée dans stdout, donc elle ne passe pas dans le pipe, et n'est pas vue par la commande awk. Dans le second cas, stderr est envoyée dans stdout, qui, elle, est prise en compte par le pipe, et ensuite par awk.

Le cas des scripts.

Dans un script shell, les cas de figure peuvent être plus complexes. La redirection par "2>..." fonctionne bien, mais pour une seule commande. Il faudrait pour l'utiliser dans un script, soit l'utiliser à chaque commande, soit lancer le script en le faisant suivre de cette redirection.
Que faire quand on souhaite par exemple garder un log d'exécution d'un script, ou juste ses erreurs sans s'encombrer d'une redirection à chaque appel du script ?
On utilise la commande exec, qui permet de créer/lier/détruire des descripteurs de fichiers.

Grace à la commande exec, je peux dupliquer un descripteur de fichier existant (une sorte de sauvegarde), ce qui me permet de ré-attribuer son numéro à un autre fichier (mon fichier de log par exemple, ou /dev/null, ...).
Un example va nous permettre de voir comment cela fonctionne.

01 #!/bin/bash
02 exec 6>&2
03 exec 2>>A.log
04 echo "ici on va générer quelques erreurs :"
05 ls -XYZ
06 exec 2>&6 6>&-
07 echo "Fin du script, les erreurs sortent normalement"
08 ls -XYZ

La ligne 02 sert à sauvegarder ma stderr dans le descripteur de fichier 6. Les descripteurs 3 à 9 sont disponibles, 6 est un choix arbitraire.
La ligne 03 remplace le descripteur 2 par mon fichier de log A.log.
Si on teste ce script, on voit bien que l'erreur générée à la ligne 05 ne sort pas dans le shell, elle est écrite directement dans A.log. Bien sûr, la ligne 05 peut être remplacée par autant de ligne de commande que l'on souhaite.
La ligne 06 ré-initialise le descripteur de fichier 2 à son ancienne valeur (stockée dans le 6), détruit le descripteur 6, et ferme le fichier de log.
Les erreurs suivantes, comme celle de la ligne 08, sont donc libres de sortir normalement dans stderr.

J'espère que ça pourra servir de point de départ à certains curieux pour approfondir les différents types de redirection dans le shell. Il y a de quoi s'occuper un bon moment avec.

Pour aller un peu plus loin, n'hésitez pas à consulter les autres articles sur bash !

One comment

  1. sinon, si on a besoin que le script affiche à la fois à l'écran (sortie standard) et dans un fichier, il y a la commande `tee`.
    Mais d'accord, c'est un peu HS par rapport au titre de ton billet qui ne concerne que les erreurs) :)

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.