Questão Bash: Iterando as linhas em uma variável


Como uma pessoa itera corretamente sobre linhas no bash em uma variável ou na saída de um comando? Simplesmente definir a variável IFS para uma nova linha funciona para a saída de um comando, mas não ao processar uma variável que contenha 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

Isso 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 você pode ver, ecoando a variável ou iterando sobre o cat comando imprime cada uma das linhas uma por uma corretamente. No entanto, o primeiro loop for imprime todos os itens em uma única linha. Alguma ideia?


167


origem




Respostas:


Com bash, se você quiser incorporar novas linhas em uma string, coloque a string 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 você já tem uma string como essa em uma variável, você pode lê-la linha por linha com:

while read -r line; do
    echo "... $line ..."
done <<< "$list"

208



Apenas uma nota que o done <<< "$list" é crucial - Jason Axelson
O motivo done <<< "$list" é crucial é porque isso vai passar "$ lista" como a entrada para read - wisbucky
Eu preciso enfatizar isso: QUOTANDO DUPLO $list é muito crucial. - André Chalella
Como eu faria isso em cinzas? Porque eu tenho um syntax error: unexpected redirection. - atripes
Existem desvantagens nessa abordagem, já que o loop while será executado em uma subshell: qualquer variável designada no loop não persistirá. - glenn jackman


Você pode usar while + read:

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

Btw. a -e opção para echo não é padrão. Usar printf em vez disso, se você quiser portabilidade.


47



Observe que, se você usar essa sintaxe, as variáveis ​​atribuídas dentro do loop não ficarão após o loop. Curiosamente, o <<< versão sugerida por glenn jackman faz trabalhar com atribuição de variável. - Sparhawk
@Sparhawk Sim, isso é porque o pipe inicia um subshell executando o while parte. o <<< versão não (em novas versões bash, pelo menos). - 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



Isso gera todos os itens, não linhas. - Tomasz Posłuszny
@ TomaszPosłuszny Não, isso gera 3 linhas (contagem é 3) na minha máquina. Observe a configuração do IFS. Você também pode usar IFS=$'\n' para o mesmo efeito. - jmiserez


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

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

Um pouco mais sensível / legível seria:

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

Mas isso é muito complexo, você só precisa de um espaço lá:

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

Você $line variável não contém novas linhas. Ele contém instâncias de \ Seguido por n. Você 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á substituindo aqueles com espaços, o que é suficiente para trabalhar 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

Demonstração:

$ 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



Interessante, você pode explicar o que está acontecendo aqui? Parece que você está substituindo \ n por uma nova linha ... Qual é a diferença entre a string original e a nova? - Alex Spurling
@Alex: atualizei minha resposta - com uma versão mais simples também :-) - Mat
Eu tentei isso e não parece funcionar se você tem espaços em sua entrada. No exemplo acima, temos "one \ ntwo \ nthree" e isso funciona, mas falha se tivermos "entry one \ nentry two \ nentry three", pois ele também adiciona uma nova linha para o espaço. - John Rocha
Apenas uma nota que, em vez de definir cr você pode usar $'\n'. - devios1