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

Bash: Iterating over lines in a variable

How does one properly iterate over lines in bash either in a variable, or from the output of a command? Simplesmente definir a variável IFS para uma nova linha funciona para a saída de um comando mas não quando se processa uma variável que contém novas linhas.

Por exemplo

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

Isto dá a saída:

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

Como pode ver, fazendo eco da variável ou iterando sobre o comando cat imprime cada uma das linhas uma a uma correctamente. No entanto, o primeiro para loop imprime todos os itens numa única linha. Alguma ideia?

Respostas (5)

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

Com bash, se quiser incorporar novas linhas numa cadeia de caracteres, rode a cadeia com $'':

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

E se já tiver uma cadeia deste tipo numa variável, pode lê-la linha a linha com:

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

Pode usar while + read:

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

Btw. a opção -e para echo não é padrão. Utilize printf em vez disso, se pretende portabilidade.

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

Aqui está uma forma engraçada de fazer o seu loop:

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

Um pouco mais sensato/legíveis seria:

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

Mas isso é tudo demasiado complexo, só precisa de um espaço lá dentro:

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

Você $line variável não contém novas linhas. Contém exemplos de `Aqui está uma forma engraçada de fazer o seu loop:

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

Um pouco mais sensato/legíveis seria:

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

Mas isso é tudo demasiado complexo, só precisa de um espaço lá dentro:

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

Você $line variável não contém novas linhas. Contém exemplos de seguidos den. Pode ver isso claramente com:

$ 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

A substituição está a substituir aqueles por espaços, o que é suficiente para funcionar em loops:

$ 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

Também pode primeiro converter a variável num array, depois iterar sobre isto.

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

Isto só é útil se não quiser mexer no IFS e também tiver problemas com o comando read, como pode acontecer, se chamar outro script dentro do loop que esse script pode esvaziar o seu buffer de leitura antes de regressar, como me aconteceu a mim.