336 votes

Bash: Parcourir les lignes dans une variable

Comment itère-t-on correctement sur les lignes en bash, que ce soit dans une variable ou à partir de la sortie d'une commande? Il suffit de définir la variable IFS sur une nouvelle ligne pour fonctionner avec la sortie d'une commande mais pas pour traiter une variable contenant des nouvelles lignes.

Par exemple

#!/bin/bash

list="Un\ndeux\ntrois\nquatre"

#Afficher la liste avec echo
echo -e "echo: \n$list"

#Définir le séparateur de champ sur une nouvelle ligne
IFS=$'\n'

#Essayer d'itérer sur chaque ligne
echo "Pour la boucle :"
for item in $list
do
        echo "Élément: $item"
done

#Sortir la variable dans un fichier
echo -e $list > list.txt

#Essayer d'itérer sur chaque ligne à partir de la commande cat
echo "Pour la boucle sur la sortie de la commande :"
for item in `cat list.txt`
do
        echo "Élément: $item"
done

Cela donne la sortie:

echo: 
Un
deux
trois
quatre
Pour la boucle :
Élément: Un\ndeux\ntrois\nquatre
Pour la boucle sur la sortie de la commande :
Élément: Un
Élément: deux
Élément: trois
Élément: quatre

Comme vous pouvez le constater, l'affichage de la variable ou itérer sur la commande cat imprime correctement chaque ligne séparément. Cependant, la première boucle for imprime tous les éléments sur une seule ligne. Des idées?

0 votes

Juste un commentaire pour toutes les réponses : j'ai dû faire $(echo "$line" | sed -e 's/^[[:space:]]*//') pour supprimer le caractère de saut de ligne.

399voto

Abbas Points 3737

Avec bash, si vous voulez intégrer des sauts de ligne dans une chaîne, entourez la chaîne de $'':

$ list="One\ntwo\nthree\nfour"
$ echo "$list"
One\ntwo\nthree\nfour
$ list=$'One\ntwo\nthree\nfour'
$ echo "$list"
One
two
three
four

Et si vous avez déjà une telle chaîne dans une variable, vous pouvez la lire ligne par ligne avec:

while IFS= read -r line; do
    echo "... $line ..."
done <<< "$list"

39 votes

Juste une remarque que le done <<< "$list" est crucial

30 votes

La raison done <<< "$list" est cruciale car cela passera "$list" comme entrée à read

28 votes

Je dois insister sur ce point : DOUBLE-CITATION $list est très crucial.

120voto

Luddig Points 191

Vous pouvez utiliser while + read:

some_command | while read line ; do
   echo === $line ===
done

À propos, l'option -e de echo n'est pas standard. Utilisez plutôt printf si vous voulez la portabilité.

22 votes

Notez que si vous utilisez cette syntaxe, les variables assignées à l'intérieur de la boucle ne resteront pas après la boucle. Étrangement, la version <<< suggérée par Glenn Jackman fonctionne avec l'assignation de variables.

10 votes

@Sparhawk Oui, c'est parce que le pipe commence une sous-shell qui exécute la partie while. La version <<< ne le fait pas (dans les nouvelles versions de bash, du moins).

51voto

nobody important Points 509
#!/bin/sh

éléments="
un deux trois quatre
bonjour le monde
cela devrait fonctionner parfaitement
"

IFS='
'
compter=0
pour élément en $éléments
faire
  compter=$((compter+1))
  echo $compter $élément
faire

2 votes

Cela affiche tous les éléments, pas les lignes.

12 votes

@TomaszPosuszny Non, cela affiche 3 lignes sur ma machine (le compte est de 3). Notez le réglage de l'IFS. Vous pourriez également utiliser IFS=$'\n' pour le même effet.

0 votes

Ceci et la proposition de @jmiserez fonctionnent tous les deux à merveille, merci!

24voto

givp Points 798

Voici une façon amusante de faire votre boucle for:

for item in ${list//\\n/
}
do
   echo "Élément : $item"
done

Un peu plus sensé/lisible serait:

cr='
'
for item in ${list//\\n/$cr}
do
   echo "Élément : $item"
done

Mais tout cela est trop complexe, vous avez juste besoin d'un espace là-dedans:

for item in ${list//\\n/ }
do
   echo "Élément : $item"

Votre variable $line ne contient pas de sauts de ligne. Elle contient des instances de \ suivies de n. Vous pouvez le voir clairement avec:

$ cat t.sh
#! /bin/bash
list="One\ntwo\nthree\nfour"
echo $list | hexdump -C

$ ./t.sh
00000000  4f 6e 65 5c 6e 74 77 6f  5c 6e 74 68 72 65 65 5c  |One\ntwo\nthree\|
00000010  6e 66 6f 75 72 0a                                 |nfour.|
00000016

La substitution remplace ces éléments par des espaces, ce qui suffit pour qu'elle fonctionne dans les boucles for:

$ cat t.sh
#! /bin/bash
list="One\ntwo\nthree\nfour"
echo ${list//\\n/ } | hexdump -C

$ ./t.sh 
00000000  4f 6e 65 20 74 77 6f 20  74 68 72 65 65 20 66 6f  |One two three fo|
00000010  75 72 0a                                          |ur.|
00000013

Démonstration :

$ cat t.sh
#! /bin/bash
list="One\ntwo\nthree\nfour"
echo ${list//\\n/ } | hexdump -C
for item in ${list//\\n/ } ; do
    echo $item
done

$ ./t.sh 
00000000  4f 6e 65 20 74 77 6f 20  74 68 72 65 65 20 66 6f  |One two three fo|
00000010  75 72 0a                                          |ur.|
00000013
One
two
three
four

0 votes

Intéressant, pouvez-vous expliquer ce qui se passe ici? Il semble que vous remplacez \n par une nouvelle ligne... Quelle est la différence entre la chaîne originale et la nouvelle?

0 votes

@Alex: mis à jour ma réponse - avec une version plus simple aussi :-)

0 votes

J'ai essayé cela et il semble ne pas fonctionner si vous avez des espaces dans votre saisie. Dans l'exemple ci-dessus, nous avons "un\ndeux\ntrois" et cela fonctionne mais cela échoue si nous avons "entrée un\n entrée deux\n entrée trois" car il ajoute également une nouvelle ligne pour l'espace aussi.

8voto

Torge Points 233

Vous pouvez également d'abord convertir la variable en un tableau, puis itérer sur celui-ci.

lines="abc
def
ghi"

declare -a theArray

while read -r line
do
    theArray+=("$line")            
done <<< "$lines"

for line in "${theArray[@]}"
do
    echo "$line"
    #Faites quelque chose de complexe ici qui pourrait casser votre boucle de lecture
done

Cela n'est utile que si vous ne voulez pas manipuler l' IFS et avez également des problèmes avec la commande read, car il peut arriver que si vous appelez un autre script à l'intérieur de la boucle, ce script puisse vider votre tampon de lecture avant de retourner, comme cela m'est arrivé.

SistemesEz.com

SystemesEZ est une communauté de sysadmins où vous pouvez résoudre vos problèmes et vos doutes. Vous pouvez consulter les questions des autres sysadmins, poser vos propres questions ou résoudre celles des autres.

Powered by:

X