E. Une introduction détaillée sur les redirections d'entrées/sorties

Écrit par Stéphane Chazelas et relu par l'auteur de ce document

Une commande s'attend à ce que les trois premiers descripteurs de fichier (fd) soient disponibles. Le premier, fd 0 (l'entrée standard, stdin), concerne la lecture. Les deux autres (fd 1, stdout et fd 2, stderr) concernent l'écriture.

Il existe un stdin, stdout et un stderr associés à chaque commande. ls 2>&1 connecte temporairement le stderr de la commande ls à la même « ressource » que le stdout du shell.

Par convention, une commande lit l'entrée à partir de fd 0 (stdin), affiche sur la sortie normale, fd 1 (stdout) et sur la sortie des erreurs, fd 2 (stderr). Si un des trois fd n'est pas ouvert, vous pouvez rencontrer des problèmes:

bash$ cat /etc/passwd >&-
cat: standard output: Bad file descriptor
      

Par exemple, lorsque xterm est lancé, il commence par s'initialiser soi-même. Avant de lancer le shell de l'utilisateur, xterm ouvre le périphérique du terminal (/dev/pts/<n> ou quelque chose de similaire) trois fois.

À ce moment, Bash hérite de ces trois descripteurs de fichiers et chaque commande (processus fils) lancée par Bash en hérite à leur tour sauf quand vous redirigez la commande. La redirection signifie la réaffectation d'un des descripteurs de fichier à un autre fichier (ou tube, ou tout autre chose permise). Les descripteurs de fichiers peuvent être réaffectés (pour une commande, un groupe de commande, un sous-shell, une boucle while ou if ou case ou for...) ou, globalement, pour le reste du script (en utilisant exec).

ls > /dev/null lance ls avec fd 1 connecté à /dev/null.

bash$ lsof -a -p $$ -d0,1,2
COMMAND PID     USER   FD   TYPE DEVICE SIZE NODE NAME
 bash    363 bozo        0u   CHR  136,1         3 /dev/pts/1
 bash    363 bozo        1u   CHR  136,1         3 /dev/pts/1
 bash    363 bozo        2u   CHR  136,1         3 /dev/pts/1


bash$ exec 2> /dev/null
bash$ lsof -a -p $$ -d0,1,2
COMMAND PID     USER   FD   TYPE DEVICE SIZE NODE NAME
 bash    371 bozo        0u   CHR  136,1         3 /dev/pts/1
 bash    371 bozo        1u   CHR  136,1         3 /dev/pts/1
 bash    371 bozo        2w   CHR    1,3       120 /dev/null


bash$ bash -c 'lsof -a -p $$ -d0,1,2' | cat
COMMAND PID USER   FD   TYPE DEVICE SIZE NODE NAME
 lsof    379 root    0u   CHR  136,1         3 /dev/pts/1
 lsof    379 root    1w  FIFO    0,0      7118 pipe
 lsof    379 root    2u   CHR  136,1         3 /dev/pts/1


bash$ echo "$(bash -c 'lsof -a -p $$ -d0,1,2' 2>&1)"
COMMAND PID USER   FD   TYPE DEVICE SIZE NODE NAME
 lsof    426 root    0u   CHR  136,1         3 /dev/pts/1
 lsof    426 root    1w  FIFO    0,0      7520 pipe
 lsof    426 root    2w  FIFO    0,0      7520 pipe

Ceci fonctionne avec différents types de redirection.

Exercice : Analyser le script suivant.

#! /usr/bin/env bash


mkfifo /tmp/fifo1 /tmp/fifo2
while read a; do echo "FIFO1: $a"; done < /tmp/fifo1 & exec 7> /tmp/fifo1
exec 8> >(while read a; do echo "FD8: $a, to fd7"; done >&7)

exec 3>&1
(
 (
  (
   while read a; do echo "FIFO2: $a"; done < /tmp/fifo2 | tee /dev/stderr | \
        tee /dev/fd/4 | tee /dev/fd/5 | tee /dev/fd/6 >&7 &
   exec 3> /tmp/fifo2

   echo 1st, to stdout
   sleep 1
   echo 2nd, to stderr >&2
   sleep 1
   echo 3rd, to fd 3 >&3
   sleep 1
   echo 4th, to fd 4 >&4
   sleep 1
   echo 5th, to fd 5 >&5
   sleep 1
   echo 6th, through a pipe | sed 's/.*/PIPE: &, to fd 5/' >&5
   sleep 1
   echo 7th, to fd 6 >&6
   sleep 1
   echo 8th, to fd 7 >&7
   sleep 1
   echo 9th, to fd 8 >&8

  ) 4>&1 >&3 3>&- | while read a; do echo "FD4: $a"; done 1>&3 5>&- 6>&-
 ) 5>&1 >&3 | while read a; do echo "FD5: $a"; done 1>&3 6>&-
) 6>&1 >&3 | while read a; do echo "FD6: $a"; done 3>&-

rm -f /tmp/fifo1 /tmp/fifo2


# Pour chaque commande et sous-shell, cherchez vers quoi est lié chaque fd.
# Bonne chance.

exit 0