Capire IFS


71

I seguenti thread su questo sito e StackOverflow sono stati utili per comprendere come IFSfunziona:

Ma ho ancora alcune brevi domande. Ho deciso di chiedere loro nello stesso post poiché penso che possa aiutare i futuri lettori migliori:

Q1. IFSè in genere discusso nel contesto della "divisione del campo". La divisione dei campi è uguale alla divisione delle parole ?

Q2: la specifica POSIX dice :

Se il valore di IFS è nullo, non deve essere eseguita alcuna divisione del campo.

L'impostazione è IFS=uguale IFSa null? È questo che si intende impostandolo empty stringanche su?

Q3: Nella specifica POSIX, ho letto quanto segue:

Se IFS non è impostato, la shell si comporterà come se il valore di IFS fosse <space>, <tab> and <newline>

Supponiamo di voler ripristinare il valore predefinito di IFS. Come lo faccio? (più specificamente, come posso fare riferimento a <tab>e <newline>?)

Q4: Infine, come sarebbe questo codice:

while IFS= read -r line
do    
    echo $line
done < /path_to_text_file

comportarsi se cambiamo la prima riga in

while read -r line # Use the default IFS value

o a:

while IFS=' ' read -r line

Risposte:


28
  1. Sì, sono uguali.
  2. Sì.
  3. In bash e conchiglie simili, potresti fare qualcosa del genere IFS=$' \t\n'. Altrimenti, è possibile inserire i codici di controllo letterali utilizzando [space] CTRL+V [tab] CTRL+V [enter]. Se si prevede di eseguire questa operazione, tuttavia, è meglio utilizzare un'altra variabile per memorizzare temporaneamente il vecchio IFSvalore, quindi ripristinarlo in seguito (o sovrascriverlo temporaneamente per un comando utilizzando la var=foo commandsintassi).
    • Il primo frammento di codice inserirà l'intera riga letta, alla lettera $line, poiché non vi sono separatori di campi per i quali eseguire la suddivisione delle parole. Tenere presente tuttavia che, poiché molte shell utilizzano cstring per archiviare le stringhe, la prima istanza di un NUL può comunque causare la sua prematura interruzione.
    • Il secondo frammento di codice potrebbe non inserire una copia esatta dell'input $line. Ad esempio, se sono presenti più separatori di campo consecutivi, verranno trasformati in un'unica istanza del primo elemento. Questo è spesso riconosciuto come perdita di spazio bianco circostante.
    • Il terzo frammento di codice farà lo stesso del secondo, tranne per il fatto che si dividerà solo su uno spazio (non sul solito spazio, tabulazione o nuova riga).

3
La risposta a Q2 è sbagliata: uno vuoto IFSe uno non impostato IFSsono molto diversi. La risposta a Q4 è in parte errata: i separatori interni non vengono toccati qui, ma solo quelli iniziali e finali.
Gilles 'SO- smetti di essere malvagio' il

3
@Gilles: Nel secondo trimestre nessuna delle tre denominazioni indicate si riferisce a un non impostato IFS, tutte significano IFS=.
Stéphane Gimenez,

@Gilles Nel secondo trimestre, non ho mai detto che fossero uguali. E separatori interni sono toccati, come illustrato di seguito: IFS=' ' ; foo=( bar baz qux ) ; echo "${#foo[@]}". (Ehm, cosa? Dovrebbero esserci più delimitatori di spazio, il motore SO continua a rimuoverli).
Chris Down,

2
@ StéphaneGimenez, Chris: Oh, giusto, scusa per il secondo trimestre, ho letto male la domanda. Per Q4, stiamo parlando read; l'ultima variabile prende tutto ciò che rimane tranne l'ultimo separatore e lascia all'interno i separatori interni.
Gilles 'SO- smetti di essere malvagio' il

1
Gilles è parzialmente corretto riguardo agli spazi che non vengono rimossi dalla lettura. Leggi la mia risposta per i dettagli.

22

Q1: Sì. "Suddivisione dei campi" e "suddivisione delle parole" sono due termini per lo stesso concetto.

Q2: Sì. Se IFSnon è impostato (ovvero dopo unset IFS), equivale a IFSessere impostato su $' \t\n'(uno spazio, una scheda e una nuova riga). Se IFSè impostato su un valore vuoto (ecco cosa significa "null" qui) (cioè dopo IFS=o IFS=''o IFS=""), non viene eseguita alcuna suddivisione del campo (e $*, che normalmente utilizza il primo carattere di $IFS, usa un carattere spazio).

Q3: se si desidera avere il IFScomportamento predefinito , è possibile utilizzare unset IFS. Se si desidera impostare IFSesplicitamente questo valore predefinito, è possibile inserire lo spazio dei caratteri letterali, tab, newline tra virgolette singole. In ksh93, bash o zsh, puoi usare IFS=$' \t\n'. Portabilmente, se si desidera evitare di avere un carattere di tabulazione letterale nel file sorgente, è possibile utilizzare

IFS=" $(echo t | tr t \\t)
"

