Pourquoi est-ce que `tant que IFS = read` est utilisé si souvent, au lieu de` IFS =; pendant la lecture..`?

81

Il semble que la pratique normale place le paramètre IFS en dehors de la boucle while afin de ne pas le répéter à chaque itération ... S'agit-il simplement d'un style "singe see, monkey do" habituel, comme il l'a été jusqu'à ce singe J'ai lu l' homme lu ou ai-je oublié un piège subtil (ou manifestement évident) ici?

Peter.O
la source

Réponses:

82

Le piège est que

IFS=; while read..

définit le IFSpour tout l'environnement shell en dehors de la boucle, alors que

while IFS= read

le redéfinit uniquement pour l' readinvocation (sauf dans le shell Bourne). Vous pouvez vérifier que faire une boucle comme

while IFS= read xxx; ... done

puis après cette boucle, echo "blabalbla $IFS ooooooo"imprime

blabalbla
 ooooooo

alors qu'après

IFS=; read xxx; ... done

les IFS séjours redéfinis: echo "blabalbla $IFS ooooooo"imprime maintenant

blabalbla  ooooooo

Donc , si vous utilisez la deuxième forme, vous devez vous rappeler de réinitialiser: IFS=$' \t\n'.


La deuxième partie de cette question a été fusionnée ici . J'ai donc supprimé la réponse correspondante d'ici.

rozcietrzewiacz
la source
D'accord, il semble qu'un 'piège' potentiel est de négliger de réinitialiser l'IFS externe ... Mais je me demande s'il y a aussi autre chose à pied ... J'ai testé des choses ici, assez fébrilement, et j'ai Notez que la définition de IFS dans la liste de commandes de while se comporte différemment, qu’elle soit suivie ou non de deux points. Je ne comprends pas (encore) ce comportement, et je me demande maintenant si une considération particulière est en jeu à ce niveau ... par exemple. while IFS=X readne se sépare pas X, mais while IFS=X; read...
Peter.O
(Vous vouliez dire demi colon, non?) Le second whilene fait pas beaucoup de sens - la condition pour while extrémités à ce point - virgule, donc il n'y a pas de boucle réelle ... readdevient juste la première commande dans la boucle d' un élément ou non ... ? Qu'en est-il du domoment ..?
rozcietrzewiacz
1
Non, attendez - vous avez raison, vous pouvez avoir plusieurs commandes dans la whilecondition (avant do).
rozcietrzewiacz
Oh .. bien sûr, vous pouvez les avoir ... comme vous l'avez compris ... mais ils ne semblent pas aimer le point-virgule ... (et la boucle continuera à boucler ad-infinitum jusqu'à ce que la dernière commande renvoie un non -Zéro code de sortie) ... Je me demande maintenant si le piège repose entièrement dans un secteur différent; celle de comprendre comment fonctionne la liste de commandes de while , par exemple. pourquoi fonctionne-t-il IFS=, mais IFS=Xne fonctionne pas ... (ou peut-être que je suis sur le même sujet depuis un moment ... une pause-café est nécessaire :)
Peter.O
1
$ rozcietrzewiacz .. Oups ... Je n'avais pas remarqué votre mise à jour, lorsque j'ai déplacé ma mise à jour (comme mentionné dans le commentaire précédent) .. Cela semble intéressant, et cela commence à avoir un sens ... mais même pour une nuit- Oiseau comme moi, il est extrêmement tard ... (je viens d'entendre les oiseaux du matin:) ... Cela dit, je me suis un peu rallié et j'ai lu vos exemples ... Je pense que je l'ai, en fait je ' Je suis sûr que vous l'avez, mais je dois dormir :) ... C'est presque un Eureka! moment ... merci
Peter.O
45

Regardons un exemple, avec du texte d'entrée soigneusement conçu:

text=' hello  world\
foo\bar'

C'est deux lignes, la première commençant par un espace et se terminant par une barre oblique inverse. Tout d’abord, regardons ce qui se passe sans précaution read(mais en utilisant printf '%s\n' "$text"pour imprimer soigneusement $textsans aucun risque d’agrandissement). (Ci-dessous, $ ‌l'invite du shell.)

$ printf '%s\n' "$text" |
  while read line; do printf '%s\n' "[$line]"; done
[hello worldfoobar]

reada mangé les barres obliques inverses: la barre oblique inversée-newline a pour effet d’ignorer la nouvelle ligne et la barre oblique inversée-tout ignore cette première barre oblique inversée. Pour éviter que les antislashs soient traités spécialement, nous utilisons read -r.

$ printf '%s\n' "$text" |
  while read -r line; do printf '%s\n' "[$line]"; done
