Bash scripting - file descriptor: differenze tra le versioni

Da Guide@Debianizzati.Org.
Vai alla navigazione Vai alla ricerca
m (rimosso template autori)
mNessun oggetto della modifica
Riga 3: Riga 3:
Su Unix e Unix-like ogni processo che non è avviato in background ha di default tre '''file descriptor''', nella forma di identificativi interi:
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);
* 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);
* 1 ('''stdout'''), lo ''standard output'', a cui si inviano tutti i messaggi prodotti (di default sul terminale associato), tranne quelli relativi a messaggi d'errore;
* 2 ('''stderr'''), lo ''standard error'', a cui si inviano tutti i messaggi di errore 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.
Lo standard input è chiuso per i processi avviati in background.
Riga 60: Riga 60:


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 <code>read</code> 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.
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 <code>read</code> 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.
Allo stesso modo:
<pre>
{
  printf %s\\n "$var"
  printf %s\\n "$var2"
  printf %s\\n "$var3"
} > file
</pre>
scrive il contenuto delle variabili nel file, facendole seguire da una riga vuota. È equivalente a:
<pre>
printf %s\\n "$var" > file
printf %s\\n "$var2" >> file # append!
printf %s\\n "$var3" >> file # append!
</pre>
Si noti invece che utilizzando sempre la redirezione <code>></code> per tre volte di fila, si scriverebbe il file sempre dall'inizio, con il risultato che solo l'ultima variabile sarebbe presente nel file al termine delle istruzioni.


==Nuovi file descriptor==
==Nuovi file descriptor==

Versione delle 11:22, 24 lug 2014

Bash scripting

Sommario

  1. Introduzione
  2. Comandi essenziali
  3. Variabili (stringhe)
  4. Caratteri di escape, apici e virgolette
  5. Espansioni in stringhe quotate
  6. Espansioni non quotabili
  7. Istruzioni composte
  8. Funzioni
  9. File descriptor e redirezioni
  10. Segnali

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), tranne quelli relativi a messaggi d'errore;
  • 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.

Allo stesso modo:

{
   printf %s\\n "$var"
   printf %s\\n "$var2"
   printf %s\\n "$var3"
} > file

scrive il contenuto delle variabili nel file, facendole seguire da una riga vuota. È equivalente a:

printf %s\\n "$var" > file
printf %s\\n "$var2" >> file # append!
printf %s\\n "$var3" >> file # append!

Si noti invece che utilizzando sempre la redirezione > per tre volte di fila, si scriverebbe il file sempre dall'inizio, con il risultato che solo l'ultima variabile sarebbe presente nel file al termine delle istruzioni.

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)!