Como posso ler linha por linha de uma variável no bash?

37

Eu tenho uma variável que contém saída de múltiplas linhas de um comando. Qual é a maneira mais eficiente de ler o resultado linha por linha a partir da variável?

Por exemplo:

jobs="$(jobs)"
if [ "$jobs" ]; then
    # read lines from $jobs
fi
    
por Eugene Yarmash 21.03.2011 / 11:07

6 respostas

55

Você pode usar um loop while com substituição de processo:

while read -r line
do
    echo "$line"
done < <(jobs)

Uma maneira ideal de ler uma variável multilinha é definir uma variável IFS em branco e printf a variável com uma nova linha:

# Printf '%s\n' "$var" is necessary because printf '%s' "$var" on a
# variable that doesn't end with a newline then the while loop will
# completely miss the last line of the variable.
while IFS= read -r line
do
   echo "$line"
done < <(printf '%s\n' "$var")

Nota: De acordo com shellcheck sc2031 , o uso da subestação de processo é preferível a um tubo para evitar [sutilmente ] criando um subshell.

Além disso, lembre-se de que nomear a variável jobs pode causar confusão, já que esse também é o nome de um comando shell comum.

    
por 21.03.2011 / 11:40
19

Para processar a saída de uma linha de comando por linha ( explicação ):

jobs |
while IFS= read -r line; do
  process "$line"
done

Se você já tiver os dados em uma variável:

printf %s "$foo" | …

printf %s "$foo" é quase idêntico a echo "$foo" , mas imprime $foo literalmente, enquanto echo "$foo" pode interpretar $foo como uma opção para o comando echo se começar com - e pode expandir a barra invertida seqüências em $foo em alguns shells.

Note que em alguns shells (ash, bash, pdksh, mas não em ksh ou zsh), o lado direito de um pipeline é executado em um processo separado, então qualquer variável que você definir no loop é perdida. Por exemplo, o seguinte script de contagem de linhas imprime 0 nesses shells:

n=0
printf %s "$foo" |
while IFS= read -r line; do
  n=$(($n + 1))
done
echo $n

Uma solução alternativa é colocar o restante do script (ou pelo menos a parte que precisa do valor de $n do loop) em uma lista de comandos:

n=0
printf %s "$foo" | {
  while IFS= read -r line; do
    n=$(($n + 1))
  done
  echo $n
}

Se a ação nas linhas não vazias for boa o suficiente e a entrada não for grande, você poderá usar a divisão de palavras:

IFS='
'
set -f
for line in $(jobs); do
  # process line
done
set +f
unset IFS

Explicação: configurar IFS para uma única nova linha faz a divisão de palavras ocorrer apenas em novas linhas (em oposição a qualquer caractere de espaço em branco na configuração padrão). set -f desativa a globulação (ou seja, expansão de caractere curinga), que, de outra forma, ocorreria com o resultado de uma substituição de comando $(jobs) ou uma variável substituta $foo . O loop for atua em todas as partes de $(jobs) , que são todas as linhas não vazias na saída do comando. Finalmente, restaure as configurações globbing e IFS .

    
por 21.03.2011 / 21:55
13

Problema: se você usar while loop, ele será executado em subshell e todas as variáveis serão perdidas. Solução: use para loop

# change delimiter (IFS) to new line.
IFS_BAK=$IFS
IFS=$'\n'

for line in $variableWithSeveralLines; do
 echo "$line"

 # return IFS back if you need to split new line by spaces:
 IFS=$IFS_BAK
 IFS_BAK=
 lineConvertedToArraySplittedBySpaces=( $line )
 echo "{lineConvertedToArraySplittedBySpaces[0]}"
 # return IFS back to newline for "for" loop
 IFS_BAK=$IFS
 IFS=$'\n'

done 

# return delimiter to previous value
IFS=$IFS_BAK
IFS_BAK=
    
por 04.09.2012 / 14:50
8

Em versões mais recentes, use mapfile ou readarray para ler com eficiência a saída de comando em matrizes

$ readarray test < <(ls -ltrR)
$ echo ${#test[@]}
6305

Aviso: exemplo horrível, mas você pode usar um comando melhor para usar do que você mesmo

    
por 22.03.2011 / 00:47
5
jobs="$(jobs)"
while IFS= read
do
    echo $REPLY
done <<< "$jobs"

Referências:

por 21.03.2011 / 11:14
-1

Você pode usar < < < para simplesmente ler a variável que contém os dados separados por nova linha:

while read -r line
do 
  echo "A line of input: $line"
done <<<"$lines"
    
por 04.05.2015 / 08:16

Tags