Appendice E. Una dettagliata introduzione all'I/O e alla redirezione I/O

scritta da Stéphane Chazelas e rivista dall'autore del documento

Un comando si aspetta che siano disponibili i primi tre descrittori di file. Il primo, fd 0 (lo standard input, stdin), è utilizzato per la lettura. Gli altri due (fd 1, stdout e fd 2, stderr) per la scrittura.

Ad ogni comando sono associati uno stdin, uno stdout e uno stderr. ls 2>&1 trasforma temporaneamente lo stderr del comando ls in un'unica "risorsa", lo stdout della shell.

Per convenzione, un comando legge il proprio input da fd 0 (stdin), visualizza l'output in fd 1 (stdout) e i messaggi d'errore in fd 2 (stderr). Se uno di questi descrittori di file non è aperto, si possono riscontrare dei problemi:

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

Ad esempio, quando viene posto in esecuzione xterm, come prima cosa questo inizializza se stesso. Prima di mettere in esecuzione la shell dell'utente, xterm apre per tre volte il dispositivo di terminale (/dev/pts/<n> o qualcosa di analogo).

A questo punto Bash eredita questi tre descrittori di file, a loro volta ereditati da ogni comando (processo figlio) messo in esecuzione da Bash, tranne quando il comando viene rediretto. Redirezione vuol dire la riassegnazione uno dei descrittori di file a un altro file (o ad una pipe, o ad altro che lo consenta). I descrittori di file possono essere riassegnati localmente (per un comando, un gruppo di comandi, una subshell, if o case, cicli for o while...), oppure globalmente, per l'intera shell (usando exec).

ls > /dev/null esegue ls con il suo fd 1 connesso a /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

Questo funziona per tipi differenti di redirezione.

Esercizio: Si analizzi lo script seguente.

#! /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, allo stdout
   sleep 1
   echo 2nd, allo stderr >&2
   sleep 1                                                             
   echo 3rd, a fd 3 >&3
   sleep 1                                                             
   echo 4th, a fd 4 >&4
   sleep 1                                                             
   echo 5th, to fd 5 >&5
   sleep 1                                                             
   echo 6th, tramite una pipe | sed 's/.*/PIPE: &, to fd 5/' >&5
   sleep 1                                                             
   echo 7th, a fd 6 >&6
   sleep 1                                                             
   echo 8th, a fd 7 >&7                                             
   sleep 1                                                             
   echo 9th, a 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


# Per ogni comando e subshell, indicate il fd in uso e a cosa punta.

exit 0