Diamo un'occhiata a un esempio, con un testo di input accuratamente realizzato:
text=' hello world\
foo\bar'
Sono due righe, il primo che inizia con uno spazio e termina con una barra rovesciata. Innanzitutto, diamo un'occhiata a ciò che accade senza alcuna precauzione in giro read
(ma usando printf '%s\n' "$text"
per stampare attentamente $text
senza alcun rischio di espansione). (Di seguito, $
è il prompt della shell.)
$ printf '%s\n' "$text" |
while read line; do printf '%s\n' "[$line]"; done
[hello worldfoobar]
read
ha mangiato le barre rovesciate: backslash-newline fa sì che la newline venga ignorata e backslash-qualunque cosa ignora la prima barra rovesciata. Per evitare che le barre rovesciate vengano trattate in modo speciale, utilizziamo read -r
.
$ printf '%s\n' "$text" |
while read -r line; do printf '%s\n' "[$line]"; done
[hello world\]
[foo\bar]
Va meglio, abbiamo due linee come previsto. Le due righe contengono quasi il contenuto desiderato: il doppio spazio tra hello
e world
è stato mantenuto, perché è all'interno della line
variabile. D'altra parte, lo spazio iniziale è stato consumato. Questo perché read
legge tutte le parole che passi attraverso le variabili, tranne per il fatto che l'ultima variabile contiene il resto della riga, ma inizia comunque con la prima parola, ovvero gli spazi iniziali vengono scartati.
Quindi, per leggere letteralmente ogni riga, dobbiamo assicurarci che non ci sia divisione di parole . Lo facciamo impostando la IFS
variabile su un valore vuoto.
$ printf '%s\n' "$text" |
while IFS= read -r line; do printf '%s\n' "[$line]"; done
[ hello world\]
[foo\bar]
Nota come abbiamo impostato IFS
specificamente per la durata del read
built-in . Le IFS= read -r line
imposta la variabile di ambiente IFS
(per un valore vuoto) specifico per l'esecuzione di read
. Questa è un'istanza della sintassi del comando semplice generale : una sequenza (possibilmente vuota) di assegnazioni di variabili seguita da un nome di comando e dai suoi argomenti (inoltre, puoi lanciare reindirizzamenti in qualsiasi punto). Poiché read
è un built-in, la variabile non finisce mai in un ambiente di processo esterno; nondimeno il valore di $IFS
è ciò che stiamo assegnando lì finché read
è in esecuzione¹. Nota che read
non è un built-in speciale , quindi l'assegnazione dura solo per la sua durata.
Pertanto, stiamo facendo attenzione a non modificare il valore di IFS
altre istruzioni che potrebbero fare affidamento su di esso. Questo codice funzionerà indipendentemente da ciò che il codice circostante ha impostato IFS
inizialmente e non causerà alcun problema se il codice all'interno del ciclo si basa su IFS
.
In contrasto con questo frammento di codice, che cerca i file in un percorso separato da due punti. L'elenco dei nomi dei file viene letto da un file, un nome file per riga.
IFS=":"; set -f
while IFS= read -r name; do
for dir in $PATH; do
## At this point, "$IFS" is still ":"
if [ -e "$dir/$name" ]; then echo "$dir/$name"; fi
done
done <filenames.txt
Se il ciclo fosse while IFS=; read -r name; do …
, quindi for dir in $PATH
non si dividerebbe $PATH
in componenti separati da due punti. Se il codice fosse IFS=; while read …
, sarebbe ancora più ovvio che IFS
non è impostato :
nel corpo del ciclo.
Naturalmente, sarebbe possibile ripristinare il valore di IFS
dopo l'esecuzione read
. Ma ciò richiederebbe la conoscenza del valore precedente, che è uno sforzo extra. IFS= read
è il modo semplice (e, convenientemente, anche il modo più breve).
¹ E, se read
viene interrotto da un segnale intrappolato, probabilmente mentre la trap è in esecuzione, ciò non è specificato da POSIX e dipende dalla shell in pratica.
while IFS=X read
non si divideX
, mawhile IFS=X; read
...