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 $textsenza 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]
readha 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 helloe worldè stato mantenuto, perché è all'interno della linevariabile. D'altra parte, lo spazio iniziale è stato consumato. Questo perché readlegge 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 IFSvariabile 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 readbuilt-in . Le IFS= read -r lineimposta 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 readnon è un built-in speciale , quindi l'assegnazione dura solo per la sua durata.
Pertanto, stiamo facendo attenzione a non modificare il valore di IFSaltre istruzioni che potrebbero fare affidamento su di esso. Questo codice funzionerà indipendentemente da ciò che il codice circostante ha impostato IFSinizialmente 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 $PATHnon si dividerebbe $PATHin componenti separati da due punti. Se il codice fosse IFS=; while read …, sarebbe ancora più ovvio che IFSnon è impostato :nel corpo del ciclo.
Naturalmente, sarebbe possibile ripristinare il valore di IFSdopo 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 readviene 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 readnon si divideX, mawhile IFS=X; read...