Cours Linux / Comprendre stdin, stdout, stderr, les redirections et les pipes

Une commande dispose habituellement de trois flux standard : stdin pour l’entrée, stdout pour les résultats et stderr pour les diagnostics. Dans Bash, < relie un fichier à l’entrée, > ou >> envoie la sortie vers un fichier, 2> redirige les diagnostics et | relie la sortie d’une commande à l’entrée d’une autre. Comprendre ces destinations permet d’expliquer un résultat qui n’apparaît plus à l’écran.

Les exemples utilisent Bash, GNU grep et les outils GNU habituels dans votre Linux de pratique. Aucun sudo n’est nécessaire. Un flux de données et un état de sortie sont deux choses distinctes : un programme peut écrire un diagnostic d’un côté et renvoyer un nombre au shell pour signaler son résultat.

Distinguer les trois flux et leurs numéros

Un descripteur de fichier est un numéro par lequel un processus accède à une entrée ou une sortie ouverte. Les numéros standard sont 0, 1 et 2. Dans un terminal interactif, ils sont souvent reliés au clavier et à l’écran, mais une redirection ou un pipe peut les relier ailleurs.

Numéro Nom Rôle habituel
0 stdin, entrée standard Données que le programme peut lire
1 stdout, sortie standard Résultat transmis à l’utilisateur ou à une autre commande
2 stderr, sortie d’erreur standard Diagnostics, erreurs et parfois messages d’information

stdout et stderr peuvent tous deux apparaître sur le même écran tout en restant deux canaux différents. Rediriger l’un ne redirige pas automatiquement l’autre. Un programme n’est pas non plus obligé de lire stdin simplement parce que ce flux est disponible.

Le flux 0 stdin entre dans la commande, les flux 1 stdout et 2 stderr en sortent séparément ; un pipe relie seulement stdout de la commande A à stdin de la commande B par défaut

Le bas du schéma représente un pipe ordinaire : il transporte stdout de la première commande vers stdin de la seconde. stderr conserve sa destination sauf indication supplémentaire.

Créer un fichier et distinguer remplacement et ajout

Créez un dossier linux-course-14-practice neuf dans votre répertoire personnel. Si ce nom existe déjà, changez candidat. Le premier mkdir doit réussir avant de renseigner flux :

flux=
candidat="$HOME/linux-course-14-practice"
if mkdir -- "$candidat"; then
  flux=$candidat
  printf 'Atelier : %s\n' "$flux"
else
  printf '%s\n' 'Choisir un dossier neuf et vérifier avant de poursuivre.' >&2
fi

Gardez le même terminal et ne redéfinissez pas flux vers un autre emplacement. Si mkdir échoue, la variable reste vide et les blocs suivants ne poursuivent pas. Créez deux lignes, puis ajoutez-en une troisième :

test -n "$flux" &&
printf 'alpha\nbeta\n' > "$flux/source.txt" &&
printf 'gamma\n' >> "$flux/source.txt" &&
cat -- "$flux/source.txt"
alpha
beta
gamma

> crée le fichier ou, par défaut, vide un fichier existant avant la nouvelle écriture. >> crée un fichier absent ou ajoute à la fin sans retirer le contenu déjà présent. La protection Bash noclobber, si elle est activée dans votre session, peut refuser l’écriture > sur un fichier ordinaire existant : examinez le refus au lieu de forcer un remplacement.

Ces redirections sont préparées par le shell avant l’exécution de la commande. Ne choisissez donc pas le fichier d’entrée comme destination > d’un traitement : il pourrait être vidé avant d’être lu. Le manuel Bash des redirections explique l’ouverture des fichiers et les conditions de remplacement.

Donner un fichier à stdin ou enregistrer stdout

Avec <, le shell ouvre le fichier pour que la commande le lise sur stdin. Comptez les fins de ligne sans fournir de nom de fichier à wc :

test -n "$flux" && wc -l < "$flux/source.txt"

Vous obtenez 3, sans nom de fichier à la suite. Avec wc -l suivi du chemin comme argument, wc affiche normalement aussi ce nom : les données lues sont ici les mêmes, mais l’interface d’entrée diffère. Le guide de wc précise les unités comptées.

Pour enregistrer les lignes qui contiennent beta, redirigez la sortie de grep vers un autre fichier :

test -n "$flux" &&
grep 'beta' -- "$flux/source.txt" > "$flux/matches.txt"

Le résultat sélectionné ne s’affiche plus dans le terminal : il est écrit dans matches.txt. Consultez ce fichier séparément :

test -n "$flux" && cat -- "$flux/matches.txt"

Vous devez lire beta. source.txt conserve ses trois lignes. Une sortie vide à l’écran ne signifie donc pas que grep n’a rien trouvé ; il faut regarder la destination choisie et l’état de la commande.

Enregistrer stderr sans le confondre avec stdout

Dans notre nouveau dossier, absent-14.txt n’existe pas et ne doit pas être créé pour ce test. GNU ls écrit le diagnostic sur stderr ; 2> dirige ce seul canal vers errors.txt :

test -n "$flux" && ls -- "$flux/absent-14.txt" 2> "$flux/errors.txt"
test -n "$flux" && cat -- "$flux/errors.txt"

La première commande doit signaler un échec par son état, mais son diagnostic n’apparaît à l’écran que lorsque cat lit errors.txt à la ligne suivante. La formulation et la langue varient ; le message doit indiquer que le chemin demandé est absent. Les deux lignes sont volontairement séparées : la lecture du diagnostic doit rester possible après l’échec attendu de ls.

