In `while IFS = read..`, perché IFS non ha alcun effetto?


12

Potrei avere qualcosa di assolutamente sbagliato, ma mi sembra convincente che l'impostazione di IFS come uno dei comandi nell'elenco pre-do / done non abbia assolutamente alcun effetto.
L'IFS esterno (al di fuori del whilecostrutto) prevale in tutti gli esempi mostrati nello script seguente.

Cosa sta succedendo qui? Ho avuto un'idea sbagliata di ciò che IFS fa in questa situazione? Mi aspettavo che i risultati della suddivisione in array fossero quelli mostrati nella colonna "prevista".


#!/bin/bash
xifs() { echo -n "$(echo -n "$IFS" | xxd -p)"; } # allow for null $IFS 
show() { x=($1) 
         echo -ne "  (${#x[@]})\t |"
         for ((j=0;j<${#x[@]};j++)); do 
           echo -n "${x[j]}|"
         done
         echo -ne "\t"
         xifs "$IFS"; echo
}
data="a  b   c"
echo -e "-----   --  -- \t --------\tactual"
echo -e "outside        \t  IFS    \tinside" 
echo -e "loop           \t Field   \tloop" 
echo -e "IFS     NR  NF \t Split   \tIFS (actual)" 
echo -e "-----   --  -- \t --------\t-----"
IFS=$' \t\n'; xifs "$IFS"; echo "$data" | while         read; do echo -ne '\t 1'; show "$REPLY"; done 
IFS=$' \t\n'; xifs "$IFS"; echo "$data" | while IFS=    read; do echo -ne '\t 2'; show "$REPLY"; done 
IFS=$' \t\n'; xifs "$IFS"; echo "$data" | while IFS=b   read; do echo -ne '\t 3'; show "$REPLY"; done
IFS=" ";      xifs "$IFS"; echo "$data" | while         read; do echo -ne '\t 4'; show "$REPLY"; done 
IFS=" ";      xifs "$IFS"; echo "$data" | while IFS=    read; do echo -ne '\t 5'; show "$REPLY"; done 
IFS=" ";      xifs "$IFS"; echo "$data" | while IFS=b   read; do echo -ne '\t 6'; show "$REPLY"; done
IFS=;         xifs "$IFS"; echo "$data" | while         read; do echo -ne '\t 7'; show "$REPLY"; done 
IFS=;         xifs "$IFS"; echo "$data" | while IFS=" " read; do echo -ne '\t 8'; show "$REPLY"; done 
IFS=;         xifs "$IFS"; echo "$data" | while IFS=b   read; do echo -ne '\t 9'; show "$REPLY"; done
IFS=b;        xifs "$IFS"; echo "$data" | while IFS=    read; do echo -ne '\t10'; show "$REPLY"; done
IFS=b;        xifs "$IFS"; echo "$data" | while IFS=" " read; do echo -ne '\t11'; show "$REPLY"; done
echo -e "-----   --  -- \t --------\t-----"

Produzione:

-----   --  --   --------       actual   
outside           IFS           inside                assigned   
loop             Field          loop    #              inner
IFS     NR  NF   Split          IFS     #  expected    IFS
-----   --  --   --------       -----   #  ---------  --------
20090a   1  (3)  |a|b|c|        20090a  #                              
20090a   2  (3)  |a|b|c|        20090a  #  |a  b   c|  IFS=
20090a   3  (3)  |a|b|c|        20090a  #  |a  |   c|  IFS=b
20       4  (3)  |a|b|c|        20      #                          
20       5  (3)  |a|b|c|        20      #  |a  b   c   IFS=
20       6  (3)  |a|b|c|        20      #  |a  |   c|  IFS=b
         7  (1)  |a  b   c|             #                          
         8  (1)  |a  b   c|             #  |a|b|c|     IFS=" "
         9  (1)  |a  b   c|             #  |a  |   c|  IFS=b
62      10  (2)  |a  |   c|     62      #  |a  b   c|  IFS=
62      11  (2)  |a  |   c|     62      #  |a|b|c|     IFS=" "
-----   --  --   --------       -----      ---------   -------                        

Risposte:


17

(Scusa, lunga spiegazione)

Sì, la IFSvariabile in while IFS=" " read; do …non ha alcun effetto sul resto del codice.

Precisiamo innanzitutto che la riga di comando della shell presenta due diversi tipi di variabili:

  • variabili di shell (che esistono solo all'interno di una shell e sono locali nella shell)
  • variabili di ambiente, che esistono per ogni processo. Questi sono di solito preservati fork()e exec(), quindi, i processi figlio li ereditano.

Quando si chiama un comando con:

  A=foo B=bar command

il comando viene eseguito all'interno di un ambiente in cui la variabile (ambiente) Aè impostata fooe Bimpostata su bar. Ma con questa riga di comando, le variabili della shell corrente Ae Bvengono lasciate invariate .

Questo è diverso da:

A=foo; B=bar; command

Qui, shell variabili Ae Bsono definiti e il comando viene eseguito senza variabili di ambiente Ae Bdefinito. I valori di Ae Bsono inaccessibili da command.

Tuttavia, se sono presenti alcune variabili di shell export, le variabili di ambiente corrispondenti sono sincronizzate con le rispettive variabili di shell. Esempio:

export A
export B
A=foo; B=bar; command

Con questo codice, sia le variabili shell che le variabili di ambiente shell sono impostate su fooe bar. Poiché le variabili di ambiente sono ereditate da sottoprocessi, commandsaranno in grado di accedere ai loro valori.

Per tornare alla domanda originale, in:

IFS='a' read

readè interessato solo . E infatti, in questo caso, readnon interessa il valore della IFSvariabile. Si utilizza IFSsolo quando chiedi alla linea di essere divisa (e memorizzata in più variabili), come in:

echo "a :  b :    c" | IFS=":" read i j k; \
    printf "i is '%s', j is '%s', k is '%s'" "$i" "$j" "$k"

IFSnon viene utilizzato da readmeno che non venga chiamato con argomenti. ( Modifica: questo non è esattamente vero: i caratteri di spazi bianchi, ovvero spazio e tabulazione, presenti in IFSvengono sempre ignorati all'inizio / alla fine della riga di input.)


Che grande spiegazione! È così semplice! Sono stato confuso da quella sintassi 'no semi-colon' per mesi; ed è semplicemente un caso che significa una variabile locale! .. rozcietrzewiacz mi ha aperto la strada (alla grande) nell'altra domanda ... e hai appena messo la ciliegina sulla torta ... per tutta la notte su questo, e ne è valsa sicuramente la pena per risposte così buone e chiare! .. Grazie ..
Peter.O

Uhm. Ho dovuto leggere quel commento di modifica più volte prima di averlo - intendi dire che i caratteri di spazio bianco che sono presenti $IFSvengono rimossi all'inizio / alla fine della riga di input, presumo? (
Ecco


Il valore di IFS è importante anche quando si legge una singola variabile, poiché la shell continua a dividere le parole sull'input. Così, per esempio, digitando i caratteri a<tab>bin read varprovocherà var avere il valore a<space>b, ma se invece si ha IFS='<newline>' read varallora il valore di var sarà a<tab>b.
John Hascall,

8

In parole semplici, devi leggere più di una variabile alla volta affinché il IFS=<something> read ...costrutto abbia un effetto visibile nei tuoi esempi 1 .

Ti manca l'ambito di readnegli esempi. Non vi è alcuna modifica di IFS all'interno del loop nei casi di test. Consentitemi di indicare esattamente, dove ha effetto il secondo IFS in ciascuna delle vostre linee:

 IFS=$' \t\n'; xifs "$IFS"; echo "$data" | while IFS=b   read; do echo ...
                                                      ^      ^
                                                      |      |
                                          from here --'       `- to here :)

È proprio come con qualsiasi programma eseguito nella shell. La variabile che (ri) definisci nella riga di comando influenza l'esecuzione del programma. E solo quello (dato che non esporti). Pertanto, per utilizzare un ridefinito IFSin tale riga, dovresti chiedere readdi assegnare valori a più di una variabile . Dai un'occhiata a questi esempi:

 $ data="a  b   c"
 $ echo "$data" | while           read A B C; do echo \|$A\|$B\|\|$C\|; done
 |a|b||c|
 $ echo "$data" | while IFS=      read A B C; do echo \|$A\|$B\|\|$C\|; done
 |a b c||||
 $ echo "$data" | while IFS='a'   read A B C; do echo \|$A\|$B\|\|$C\|; done
 || b c|||
 $ echo "$data" | while IFS='ab'  read A B C; do echo \|$A\|$B\|\|$C\|; done
 || || c|

1 Come ho appena appreso da Gilles , potrebbe esserci effettivamente un vantaggio nell'impostazione IFS=''(vuoto) quando si legge solo un campo: evita il troncamento degli spazi bianchi all'inizio della riga.


Bene .. Grazie ... L'ho preso questa volta .. e adoro il tuo schizzo :)
Peter.O

OK, ora ho letto il tuo commento che vede che non hai notato la mia risposta a quel problema nell'altra domanda. Forse potresti semplicemente ripristinare l'altro ed eliminare questo, dal momento che è davvero un problema generale?
rozcietrzewiacz,

Sì, le due domande hanno un tema correlato, ma il titolo dell'altra è "Perché viene IFS= readutilizzato preferibilmente per reimpostare la variabile di ambiente IFS". Non avevo consapevolezza, quindi, che le variabili locali potessero essere impostate dal chiamante di un comando. Questa era la risposta a quella domanda. Si è evoluto ulteriormente per affrontare il punto principale di questa domanda, ma quando me ne sono reso conto, avevo già fatto questa domanda ... Forse le due domande sono simili come due seddomande, quindi mi sento di tenerlo così ... Altri titoli per googler su google.
Peter
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.