web-dev-qa-db-fra.com

Bash: Itérer sur des lignes dans une variable

Comment peut-on correctement parcourir les lignes de bash soit dans une variable, soit à partir du résultat d'une commande? Définir simplement la variable IFS sur une nouvelle ligne fonctionne pour la sortie d'une commande, mais pas pour le traitement d'une variable contenant 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:

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 constater, l'écho de la variable ou l'itération sur la commande cat imprime correctement chacune des lignes. Cependant, la première boucle imprime tous les éléments sur une seule ligne. Des idées?

205
Alex Spurling

Avec bash, si vous voulez incorporer des nouvelles lignes dans une chaîne, entourez-la 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 read -r line; do
    echo "... $line ..."
done <<< "$list"
266
glenn jackman

Vous pouvez utiliser while + read:

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

Btw. l'option -e à echo est non standard. Utilisez plutôt printf si vous voulez la portabilité.

58
maxelost
#!/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
25
nobody important

Voici une façon amusante de faire votre boucle:

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

Un peu plus sensible/lisible 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:

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

Votre variable $line ne contient pas de nouvelles lignes. Il contient des instances de \ suivi de n. Vous pouvez voir cela 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 celles-ci par des espaces, ce qui est suffisant pour que cela fonctionne dans les boucles:

$ 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émo:

$ 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
9
Mat