Q4: con IFSimpostato su un valore vuoto, read -r lineimposta linesu tutta la riga tranne la sua nuova riga finale. Con IFS=" ", gli spazi all'inizio e alla fine della linea vengono tagliati. Con il valore predefinito di IFS, le schede e gli spazi vengono tagliati.


2
Q2 è parzialmente sbagliato. Se IFS è vuoto, "$ *" viene unito senza separatori. (per $@, ci sono alcune variazioni tra shell in contesti non di lista come IFS=; var=$@). Va notato che quando IFS è vuoto, nessuna suddivisione delle parole viene eseguita, ma $ var si espande in nessun argomento invece di un argomento vuoto quando $ var è vuoto e si applica ancora il globbing, quindi è ancora necessario citare le variabili (anche se si disabilita il globbing)
Stéphane Chazelas,

13

Q1. Divisione del campo.

La divisione dei campi è uguale alla divisione delle parole?

Sì, entrambi indicano la stessa idea.

Q2: quando IFS è nullo ?

L'impostazione è IFS=''uguale a null, uguale anche a una stringa vuota?

Sì, tutti e tre significano lo stesso: non deve essere eseguita alcuna divisione di campi / parole. Inoltre, ciò influisce sui campi di stampa (come nel caso di echo "$*") tutti i campi verranno concatenati insieme senza spazio.

Q3: (parte a) Disinserire IFS.

Nella specifica POSIX, ho letto quanto segue :

Se IFS non è impostato, la shell si comporterà come se il valore di IFS fosse <space><tab> <newline> .

Che è esattamente equivalente a:

Con un unset IFS, la shell si comporterà come se IFS fosse di default.

Ciò significa che la "suddivisione del campo" sarà esattamente la stessa con un valore IFS predefinito o non impostata.
Ciò NON significa che IFS funzionerà allo stesso modo in tutte le condizioni. Essendo più specifico, l'esecuzione OldIFS=$IFSimposterà var OldIFSsu null , non sul valore predefinito. E provare a ripristinare IFS, in questo modo, IFS=OldIFSrenderà IFS su null, non lo manterrà come prima. Attento !!.

Q3: (parte b) Ripristina IFS.

Come posso ripristinare il valore di IFS sul valore predefinito. Supponiamo di voler ripristinare il valore predefinito di IFS. Come lo faccio? (più specificamente, come faccio a fare riferimento a <tab> e <newline> ?)

Per zsh, ksh e bash (AFAIK), IFS potrebbe essere impostato sul valore predefinito come:

IFS=$' \t\n'        # works with zsh, ksh, bash.

Fatto, non devi leggere nient'altro.

Ma se è necessario reimpostare IFS per sh, potrebbe diventare complesso.

Diamo un'occhiata dal più semplice da completare senza inconvenienti (tranne la complessità).

1.- Disinserire IFS.

Potremmo solo unset IFS(Leggi Q3 parte a, sopra.).

2.- Scambia caratteri.

Per ovviare a questo problema, scambiare il valore di tab e newline rende più semplice impostare il valore di IFS e quindi funziona in modo equivalente.

Impostare IFS su <spazio><newline> <tab> :

sh -c 'IFS=$(echo " \n\t"); printf "%s" "$IFS"|xxd'      # Works.

3.- Un semplice? soluzione:

Se ci sono script figlio che richiedono che IFS sia impostato correttamente, puoi sempre scrivere manualmente:

IFS ='   
'

Dove era la sequenza digitata manualmente:, IFS='spacetabnewline'sequenza che è stata effettivamente digitata correttamente sopra (Se è necessario confermare, modificare questa risposta). Ma una copia / incolla dal tuo browser si interromperà perché il browser comprime / nasconde lo spazio bianco. Rende difficile condividere il codice come scritto sopra.

4.- Soluzione completa.

Scrivere un codice che può essere copiato in modo sicuro di solito comporta inequivocabili escape stampabili.

Abbiamo bisogno di un codice che "produca" il valore atteso. Ma, anche se concettualmente corretto, questo codice NON imposta un trailing \n:

sh -c 'IFS=$(echo " \t\n"); printf "%s" "$IFS"|xxd'      # wrong.

Ciò accade perché, sotto la maggior parte delle shell, tutte le nuove righe finali $(...)o le `...`sostituzioni di comandi vengono rimosse durante l'espansione.

Dobbiamo usare un trucco per sh:

sh -c 'IFS="$(printf " \t\nx")"; IFS="${IFS%x}"; printf "$IFS"|xxd'  # Correct.

Un modo alternativo potrebbe essere quello di impostare IFS come valore di ambiente da bash (ad esempio) e quindi chiamare sh (le versioni di esso che accettano IFS da impostare tramite l'ambiente), in quanto:

env IFS=$' \t\n' sh -c 'printf "%s" "$IFS"|xxd'

In breve, sh rende il ripristino di IFS predefinito un'avventura piuttosto strana.

Q4: nel codice attuale:

Infine, come sarebbe questo codice:

while IFS= read -r line
do
    echo $line
done < /path_to_text_file

comportarsi se cambiamo la prima riga in

while read -r line # Use the default IFS value

o a:

while IFS=' ' read -r line

Primo: non so se il echo $line(con il var NON citato) sia presente su Porpouse o no. Introduce un secondo livello di "divisione del campo" che la lettura non ha. Quindi risponderò ad entrambi. :)

