Alcune persone hanno quell'idea errata che read
è il comando di leggere una riga. Non è.
read
legge le parole da una riga (possibilmente con barra rovesciata), in cui le parole sono $IFS
delimitate e la barra rovesciata può essere utilizzata per sfuggire ai delimitatori (o per le linee continue).
La sintassi generica è:
read word1 word2... remaining_words
read
legge stdin un byte alla volta finché non trova un carattere di nuova riga escape (o fine input), spaccature che secondo regole complesse e memorizza il risultato di tale frazionamento in $word1
, $word2
... $remaining_words
.
Ad esempio su un input come:
<tab> foo bar\ baz bl\ah blah\
whatever whatever
e con il valore predefinito di $IFS
, read a b c
assegnerebbe:
$a
⇐ foo
$b
⇐ bar baz
$c
⇐ blah blahwhatever whatever
Ora se viene passato solo un argomento, questo non diventa read line
. È ancora read remaining_words
. L'elaborazione della barra rovesciata viene ancora eseguita, i caratteri degli spazi bianchi IFS vengono comunque rimossi dall'inizio e dalla fine.
L' -r
opzione rimuove l'elaborazione della barra rovesciata. Quindi lo stesso comando sopra con -r
invece avrebbe assegnato
$a
⇐ foo
$b
⇐ bar\
$c
⇐ baz bl\ah blah\
Ora, per la parte di divisione, è importante rendersi conto che ci sono due classi di caratteri per $IFS
: i caratteri degli spazi bianchi IFS (ovvero spazio e tabulazione (e newline, anche se qui non importa a meno che non si usi -d), che si verificano anche essere nel valore predefinito di $IFS
) e gli altri. Il trattamento per queste due classi di personaggi è diverso.
Con IFS=:
( :
essendo non un IFS spazi di carattere), un ingresso come :foo::bar::
sarebbe diviso in ""
, "foo"
, ""
, bar
e ""
(e un extra ""
con alcune implementazioni anche se questo non importa tranne read -a
). Mentre se lo sostituiamo :
con lo spazio, la divisione viene eseguita in solo foo
e bar
. Ciò significa che quelli iniziali e finali vengono ignorati e le loro sequenze vengono trattate come una sola. Ci sono regole aggiuntive quando si combinano caratteri bianchi e non bianchi $IFS
. Alcune implementazioni possono aggiungere / rimuovere il trattamento speciale raddoppiando i caratteri in IFS ( IFS=::
o IFS=' '
).
Quindi, qui, se non vogliamo che vengano eliminati i caratteri di spazi bianchi senza escape iniziali e finali, dobbiamo rimuovere quei caratteri di spazi bianchi IFS da IFS.
Anche con caratteri IFS non di spazi bianchi, se la riga di input contiene uno (e solo uno) di quei caratteri ed è l'ultimo carattere della riga (come IFS=: read -r word
su un input come foo:
) con shell POSIX (non zsh
né alcune pdksh
versioni), quell'input è considerato come una sola foo
parola perché in quelle shell, i caratteri $IFS
sono considerati come terminatori , quindi word
conterranno foo
, non foo:
.
Quindi, il modo canonico di leggere una riga di input con l' read
integrato è:
IFS= read -r line
(nota che per la maggior parte delle read
implementazioni, che funziona solo per le righe di testo in quanto il carattere NUL non è supportato tranne in zsh
).
L'uso della var=value cmd
sintassi assicura che IFS
sia impostato diversamente solo per la durata di quel cmd
comando.
Nota storica
Il read
builtin fu introdotto dalla shell Bourne e doveva già leggere le parole , non le righe. Ci sono alcune differenze importanti con le moderne shell POSIX.
La shell Bourne read
non supportava -r
un'opzione (che è stata introdotta dalla shell Korn), quindi non c'è modo di disabilitare l'elaborazione della barra rovesciata se non la pre-elaborazione dell'input con qualcosa di simile sed 's/\\/&&/g'
lì.
La shell Bourne non aveva quella nozione di due classi di personaggi (che di nuovo fu introdotta da ksh). Nella Bourne shell tutti i caratteri subiscono lo stesso trattamento IFS spazi caratteri fare a ksh, che è IFS=: read a b c
su un ingresso come foo::bar
assegnerebbe bar
a $b
, non la stringa vuota.
Nella shell Bourne, con:
var=value cmd
Se cmd
è un built-in (come read
è), var
rimane impostato su value
dopo che cmd
è terminato. Questo è particolarmente critico $IFS
perché nella shell Bourne $IFS
viene utilizzato per dividere tutto, non solo le espansioni. Inoltre, se si rimuove il carattere spazio dalla $IFS
shell Bourne, "$@"
non funziona più.
Nella shell Bourne, il reindirizzamento di un comando composto provoca l'esecuzione in una subshell (nelle prime versioni, anche cose come read var < file
o exec 3< file; read var <&3
non funzionavano), quindi era raro nella shell Bourne utilizzare read
per qualsiasi cosa tranne l'input dell'utente sul terminale (dove aveva senso la gestione della continuazione di linea)
Alcuni Unices (come HP / UX, ce n'è anche uno in util-linux
) hanno ancora un line
comando per leggere una riga di input (che era un comando UNIX standard fino alla specifica Single UNIX versione 2 ).
Questo è fondamentalmente lo stesso head -n 1
tranne per il fatto che legge un byte alla volta per assicurarsi che non legga più di una riga. Su quei sistemi, puoi fare:
line=`line`
Ovviamente, ciò significa generare un nuovo processo, eseguire un comando e leggere il suo output attraverso una pipe, quindi molto meno efficiente di quello di Ksh IFS= read -r line
, ma ancora molto più intuitivo.