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

Bash: Itereren over regels in een variabele

Hoe kan men goed itereren over regels in bash, hetzij in een variabele, hetzij uit de uitvoer van een commando? Simpelweg het instellen van de IFS variabele op een nieuwe regel werkt voor de uitvoer van een commando, maar niet bij het verwerken van een variabele die nieuwe regels bevat.

Bijvoorbeeld

#!/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

Dit geeft de uitvoer:

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

Zoals je kunt zien, drukt het echoën van de variabele of het itereren over het cat commando elk van de regels één voor één correct af. Echter, de eerste voor lus print alle items op een enkele regel. Heeft u een idee?

Antwoorden (5)

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

Met bash, als je nieuwe lijnen in een string wilt insluiten, sluit je de string in met $'':

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

En als je zo'n string al in een variabele hebt, kun je hem regel voor regel lezen met:

while IFS= read -r line; do
    echo "... $line ..."
done <<< "$list"
72
72
72
2011-05-16 12:21:28 +0000

U kunt while + read gebruiken:

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

Btw. de -e optie naar echo is niet standaard. Gebruik printf in plaats daarvan, als u portabiliteit wilt.

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

Hier is een grappige manier om je for loop te doen:

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

Een beetje verstandiger/leesbaarder zou zijn:

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

Maar dat is al te complex, je hebt daar alleen een ruimte in nodig:

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

U $line-variabele bevat geen nieuwe lijnen. Het bevat gevallen van `Hier is een grappige manier om je for loop te doen:

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

Een beetje verstandiger/leesbaarder zou zijn:

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

Maar dat is al te complex, je hebt daar alleen een ruimte in nodig:

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

U $line-variabele bevat geen nieuwe lijnen. Het bevat gevallen van gevolgd doorn. Dat zie je duidelijk met:

$ 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

De vervanging is het vervangen van die door spaties, wat genoeg is om in te werken voor lussen:

$ 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

Demo:

$ 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

Je kunt de variabele ook eerst omzetten in een array, en dan itereren.

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

Dit is alleen nuttig als je niet wilt knoeien met de IFS en ook problemen hebt met het read commando, zoals het kan gebeuren, als je een ander script aanroept binnen de lus dat dat script je leesbuffer kan leegmaken voordat je terugkomt, zoals het mij is overkomen.