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 $text
sans 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]
read
a 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 hello
et world
a été conservé, car il se trouve dans la line
variable. D'autre part, l'espace initial était épuisé. C’est parce que read
lit 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 IFS
variable 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 read
intégrée . Les IFS= read -r line
ensembles 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 read
s'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 $IFS
est ce que nous attribuons là-bas tant qu’il read
est exécuté¹. Notez que ce read
n’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 IFS
pour d’autres instructions qui peuvent en dépendre. Ce code fonctionnera quel que soit le code IFS
initial 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 $PATH
ne serait pas divisé $PATH
en composants séparés par des deux-points. Si le code l'était IFS=; while read …
, il serait encore plus évident que ce IFS
ne soit pas défini :
dans le corps de la boucle.
Bien sûr, il serait possible de restaurer la valeur de IFS
aprè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= read
est le moyen le plus simple (et, idéalement, aussi le plus court).
¹ Et, s’il read
est 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 read
ne se sépare pasX
, maiswhile IFS=X; read
...