2011-05-16 12:14:48 +0000 2011-05-16 12:14:48 +0000
247
247

Bash : Itération sur les lignes d'une variable

Comment itérer correctement sur les lignes de bash, soit dans une variable, soit à partir de la sortie d'une commande ? Le simple fait de définir la variable IFS sur une nouvelle ligne fonctionne pour la sortie d'une commande, mais pas lorsqu'on traite une variable qui contient de nouvelles lignes.

Par exemple

#!/bin/bash

list="One\ntwo\nthree\nfour"

#Print the list with echo
echo -e "echo: \n$list"

#Set the field separator to new line
IFS=$'\n'

#Try to iterate over each line
echo "For loop:"
for item in $list
do
        echo "Item: $item"
done

#Output the variable to a file
echo -e $list > list.txt

#Try to iterate over each line from the cat command
echo "For loop over command output:"
for item in `cat list.txt`
do
        echo "Item: $item"
done

Cela donne la sortie suivante :

echo: 
One
two
three
four
For loop:
Item: One\ntwo\nthree\nfour
For loop over command output:
Item: One
Item: two
Item: three
Item: four

Comme vous pouvez le voir, faire écho à la variable ou itérer sur la commande cat permet d'imprimer correctement chacune des lignes une par une. Cependant, la première boucle imprime tous les éléments sur une seule ligne. Des idées ?

Réponses (5)

312
312
312
2011-05-16 13:47:36 +0000

Avec bash, si vous voulez intégrer de nouvelles lignes dans une chaîne, entourez la chaîne avec $'' :

$ 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"
72
72
72
2011-05-16 12:21:28 +0000

Vous pouvez utiliser while + read :

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

A propos, l'option -e à echo n'est pas standard. Utilisez plutôt printf, si vous souhaitez la portabilité.

32
32
32
2014-03-17 21:45:33 +0000
#!/bin/sh

items="
one two three four
hello world
this should work just fine
"

IFS='
'
count=0
for item in $items
do
  count=$((count+1))
  echo $count $item
done
15
15
15
2011-05-16 12:45:14 +0000

Voici une drôle de façon de faire votre boucle pour :

for item in ${list//\n/
}
do
   echo "Item: $item"
done

Un peu plus sensible/ludique serait :

cr='
'
for item in ${list//\n/$cr}
do
   echo "Item: $item"
done

Mais c'est trop complexe, vous n'avez besoin que d'un espace là-dedans :

for item in ${list//\n/ }
do
   echo "Item: $item"
done

Votre variable $line ne contient pas de nouvelles lignes. Elle contient des instances de `Voici une drôle de façon de faire votre boucle pour :

for item in ${list//\n/
}
do
   echo "Item: $item"
done

Un peu plus sensible/ludique serait :

cr='
'
for item in ${list//\n/$cr}
do
   echo "Item: $item"
done

Mais c'est trop complexe, vous n'avez besoin que d'un espace là-dedans :

for item in ${list//\n/ }
do
   echo "Item: $item"
done

Votre variable $line ne contient pas de nouvelles lignes. Elle contient des instances de suivies den. 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 consiste à remplacer ces instances par des espaces, ce qui est suffisant pour qu'elle fonctionne en boucle :

$ 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
3
3
3
2019-05-07 16:19:00 +0000

Vous pouvez aussi d'abord convertir la variable en un tableau, puis l'itérer par dessus.

lines="abc
def
ghi"

declare -a theArray

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

for line in "${theArray[@]}"
do
    echo "$line"
    #Do something complex here that would break your read loop
done

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