Bash scripting - file descriptor: differenze tra le versioni
S3v (discussione | contributi) (- template "Versioni compatibili") |
m (rimosso template autori) |
||
Riga 112: | Riga 112: | ||
read riga < file # legge sempre la prima riga del file (se esiste)! | read riga < file # legge sempre la prima riga del file (se esiste)! | ||
</pre> | </pre> | ||
[[Categoria:Bash]][[Categoria:Bash_Scripting]] | [[Categoria:Bash]][[Categoria:Bash_Scripting]] |
Versione delle 18:07, 23 lug 2014
Bash scripting |
Sommario |
File descriptor
Su Unix e Unix-like ogni processo che non è avviato in background ha di default tre file descriptor, nella forma di identificativi interi:
- 0 (stdin), lo standard input, da cui si leggono gli input (di default quanto scritto dalla tastiera sul terminale associato);
- 1 (stdout), lo standard output, a cui si inviano tutti i messaggi prodotti (di default sul terminale associato);
- 2 (stderr), lo standard error, a cui si inviano tutti i messaggi di errore prodotti (di default sul terminale associato).
Lo standard input è chiuso per i processi avviati in background.
Si chiamano file descriptor perché sono identificativi che fanno riferimento a un file; e infatti perfino i dispositivi, anche virtuali come il terminale, sono considerati dei file. Questo permette la possibilità di associare in modo trasparente questi file descriptor standard anche a file qualsiasi, oltre che ad altri file descriptor, in modo da ridirigerne il contenuto. E disporre di due diversi file descriptor per l'output prodotto da un comando e i messaggi di errore, permette di disabilitare anche solo uno dei due, o di salvarli su due file diversi.
Redirezioni
Alcune comuni redirezioni, da scriversi dopo un comando (la stringa file può essere anche una variabile quotata):
- < file collega lo standard input al file, in modo da leggerne il contenuto. Il file descriptor è implicito, ma sarebbe equivalente scrivere 0< file;
- > file dopo un comando ne scrive lo standard output sul file (troncandolo, se esiste). Si noti che il file descriptor è implicito, ma sarebbe equivalente scrivere 1> file;
- >> file (append) aggiunge il contenuto dello standard output al file (creandolo, se non esiste). Il file descriptor è implicito, ma sarebbe equivalente scrivere 1>> file;
- 2> file è simile a > file, ma ridirige lo standard error anziché lo standard output sul file (con append: 2>> file);
- >&2 scrive lo standard output sullo standard error. Il file descriptor dello standard output è implicito, mentre quello alla destra dev'essere preceduto da
&
, ma sarebbe equivalente scrivere 1>&2; - 2>&1 scrive lo standard error sullo standard output;
- &> file (non POSIX, abbreviazione per: > file 2>&1) invia standard output ed error sul file (con append: &>> file);
- <&- chiude lo standard input. Il file descriptor è implicito, ma sarebbe equivalente scrivere 0<&-;
- >&- chiude lo standard output. Il file descriptor è implicito, ma sarebbe equivalente scrivere 1>&-;
- 2>&- chiude lo standard error.
Per evitare la scrittura a schermo standard output e/o standard error non vanno chiusi, oppure l'operazione genererebbe un errore (perché il file descriptor relativo non esisterebbe più), ma ridiretti su /dev/null
, un file dispositivo la cui esistenza è specificata da POSIX che non produce mai output e può essere usato per assorbire qualsiasi cosa.
Esempi:
comando &> /dev/null # non stampa niente a schermo, neanche gli errori comando > /dev/null 2>&1 # equivalente (POSIX) # si noti la differenza con: comando >&- 2>&- # standard output ed error sono chiusi, quindi se # venissero usati, l'exit status sarebbe diverso da 0!
Una pipe (comando1 | comando2
), già trattata nella sezione sulle istruzioni composte, è anch'essa una forma di redirezione, in cui lo standard output di comando1 diventa lo standard input di comando2.
Scope della redirezione
Si noti che lo scope (il raggio d'azione) della redirezione non serve soltanto per evitare di scrivere più volte il percorso del file, ma ne influenza anche il significato. In particolare con la redirezione dello standard input (lettura), per garantire che tutte le istruzioni continuino la lettura da dove era rimasta invece che riprenderla sempre dall'inizio, e dello standard output, per garantire che il file non venga troncato da ogni comando ma che i successivi continuino a scrivere in seguito (come se usassero la redirezione con append).
Per esempio in genere è sbagliato scrivere:
while read riga < file do # "$riga" contiene *SEMPRE* la prima riga del file # quindi il ciclo può essere eseguito per sempre se ha più di una riga! done
Mentre quello che si vuole è probabilmente:
while read riga do # "$riga" contiene sempre una nuova linea del file # il ciclo è eseguito per ogni linea del file done < file
Si noti che leggere in questo modo un file ha senso solo per file non binari, che potrebbero contenere anche il carattere ASCII n. 0, che non può essere memorizzato in una variabile. Inoltre l'istruzione read
fallisce se non trova un "a capo", per cui l'ultima riga del file è letta all'interno del ciclo soltanto se termina con un "a capo", altrimenti è necessario controllare se è vuota la variabile dopo il ciclo.
Nuovi file descriptor
In aggiunta ai tre file descriptor standard, è possibile aprirne di nuovi, in lettura (FD<...
...), in scrittura (FD>...
, append: FD>>...
) e in lettura/scrittura (FD<>...
), dove FD è un intero rappresentante il nuovo file descriptor e al posto dei puntini può esserci sia un file sia un file descriptor già esistente preceduto dal carattere &
.
È necessario quindi determinare a quale file (o file descriptor, se preceduto da &
) il nuovo file descriptor va associato. Ci sono due modi:
- specificarlo dopo un'istruzione composta, in modo che il file descriptor sia utilizzabile normalmente all'interno;
- specificarlo con l'istruzione
exec
senza altri argomenti (o avrebbe un significato diverso).
Per usarlo invece, una volta aperto, il file descriptor va scritto subito dopo la redirezione, preceduto dal carattere &
.
Per esempio:
{ # il file descriptor 3 è definito dentro il blocco (in scrittura) comando1 >&3 # ridirige lo standard output sul nuovo file descriptor comando2 comando3 >&3 # come per comando1 } 3> file # apre in scrittura il file descriptor per il contenuto del blocco # NOTA: fuori dal blocco il nuovo file descriptor non esiste più
Sul file scelto sono ridiretti lo standard output di comando1 e comando3. È equivalente a:
comando1 > file # scrittura comando2 comando3 >> file # scrittura con append! (nel blocco è implicito)
Equivalentemente, con exec
:
exec 3> file # apre il file descriptor in scrittura comando1 >&3 # usa il file descriptor comando2 comando3 >&3 # usa il file descriptor exec 3>&- # chiude il file descriptor
Finché un file descriptor non è chiuso, non è garantita la scrittura sul file, ma la scrittura tramite file descriptor è più efficiente.
Salvare i file descriptor
Si noti che exec
è equivalente a una redirezione in un blocco, ma la chiusura dei file descriptor aperti dev'essere gestita manualmente. Prima di cambiare quelli standard, per consentirne il successivo ripristino al loro valore originale, vanno salvati in un nuovo file descriptor temporaneo:
exec 3<&0 # apre un nuovo file descriptor per salvare lo standard input exec < file # sovrascrive lo standard input read riga # legge la prima riga (se esiste) read riga # legge la seconda riga (se esiste)! (e non di nuovo la prima) exec <&3 # assegna nuovamente lo standard input al suo valore precedente exec 3<&- # chiude il nuovo file descriptor
si ricordi che invece, senza exec e senza un blocco, una redirezione (di lettura o scrittura senza append) su un singolo comando parte sempre dall'inizio:
read riga < file # legge la prima riga del file (se esiste) read riga < file # legge sempre la prima riga del file (se esiste)!