Eu tenho uma variável que contém saída multilinha de um comando. Qual é a maneira mais eficiente de ler a linha de saída por linha da variável?
Por exemplo:
jobs="$(jobs)"
if [ "$jobs" ]; then
# read lines from $jobs
fi
Eu tenho uma variável que contém saída multilinha de um comando. Qual é a maneira mais eficiente de ler a linha de saída por linha da variável?
Por exemplo:
jobs="$(jobs)"
if [ "$jobs" ]; then
# read lines from $jobs
fi
Respostas:
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 de múltiplas linhas é definir uma IFSvariável em branco e printfa variável com uma nova linha à direita:
# 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: Conforme o shellcheck sc2031 , o uso da subestação de processo é preferível a um tubo para evitar [sutilmente] criar um subshell.
Além disso, saiba que, ao nomear a variável, jobsisso pode causar confusão, pois esse também é o nome de um comando shell comum.
echopara printf %s, para que seu script funcionasse mesmo com entradas não domesticadas.
/tmpdiretório seja gravável, pois depende de ser capaz de criar um arquivo de trabalho temporário. Se você se encontrar em um sistema restrito com /tmpapenas leitura (e não pode ser alterado por você), ficará feliz com a possibilidade de usar uma solução alternativa, por exemplo, com o printfcano.
printf "%s\n" "$var" | while IFS= read -r line
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á possui os dados em uma variável:
printf %s "$foo" | …
printf %s "$foo"é quase idêntico a echo "$foo", mas é impresso $fooliteralmente, enquanto que echo "$foo"pode ser interpretado $foocomo uma opção para o comando echo se começar com a -e pode expandir as seqüências de barra invertida $fooem alguns shells.
Observe que, em alguns shells (ash, bash, pdksh, mas não ksh ou zsh), o lado direito de um pipeline é executado em um processo separado; portanto, 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 do $nloop) em uma lista de comandos:
n=0
printf %s "$foo" | {
while IFS= read -r line; do
n=$(($n + 1))
done
echo $n
}
Se agir nas linhas não vazias for bom 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: a configuração IFSde uma única nova linha faz com que a divisão de palavras ocorra somente em novas linhas (em oposição a qualquer caractere de espaço em branco na configuração padrão). set -fdesativa o globbing (ou seja, expansão de curinga), que de outra forma ocorreria com o resultado de uma substituição de comando $(jobs)ou de uma variável substitutino $foo. O forloop atua em todas as partes de $(jobs), que são todas as linhas não vazias na saída do comando. Por fim, restaure as globbing e as IFSconfigurações.
local IFS=something. Não afetará o valor do escopo global. IIRC, unset IFSvocê não volta ao padrão (e certamente não funciona se não fosse o padrão anteriormente).
Problema: se você usar o loop while, 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=
while readloop no bash significa que o loop while está em um subshell, portanto, as variáveis não são globais. while read;do ;done <<< "$var"torna o corpo do loop não um subconjunto. (Bash recentes tem a opção de colocar o corpo de um cmd | whileloop não em um subnível, como ksh sempre teve.)
Nas versões recentes do bash, use mapfileou readarraypara ler com eficiência a saída do comando em matrizes
$ readarray test < <(ls -ltrR)
$ echo ${#test[@]}
6305
Isenção de responsabilidade: exemplo horrível, mas você pode criar um comando melhor do que você mesmo
readarrayuma função e chame a função algumas vezes.
jobs="$(jobs)"
while IFS= read
do
echo $REPLY
done <<< "$jobs"
Referências:
-ré uma boa ideia também; Ela impede que \` interpretation... (it is in your links, but its probably worth mentioning, just to round out your IFS = `(que é essencial para evitar a perda de espaço em branco)
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"
while IFS= read.... Se você quer evitar \ interpretação, então useread -r