Un simple > redirige normalement le descripteur 1, pas le 2. Pour observer les deux flux dans une même opération, demandez à ls de nommer notre fichier existant et le chemin absent, puis regroupez les sorties :

test -n "$flux" &&
ls -d -- "$flux/source.txt" "$flux/absent-14.txt" > "$flux/all.txt" 2>&1
test -n "$flux" && cat -- "$flux/all.txt"

all.txt doit contenir le chemin de source.txt et le diagnostic concernant absent-14.txt. Ne déduisez pas de leur ordre un déroulement universel : la mise en tampon peut influencer la présentation. Le résultat normal et le diagnostic ont la même destination, mais ls conserve son état d’échec à cause du fichier absent.

Comprendre pourquoi l’ordre de 2>&1 compte

Les redirections Bash sont traitées de gauche à droite. Dans > all.txt 2>&1, stdout est d’abord dirigé vers all.txt ; stderr est ensuite dupliqué vers la destination actuelle de stdout. Les deux finissent ainsi dans le fichier.

Inversez seulement l’ordre de ces deux opérations et utilisez un autre fichier pour comparer :

test -n "$flux" &&
ls -d -- "$flux/source.txt" "$flux/absent-14.txt" 2>&1 > "$flux/stdout-only.txt"
test -n "$flux" && cat -- "$flux/stdout-only.txt"

Cette fois, stderr a d’abord reçu la destination qu’avait stdout à cet instant, habituellement le terminal. Le déplacement ultérieur de stdout vers stdout-only.txt ne déplace pas à nouveau stderr. Vous voyez donc le diagnostic sur le terminal, puis cat affiche le chemin de source.txt enregistré seul dans le fichier.

2>&1 ne signifie pas « enregistrer le texte 1 » ni « suivre toutes les futures redirections du canal 1 ». Il duplique une destination au moment où le shell traite cette notation. Dans un script dont stdout est déjà redirigé ailleurs, cette destination initiale ne sera pas forcément l’écran.

Relier deux commandes par un pipe

Le pipe | évite un fichier intermédiaire pour transmettre les résultats. Cherchez la ligne beta, puis comptez les fins de ligne transmises :

test -n "$flux" && (
  grep 'beta' -- "$flux/source.txt" | wc -l
)

Le résultat attendu est 1. grep lit ici le fichier indiqué par son argument ; wc lit stdin, c’est-à-dire les lignes reçues du pipe. Par défaut, le pipe ne transporte pas stderr de grep. Si son ouverture de fichier échoue, le diagnostic peut donc rester sur le terminal pendant que wc traite une entrée vide.

Les flux transmis et le verdict de l’ensemble sont deux questions distinctes. Par défaut, Bash utilise l’état de la dernière commande du pipe. Dans notre fichier connu, aucune ligne ne contient absent ; observez un comptage nul avec pipefail explicitement désactivé dans un sous-shell :

test -n "$flux" && (
  set +o pipefail
  grep 'absent' -- "$flux/source.txt" | wc -l
  etat=$?
  printf 'Etat du pipe : %s\n' "$etat"
)

Vous obtenez 0 pour le nombre de lignes, puis Etat du pipe : 0, parce que wc a terminé normalement. grep a pourtant renvoyé 1 pour « aucune correspondance », ce qui n’est pas une erreur d’accès. Ce 0 global ne prouve donc pas que chaque commande a renvoyé 0.

Changez seulement le réglage en set -o pipefail et répétez le même bloc. Le nombre affiché reste 0, mais Etat du pipe devient 1 : l’état non nul de grep est maintenant pris en compte. Les parenthèses limitent ce changement de réglage au sous-shell et préservent votre session principale. Le manuel Bash des pipelines définit la connexion des flux et le calcul de l’état avec pipefail.

La leçon rechercher et traiter du texte avec grep, cut, sort et uniq utilise cette connexion pour compter des noms. Choisissez le traitement de l’état en fonction de ce que vous considérez comme un résultat acceptable, puis lisez les diagnostics plutôt que de confondre absence de correspondance et panne.

Vérifier les destinations et retrouver le contenu initial

Notation Connexion Effet à contrôler
< fichier Fichier vers stdin Le programme doit lire son entrée standard
> fichier stdout vers fichier Création ou remplacement selon la protection active
>> fichier stdout vers la fin du fichier Ajout, sans retirer le contenu précédent
2> fichier stderr vers fichier Les diagnostics ne vont plus à leur destination précédente
> fichier 2>&1 stdout puis stderr vers le même fichier Ordre des redirections
A | B stdout de A vers stdin de B stderr et états de sortie à examiner séparément

À la fin de cet atelier, source.txt doit toujours contenir alpha, beta, gamma. Vérifiez-le avec le même cat que lors de sa création. Les fichiers matches.txt, errors.txt, all.txt et stdout-only.txt contiennent des résultats séparés : leur présence n’indique pas que l’entrée a été modifiée. Si vous avez recommencé l’ajout de gamma plusieurs fois, rétablissez seulement le fichier de démonstration, puis vérifiez de nouveau :

test -n "$flux" &&
printf 'alpha\nbeta\ngamma\n' > "$flux/source.txt" &&
cat -- "$flux/source.txt" &&
wc -l < "$flux/source.txt"

Vous devez retrouver les trois lignes et le compte 3. Pour diagnostiquer une autre commande, identifiez d’abord ce qu’elle lit, où elle envoie stdout et stderr, puis examinez son état de sortie. Une commande qui attend encore stdin peut rester active ; une commande dont stdout a été enregistré peut avoir fini sans rien montrer à l’écran.