Question Bash: itération sur les lignes d'une variable


Comment peut-on itérer correctement sur les lignes en bash, soit dans une variable, soit à partir de la sortie d'une commande? Il suffit de définir la variable IFS sur une nouvelle ligne pour la sortie d'une commande, mais pas lors du 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 voir, en faisant écho à la variable ou en itérant sur la cat commande imprime chacune des lignes une par une correctement. Cependant, le premier pour la boucle imprime tous les éléments sur une seule ligne. Des idées?


167
2018-05-16 12:14


origine




Réponses:


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

208
2018-05-16 13:47



Juste une note que le done <<< "$list" est crucial - Jason Axelson
La raison done <<< "$list" est crucial parce que cela passera "$ list" comme entrée pour read - wisbucky
Je dois insister sur ceci: $list est très crucial. - André Chalella
Comment pourrais-je le faire en cendre? Parce que je reçois un syntax error: unexpected redirection. - atripes
Il y a des inconvénients à cette approche car la boucle while s'exécutera dans un sous-shell: toutes les variables affectées dans la boucle ne persisteront pas. - glenn jackman


Vous pouvez utiliser while + read:

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

Btw. la -e option à echo est non standard. Utilisation printf au lieu de cela, si vous voulez la portabilité.


47
2018-05-16 12:21



Notez que si vous utilisez cette syntaxe, les variables affectées à l'intérieur de la boucle ne resteront pas après la boucle. Curieusement, le <<< version proposée par glenn jackman Est-ce que travailler avec affectation variable. - Sparhawk
@Sparhawk Oui, c'est parce que le canal lance un sous-shell exécutant le while partie. le <<< la version ne le fait pas (dans les nouvelles versions de bash, au moins). - 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

18
2018-03-17 21:45



Cela génère tous les éléments, pas les lignes. - Tomasz Posłuszny
@ TomaszPosłuszny Non, cela produit 3 (le compte est 3) lignes sur ma machine. Notez le réglage de l'IFS. Vous pouvez aussi utiliser IFS=$'\n' pour le même effet. - jmiserez


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

Toi $line variable ne contient pas de nouvelles lignes. Il contient des instances de \ suivi par 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 remplace celles avec des espaces, ce qui suffit pour que cela fonctionne pour des 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

7
2018-05-16 12:45



Intéressant, pouvez-vous expliquer ce qui se passe ici? On dirait que vous remplacez \ n par une nouvelle ligne ... Quelle est la différence entre la chaîne d'origine et la nouvelle? - Alex Spurling
@Alex: mis à jour ma réponse - avec une version plus simple aussi :-) - Mat
Je l'ai essayé et cela ne semble pas fonctionner si vous avez des espaces dans votre saisie. Dans l'exemple ci-dessus, nous avons "un \ n" trois fois et cela fonctionne mais il échoue si nous avons "entrée un \ nentry deux \ nentry trois" car cela ajoute également une nouvelle ligne pour l'espace. - John Rocha
Juste une note qu'au lieu de définir cr vous pouvez utiliser $'\n'. - devios1