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.
while IFS=X readne se sépare pasX, maiswhile IFS=X; read...