[hello  world\]
[foo\bar]

C'est mieux, nous avons deux lignes comme prévu. Les deux lignes contiennent presque le contenu souhaité: le double espace entre helloet worlda été conservé, car il se trouve dans la linevariable. D'autre part, l'espace initial était épuisé. C’est parce que readlit autant de mots que vous transmettez les variables, sauf que la dernière variable contient le reste de la ligne - mais elle commence toujours par le premier mot, c’est-à-dire que les espaces initiaux sont supprimés.

Donc, pour lire chaque ligne littéralement, nous devons nous assurer qu’aucun fractionnement de mots n’est en cours. Nous faisons cela en définissant la IFSvariable sur une valeur vide.

$ printf '%s\n' "$text" |
  while IFS= read -r line; do printf '%s\n' "[$line]"; done
[ hello  world\]
[foo\bar]

Notez comment nous avons défini IFS spécifiquement pour la durée de la fonction readintégrée . Les IFS= read -r lineensembles de la variable d'environnement IFS(pour une valeur vide) spécifiquement pour l'exécution read. Il s'agit d'une instance de la syntaxe de commande simple générale : une séquence (éventuellement vide) d'assignations de variables suivie d'un nom de commande et de ses arguments (vous pouvez également insérer des redirections à tout moment). Comme il reads'agit d'un paramètre intégré, la variable ne se termine jamais dans l'environnement d'un processus externe; néanmoins, la valeur de $IFSest ce que nous attribuons là-bas tant qu’il readest exécuté¹. Notez que ce readn’est pas un élément intégré spécial , l’affectation ne dure que pour sa durée.

Nous veillons donc à ne pas modifier la valeur de IFSpour d’autres instructions qui peuvent en dépendre. Ce code fonctionnera quel que soit le code IFSinitial défini par le code environnant et ne causera aucun problème si le code contenu dans la boucle s'appuie sur IFS.

Contraste avec cet extrait de code, qui recherche les fichiers dans un chemin séparé par deux-points. La liste des noms de fichiers est lue dans un fichier, un nom de fichier par ligne.

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

Si la boucle était while IFS=; read -r name; do …, alors for dir in $PATHne serait pas divisé $PATHen composants séparés par des deux-points. Si le code l'était IFS=; while read …, il serait encore plus évident que ce IFSne soit pas défini :dans le corps de la boucle.

Bien sûr, il serait possible de restaurer la valeur de IFSaprès exécution read. Mais cela nécessiterait de connaître la valeur précédente, ce qui représente un effort supplémentaire. IFS= readest le moyen le plus simple (et, idéalement, aussi le plus court).

¹ Et, s’il readest interrompu par un signal piégé, éventuellement pendant l’exécution de la trappe, cela n’est pas spécifié par POSIX et dépend du shell en pratique.

Gilles
la source
4
Merci Gilles .. une très belle visite guidée ... (vouliez-vous dire 'ensemble -f'?) ... Maintenant, pour le lecteur, pour reformuler ce qui a déjà été dit, je voudrais souligner le problème qui moi en le regardant dans le mauvais sens. Tout d’abord, c’est le fait que la construction while IFS= read(sans point-virgule après =) n’est pas une forme spéciale de whileou de IFSou de read.. La construction est générique: ie. anyvar=anyvalue anycommand. Le manque de ;réglage après anyvarrend la portée de anyvar local à anycommand.. La boucle while - do / done est 100% indépendante de la portée locale de any_var.
Peter.O
3

Mis à part le (déjà clarifié) les IFSdifférences de cadrage entre les while IFS='' read, IFS=''; while readet les while IFS=''; readidiomes (par-commande vs script / shell large IFSportée variable), la leçon à retenir est que vous perdez les plus grands et les espaces de fin d'une ligne d'entrée si la variable IFS est défini sur (contient un) espace.

Cela peut avoir des conséquences assez graves si les chemins de fichiers sont en cours de traitement.

Par conséquent, définir la variable IFS sur la chaîne vide est tout sauf une mauvaise idée, car cela garantit que les espaces de début et de fin d'une ligne ne sont pas supprimés.

Voir aussi: Bash, lire ligne par ligne à partir d'un fichier, avec 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"
)
jon
la source
+1 excellente démonstration, nettoyage après avec 'rm * fichier * avec * espaces *'
vendredi
0

Inspiré par la réponse de Yuzem

Si vous voulez définir IFSun personnage réel, cela a fonctionné pour moi

iconv -f cp1252 zapni.tv.php | while IFS='#' read -d'#' line
do
  echo "$line"
done
Steven Penny
la source