Con questo codice (in modo da poter confermare). Avrai bisogno dell'utile xxd :

#!/bin/ksh
# Correctly set IFS as described above.
defIFS="$(printf " \t\nx")"; defIFS="${defIFS%x}";
IFS="$defIFS"
printf "IFS value: "
printf "%s" "$IFS"| xxd -p

a='   bar   baz   quz   '; l="${#a}"
printf "var value          : %${l}s-" "$a" ; printf "%s\n" "$a" | xxd -p

printf "%s\n" "$a" | while IFS='x' read -r line; do
    printf "IFS --x--          : %${l}s-" "$line" ;
    printf "%s" "$line" |xxd -p; done;

printf 'Values      quoted :\n' ""  # With values quoted:
printf "%s\n" "$a" | while IFS='' read -r line; do
    printf "IFS null    quoted : %${l}s-" "$line" ;
    printf "%s" "$line" |xxd -p; done;

printf "%s\n" "$a" | while IFS="$defIFS" read -r line; do
    printf "IFS default quoted : %${l}s-" "$line" ;
    printf "%s" "$line" |xxd -p; done;

unset IFS; printf "%s\n" "$a" | while read -r line; do
    printf "IFS unset   quoted : %${l}s-" "$line" ;
    printf "%s" "$line" |xxd -p; done;
    IFS="$defIFS"   # set IFS back to default.

printf "%s\n" "$a" | while IFS=' ' read -r line; do
    printf "IFS space   quoted : %${l}s-" "$line" ;
    printf "%s" "$line" |xxd -p; done;

printf '%s\n' "Values unquoted :"   # Now with values unquoted:
printf "%s\n" "$a" | while IFS='x' read -r line; do
    printf "IFS --x-- unquoted : "
    printf "%s, " $line; printf "%s," $line |xxd -p; done

printf "%s\n" "$a" | while IFS='' read -r line; do
    printf "IFS null  unquoted : ";
    printf "%s, " $line; printf "%s," $line |xxd -p; done

printf "%s\n" "$a" | while IFS="$defIFS" read -r line; do
    printf "IFS defau unquoted : ";
    printf "%s, " $line; printf "%s," $line |xxd -p; done

unset IFS; printf "%s\n" "$a" | while read -r line; do
    printf "IFS unset unquoted : ";
    printf "%s, " $line; printf "%s," $line |xxd -p; done
    IFS="$defIFS"   # set IFS back to default.

printf "%s\n" "$a" | while IFS=' ' read -r line; do
    printf "IFS space unquoted : ";
    printf "%s, " $line; printf "%s," $line |xxd -p; done

Ottengo:

$ ./stackexchange-Understanding-IFS.sh
IFS value: 20090a
var value          :    bar   baz   quz   -20202062617220202062617a20202071757a2020200a
IFS --x--          :    bar   baz   quz   -20202062617220202062617a20202071757a202020
Values      quoted :
IFS null    quoted :    bar   baz   quz   -20202062617220202062617a20202071757a202020
IFS default quoted :       bar   baz   quz-62617220202062617a20202071757a
IFS unset   quoted :       bar   baz   quz-62617220202062617a20202071757a
IFS space   quoted :       bar   baz   quz-62617220202062617a20202071757a
Values unquoted :
IFS --x-- unquoted : bar, baz, quz, 6261722c62617a2c71757a2c
IFS null  unquoted : bar, baz, quz, 6261722c62617a2c71757a2c
IFS defau unquoted : bar, baz, quz, 6261722c62617a2c71757a2c
IFS unset unquoted : bar, baz, quz, 6261722c62617a2c71757a2c
IFS space unquoted : bar, baz, quz, 6261722c62617a2c71757a2c

Il primo valore è solo il valore corretto di IFS='spacetabnewline'

La riga successiva è rappresentata da tutti i valori esadecimali di var $ae una nuova riga '0a' alla fine in quanto verrà data a ciascun comando di lettura.

La riga successiva, per la quale IFS è nulla, non esegue alcuna "divisione del campo", ma la nuova riga viene rimossa (come previsto).

Le tre righe successive, poiché IFS contiene uno spazio, rimuovono gli spazi iniziali e impostano la var line sul saldo rimanente.

Le ultime quattro righe mostrano cosa farà una variabile non quotata. I valori verranno suddivisi sui (diversi) spazi e verranno stampati come:bar,baz,qux,


4

unset IFS cancella IFS, anche se da allora in poi si presume che sia "\ t \ n":

$ echo "'$IFS'"
'   
'
$ IFS=""
$ echo "'$IFS'"
''
$ unset IFS
$ echo "'$IFS'"
''
$ IFS=$' \t\n'
$ echo "'$IFS'"
'   
'
$

Testato sulle versioni bash 4.2.45 e 3.2.25 con lo stesso comportamento.


La domanda e la documentazione collegata non parlano unsetdi IFS, come spiegato nei commenti della risposta accettata qui.
ILMostro_7,
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.