Perché `while IFS = read` viene usato così spesso, invece di` IFS =; mentre leggi..`?


81

Sembra che la pratica normale metterebbe l'impostazione di IFS al di fuori del ciclo while per non ripetere l'impostazione per ogni iterazione ... È solo uno stile abituale di "scimmia vedi, scimmia fa", come è stato per questa scimmia fino a quando Ho letto man read , o mi sto perdendo una trappola sottile (o palesemente ovvia) qui?

Risposte:


82

La trappola è quella

IFS=; while read..

imposta l'ambiente IFSper l'intera shell al di fuori del ciclo, mentre

while IFS= read

lo ridefinisce solo per l' readinvocazione (tranne nella shell Bourne). Puoi verificarlo facendo un ciclo simile

while IFS= read xxx; ... done

poi dopo tale ciclo, echo "blabalbla $IFS ooooooo"stampa

blabalbla
 ooooooo

mentre dopo

IFS=; read xxx; ... done

i IFS soggiorni ridefiniti: ora echo "blabalbla $IFS ooooooo"stampa

blabalbla  ooooooo

Quindi, se si utilizza il secondo modulo, si deve ricordare di reimpostare: IFS=$' \t\n'.


La seconda parte di questa domanda è stata unita qui , quindi ho rimosso la relativa risposta da qui.


Va bene, sembra che una potenziale 'trappola' sia quella di trascurare di ripristinare l'IFS esterno ... Ma mi chiedo se ci sia anche qualcos'altro in corso ... Sto testando le cose qui, piuttosto febbrilmente, e ho si noti che l'impostazione di IFS nell'elenco dei comandi while si comporta in modo diverso da qute, a seconda che sia seguita o meno da due punti. Non capisco questo comportamento (ancora), e ora mi chiedo se ci sia qualche considerazione speciale a questo livello ... ad es. while IFS=X readnon si divide X, ma while IFS=X; read...
Peter

(Si intende semi colon, giusto?) La seconda whilenon ha molto senso - la condizione per while fini in quel punto e virgola, quindi non c'è ciclo attuale ... readdiventa solo il primo comando all'interno del ciclo di un elemento ... O no ? E doallora ...?
rozcietrzewiacz,

1
No, aspetta - hai ragione, puoi avere diversi comandi nella whilecondizione (prima do).
rozcietrzewiacz,

Oh .. sicuramente, puoi averli ... come hai capito ... ma sembra che non piacciano i punti e virgola ... (e il ciclo continuerà a scorrere all'infinito fino a quando l'ultimo comando non restituisce un non -zero codice di uscita) ... ora mi chiedo se la trappola si trovi interamente in un altro settore; quello di capire come funziona la lista dei comandi di while , ad es. perché IFS=funziona, ma IFS=Xnon ... (o forse non ci ho pensato per un po '.. pausa caffè necessaria :)
Peter.O

1
$ rozcietrzewiacz .. Oops ... Non avevo notato il tuo aggiornamento, quando ho spostato il mio aggiornamento (come menzionato nel commento precedente) .. Sembra interessante e sta iniziando a dare un senso ... ma anche per una notte- uccello come me, è estremamente tardi ... (Ho appena sentito gli uccelli del mattino:) ... Detto questo, mi sono radunato un po 'e ho letto i tuoi esempi ... Penso di averlo, in realtà io' Sono sicuro che ce l'hai, ma devo dormire :) ... Questo è quasi un Eureka! momento ... grazie
Peter.O

45

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.


4
Grazie Gilles .. una visita guidata molto bella .. (volevi dire "set -f"?) .... Ora, per il lettore, per ribadire ciò che è già stato detto, vorrei sottolineare il problema che aveva io lo guardo nel modo sbagliato. Innanzitutto è il fatto che il costrutto while IFS= read(senza un punto e virgola dopo =) non è una forma speciale di whileo di IFSo di read.. Il costrutto è generico: vale a dire. anyvar=anyvalue anycommand. La mancanza di ;after setting anyvarrende l'ambito di anyvar local a anycommand.. Il ciclo while - do / done non è correlato al 100% all'ambito locale di any_var.
Peter

3

A parte IFSle differenze di ambito (già chiarite) tra while IFS='' read, IFS=''; while reade while IFS=''; readidiomi (per comando vs script / IFSambito variabile a livello di shell ), la lezione da portare a casa è che si perdono gli spazi iniziali e finali di una linea di input se la variabile IFS è impostato su (contiene un) spazio.

Ciò può avere conseguenze piuttosto gravi se i percorsi dei file vengono elaborati.

Pertanto, impostare la variabile IFS su una stringa vuota è tutt'altro che una cattiva idea poiché garantisce che lo spazio bianco iniziale e finale di una linea non venga rimosso.

Vedi anche: Bash, leggi riga per riga da file, con IFS

(
shopt -s nullglob
touch '  file with spaces   '
IFS=$' \t\n' read -r file <<<"$(printf '%s' *file*with*spaces*)"
ls -l "$file"
IFS='' read -r file <<<"$(printf '%s' *file*with*spaces*)"
ls -l "$file"
)

+1 dimostrazione eccellente, pulizia dopo con 'rm * file * con * spazi *'
amdn

0

Ispirato dalla risposta di Yuzem

Se vuoi impostare IFSun personaggio reale, questo ha funzionato per me

iconv -f cp1252 zapni.tv.php | while IFS='#' read -d'#' line
do
  echo "$line"
done
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.