Dans `while IFS = read..`, pourquoi IFS n'a aucun effet?

12

J'ai peut-être quelque chose d'absolument erroné, mais cela me semble convaincant, que le fait de définir IFS comme l'une des commandes de la liste pre-do / done n'a absolument aucun effet.
L'IFS externe (en dehors de la whileconstruction) prévaut dans tous les exemples illustrés dans le script ci-dessous.

Que se passe t-il ici? Ai-je une mauvaise idée de ce que fait IFS dans cette situation? Je m'attendais à ce que les résultats du tableau soient divisés comme indiqué dans la colonne "attendu".


#!/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-----"

Production:

-----   --  --   --------       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=" "
-----   --  --   --------       -----      ---------   -------                        
Peter.O
la source

Réponses:

17

(Désolé, longue explication)

Oui, la IFSvariable in while IFS=" " read; do …n'a aucun effet sur le reste du code.

Précisons d'abord que la ligne de commande du shell comporte deux types de variables différents:

  • Variables du shell (qui n'existent qu'au sein d'un shell et sont locales au shell)
  • variables d'environnement, qui existent pour chaque processus. Ceux -ci sont généralement conservés sur fork()et exec(), si les processus enfants les Hériter.

Lorsque vous appelez une commande avec:

  A=foo B=bar command

la commande est exécutée dans un environnement où la variable (environnement) Aest définie sur fooet Best définie sur bar. Mais avec cette ligne de commande, les variables shell actuelles Aet Brestent inchangées .

C'est différent de:

A=foo; B=bar; command

Ici, les variables shell Aet Bsont définies et la commande est exécutée sans variables d'environnement Aet Bdéfinies. Les valeurs de Aet Bsont inaccessibles command.

Cependant, si certaines variables shell sont export-ed, les variables d'environnement correspondantes sont synchronisées avec leurs variables shell respectives. Exemple:

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

Avec ce code, les variables shell et les variables d' environnement shell sont définies sur fooet bar. Étant donné que les variables d'environnement sont héritées par des sous-processus, commandelles pourront accéder à leurs valeurs.

Pour revenir à votre question d'origine, en:

IFS='a' read

seul readest affecté. Et en fait, dans ce cas, readne se soucie pas de la valeur de la IFSvariable. Il utilise IFSuniquement lorsque vous demandez que la ligne soit divisée (et stockée dans plusieurs variables), comme dans:

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

IFSn'est pas utilisé par readsauf s'il est appelé avec des arguments. ( Modifier: ce n'est pas tout à fait vrai: les caractères d'espacement, c'est-à-dire l'espace et la tabulation, présents dans IFSsont toujours ignorés au début / à la fin de la ligne d'entrée.)

Stéphane Gimenez
la source
Quelle belle explication! C'est tellement simple! Cela fait des mois que je suis perplexe devant cette syntaxe «sans point-virgule» ; et c'est tout simplement un cas qui signifie une variable locale! .. rozcietrzewiacz m'a ouvert la voie (grand temps) dans l' autre question ... et vous venez de mettre la cerise sur le gâteau ... j'ai été toute la nuit sur celui-ci, et cela en a certainement valu la peine pour des réponses aussi bonnes et claires! .. Merci ..
Peter.O
Uhm. J'ai dû lire ce commentaire d'édition plusieurs fois avant de l'obtenir - vous voulez dire que les caractères blancs qui sont présents dans $IFSsont supprimés au début / à la fin de la ligne d'entrée, je suppose? (C'est ainsi que cela fonctionne.)
zrajm
Veuillez jeter un œil à ceci: unix.stackexchange.com/questions/382963/…
La valeur de IFS est importante même lors de la lecture d'une seule variable, car le shell effectue toujours la division des mots sur l'entrée. Ainsi , par exemple, en tapant les caractères a<tab>ben read varse traduira par var ayant la valeur a<space>b, mais si au contraire , vous avez IFS='<newline>' read varalors la valeur de var sera a<tab>b.
John Hascall
8

En termes simples, vous devez lire plusieurs variables à la fois pour que la IFS=<something> read ...construction ait un effet visible dans vos exemples 1 .

Vous manquez la portée de readdans les exemples. Il n'y a aucune modification d'IFS à l'intérieur de la boucle dans vos cas de test. Permettez-moi de vous indiquer exactement où le deuxième IFS a son effet dans chacune de vos lignes:

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

C'est comme pour tout programme exécuté dans le shell. La variable que vous (re) définissez sur la ligne de commande affecte l'exécution du programme. Et seulement cela (puisque vous n'exportez pas). Par conséquent, pour faire un usage de redéfini IFSdans une telle ligne, vous devez demander readd'attribuer des valeurs à plusieurs variables . Jetez un œil à ces exemples:

 $ 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 Comme je viens de l'apprendre de Gilles , il pourrait en fait être avantageux de définir IFS=''(vide) lors de la lecture d'un seul champ: cela évite la troncature des espaces au début de la ligne.

rozcietrzewiacz
la source
Bon .. Merci ... Je l'ai eu cette fois .. et j'adore ton croquis :)
Peter.O
OK, maintenant j'ai lu votre commentaire qui montre que vous n'avez pas remarqué ma réponse à ce problème dans l'autre question. Peut-être pourriez-vous simplement revenir sur l'autre et le supprimer, car il s'agit vraiment d'un problème général?
rozcietrzewiacz
Oui, les deux questions ont un thème connexe, mais le titre de l'autre est "Pourquoi est-il IFS= readutilisé de préférence à simplement réinitialiser la variable d'environnement IFS". Je ne savais pas, alors, que les variables locales pouvaient être définies par l'appelant d'une commande. C'était la réponse à cette question. Il a évolué davantage pour aborder le point principal de cette question, mais au moment où je me suis rendu compte que j'avais déjà posé cette question ... Peut-être que les deux questions sont aussi similaires que deux sedquestions, donc j'ai l'impression de le garder tel quel ... Plus de titres pour les googleurs sur google.
Peter.O