Dividir saída de comando por linebreak?

7

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

Meu código atual funciona modificando o IFS ( Separador de campo interno ):

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

Eu estou querendo saber, existe uma maneira melhor de acessar as linhas simples da saída de múltiplas linhas, uma sem modificar o IFS? Especialmente a legibilidade do meu script fica ruim, modificando o IFS.

Atualização: Eu tenho problemas para conseguir as respostas funcionando, 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!

    
por stefan.at.wpf 11.09.2012 / 22:20

3 respostas

11

Que tal read em um loop while ?

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

Tenha cuidado, porém, o pipe é executado em um subshell, o que significa que você não pode alterar os valores das variáveis para o restante do script. Se você precisar de algo para sobreviver a partir do seu loop while, você pode 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"
    
por 11.09.2012 / 22:32
1

A definição de IFS apenas para uma nova linha não é suficiente. (Por que você também está dividindo em caracteres backspace, a propósito?)

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

Você pode desativar a globbing com set -f . Na maioria dos casos em que você define IFS para usar o recurso de divisão de campo do shell, também é necessário desativar globbing. Defina-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."
}
    
por 12.09.2012 / 03:58
1

Você está misturando a sintaxe here-document e here-string em sua pergunta de atualização.

Use aqui 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:

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