Dividir a saída do comando por quebra de linha?

9

Eu tenho um comando retornando várias linhas. Para processamento adicional, eu preciso processar cada linha dessas linhas.

Meu código atual funciona modificando o IFS ( Internal Field Separator ):

ROWS=$(some command returning multiple lines)

O=$IFS #save original IFS
IFS=$(echo -en "\n\b") # set IFS to linebreak

for ROW in ${ROWS[@]}
do
  echo "$ROW"
done

IFS=$O #restore old IFS

Gostaria de saber, existe uma maneira melhor de acessar as linhas únicas da saída de várias linhas, uma sem modificar o IFS? Especialmente a legibilidade do meu script fica ruim ao modificar o IFS.

Atualização: Tenho problemas para obter respostas, por exemplo, a da choroba:

while IFS= read -r line ; do
    let var+=line #line 42
done << $(sqlite3 -list -nullvalue NULL -separator ',' /var/log/asterisk/master.db "${QUERY}")
echo "$var" # line 44

me dá

./bla.sh: row 44: Warning: here-document at line 43 delimited by end-of-file (wanted `$(sqlite3 -list -nullvalue NULL -separator , /var/log/asterisk/master.db ${QUERY})')
./bla.sh: row 42: let: echo "": syntax error: invalid arithmetic operator. (error causing character is \"""\").

Alguém pode me ajudar com isso? Obrigado!

stefan.at.wpf
fonte
a resposta do choroba diz para fazer < <(some command returning multiple lines), mas não é isso que você está fazendo.
22312 Mikel

Respostas:

13

Que tal readem um whileloop?

some command returning multiple lines | while IFS= read -r line ; do
    echo "$line"
done

Tenha cuidado, porém, o pipe é executado em uma subshell, o que significa que você não pode alterar valores de variáveis ​​para o restante do script. Se você precisar de algo para sobreviver a partir do loop while, poderá usar a substituição de processos do bash:

while IFS= read -r line ; do
    let var+=line
done < <(some command returning multiple lines)
echo "$var"
choroba
fonte
Usar a substituição de processo é uma solução inteligente para isso bash, mas vale a pena notar que o escopo de subconjuntos de variáveis ​​é apenas um problema bash, zshpois fazer o mesmo não teria esse problema.
Caleb
O ksh também não tem esse problema.
Didi Kohen
obrigado pessoal, tentei esta solução, mas não consigo fazê-la funcionar, consulte meu post original atualizado, obrigado!
stefan.at.wpf
@ stefan.at.wpf: Você não pode colocar espaços e cifrões à vontade. Eles têm significado.
choroba
3

Definir IFSapenas uma nova linha não é suficiente. (Por que você também está dividindo caracteres de backspace, a propósito?)

No seu código, ${ROWS[@]}(que é uma maneira estranha de escrever $ROWS- ROWSnão é uma matriz) não é citado duas vezes. (Se estivesse entre aspas duplas, você obteria uma única string, já que ROWSnão é uma matriz.) Portanto, o shell divide o valor da variável em campos em cada IFScaractere e depois trata cada campo como um padrão glob. Por exemplo, se uma das linhas impressas pelo comando contiver um caractere único *, isso será substituído pelos nomes dos arquivos no diretório atual.

Você pode desativar o globbing com set -f. Na maioria dos casos em que você define o IFSuso do recurso de divisão de campo do shell, também precisa desativar o globbing. Coloque-o novamente com set +f.

O idioma confiável para ler a linha de saída de um comando por linha é while IFS= read -r.

some command returning multiple lines |
while IFS= read -r ROW; do
  
done

Observe que a maioria dos shells executa cada comando de um pipeline em um subshell separado. Portanto, se você precisar definir variáveis ​​e usá-las após o loop, agrupe esses comandos em um grupo junto com o loop. (Ksh e zsh são as exceções, eles executam o último comando de um pipeline no shell pai.)

some command returning multiple lines | {
  while IFS= read -r ROW; do
    
    row_count=$((row_count+1))
  done
  echo "There were $row_count rows."
}
Gilles 'SO- parar de ser mau'
fonte
1

Você está misturando a sintaxe aqui-documento e aqui-string na sua pergunta de atualização.

Use aqui o documento:

while IFS= read -r line ; do
    let var+=line #line 42
done <<ENDMARK
$(sqlite3 -list -nullvalue NULL -separator ',' /var/log/asterisk/master.db "${QUERY}")
ENDMARK

Ou aqui-string:

while IFS= read -r line ; do
    let var+=line #line 42
done <<< $(sqlite3 -list -nullvalue NULL -separator ',' /var/log/asterisk/master.db "${QUERY}")
homem a trabalhar
fonte