Processamento de entrada de linha a linha (com linhas vazias) do shell

3

Em um script de shell, preciso analisar a saída de um comando linha por linha. A saída pode incluir linhas vazias e estas são relevantes. Eu estou usando cinzas, não bash, então não posso recorrer à substituição de processos. Eu estou tentando isso:

    OUT='my_command'
    IFS=$'\n'              
    i=1
    for line in $OUT; do
        echo $line                                          
        eval VAL$i=$line             
        i=$((i+1))             
    done

No entanto, isso está descartando linhas vazias em $ OUT. Como posso corrigir isso para que as linhas vazias também sejam processadas?

    
por Grodriguez 13.02.2015 / 16:05

3 respostas

0

Eu implementei isso com um documento aqui:

    i=1
    while read -r line; do
        eval VAL$i=\$line
        i=$((i+1))
    done <<EOF
$(my_command)
EOF

Funciona bem.

Atualização: Comentários incorporados de Gilles e mikeserv.

    
por 13.02.2015 / 17:22
3

Um loop de shell viável poderia ser semelhante ...

set -f -- "-$-"' -- "$@" '"
    ${IFS+IFS=\} ${out+out=\}" \
    "$IFS" "$out" "$@"
IFS='
';for out in $(my command|grep -n '.\|')
do  : something with "${out%%:*}" and "${out#*:}"
done
unset IFS out
eval "set +f $1"
shift 3

Você precisa apenas organizá-lo para que não haja linhas em branco. Embora inicialmente eu tenha sugerido nl para essa finalidade, pensando bem, há uma pequena chance de que o divisor de páginas lógicas de nl possa ocorrer na entrada e distorcer sua saída (ela acabaria resultando em uma linha em branco, na verdade, e influenciaria qual linha foi numerada - é um recurso muito útil para outras finalidades) . Além de não interpretar quebras de página lógicas, os resultados de grep -n '.\|' serão idênticos.

Usando um pipeline como esse com uma pequena substituição de parâmetro e você pode não apenas evitar o problema da linha em branco, mas também cada iteração vem pré-numerada ao mesmo tempo - (o número atual da iteração será no momento cabeça de cada valor serviu para você por $out seguido por um : ) .

As linhas set ... IFS=... estão lá para garantir que o estado do shell seja restaurado para onde você o deixou antes de alterá-lo. Essas precauções podem ser exageradas se for um script e não uma função. Ainda assim, você deve pelo menos set -f antes da divisão do shell para evitar globalização não intencional em sua entrada.

Mas sobre (d)ash e <( substituição de processo )

Então, novamente, em um Debian ( dash ) derivado ash (como busybox ash ) você pode achar que o seu tratamento de links de descritor de arquivo e aqui-documents fornece uma alternativa superior ao que você pode estar acostumado a fazer com <( process substitution ) .

Considere este exemplo:

exec "$((i=3))"<<R "$((o=4))"<<W 3<>/dev/fd/3 4<>/dev/fd/4
R
W

sed -u 's/.*/here I am./' <&"$o" >&"$i" &
echo "hey...sed?" >&"$o"
head -n1          <&"$i"

Porque dash e derivados de volta aqui - documentos com caches anônimas em vez de (como a maioria das outras shells) com arquivos regulares, e porque os links /dev/fd/[num] nos sistemas linux fornecem uma maneira indireta de referir-se ao arquivo de apoio do descritor de arquivo (mesmo quando não pode ser referenciado em um sistema de arquivos - como para pipes anônimos) a seqüência acima demonstra um meio muito simples de configurar o que alguns shells podem consulte como coprocess . Por exemplo, em busybox ash ou dash em um sistema linux (não atestarei outros) o acima será impresso:

here I am.

... e continuará fazendo isso até que o shell feche seus descritores de arquivo $i e $o . Aproveita a opção -u nbuffered do GNU sed para evitar problemas de buffer, mas mesmo sem ela a entrada do processo em segundo plano pode ser filtrada e conv=sync hronized em blocos de ddNUL bytes w / sed em um pipeline, se necessário.

Aqui está uma maneira em que normalmente uso o acima com sed em um shell interativo:

: & SEDD=$$$!
sed -un "/^$SEDD$/!H;//!d;s///;x;/\n/!q;s///;s/%/&&/g;l" <&"$o" >&"$i" &

... quais fundos a % que lerá e armazenará entrada até encontrar um delimitador exclusivo, quando dobrará qualquer ocorrência de H em seu buffer exec old e imprimirá em meu sed 'd anonymous cana uma string amigável com C no formato printf em uma única linha - ou, em várias linhas, se o resultado for maior que 80 caracteres. Este último - para o GNU sed -l0 - pode ser tratado com sed , que é um parâmetro que instruiria \ a não quebrar linhas em $fmt , ou então:

fmt=
while IFS= read -r r <&"$i" 
      case $r in (*$) 
      ! fmt=$fmt$r ;;esac
do    fmt=$fmt${r%?} 
done

De qualquer forma, eu construo seu buffer como:

echo something at sed >&"$o"
printf '%s\n' more '\lines%' at sed "$SEDD" >&"$o"

Então eu puxo-o como ...

IFS= read -r fmt <&"$i"

Este é o conteúdo de sed depois:

printf %s\n "$fmt"
something at sed\nmore\n\lines%%\nat\nsed$

sed também fará escutas octal no estilo C para caracteres não imprimíveis.

Então eu posso usá-lo como ...

printf "%d\n${fmt%$}\n" 1 2 3

... que imprime ...

1
something at sed
more
\lines%
at
sed
2
something at sed
more
\lines%
at
sed
3
something at sed
more
\lines%
at
sed

E eu posso matar %code% e liberar os canos conforme necessário ...

printf %s\n "$SEDD" "$SEDD" >&"$o"
exec "$i">&- "$o">&-

Esse é o tipo de coisa que você pode fazer quando consegue segurar um fd em vez de usá-lo apenas uma vez. Você pode manter um back-pipe pelo tempo que for necessário - e é mais seguro do que um pipe nomeado porque o kernel não oferece esses links para nenhum, exceto o processo que os possui ( seu shell) , enquanto um pipe nomeado pode ser encontrado (e tocado / roubado) em um sistema de arquivos por qualquer processo com permissões para seu arquivo de referência.

Para fazer coisas semelhantes em um shell que processa a substituição, você provavelmente pode gostar ...

eval "exec [num]<>"<(:)

... mas eu nunca tentei isso.

    
por 13.02.2015 / 17:09
1

Faça assim:

i=1
my_command | while read line; do
    echo $line
    eval VAL$i="$line"
    i=$((i+1))
done

Como a saída do comando é lida linha por linha, essas linhas são processadas individualmente (incluindo linhas vazias) sem ter que armazenar essas linhas em uma variável primeiro. Isso também economiza memória, já que a saída não acaba na memória duas vezes, e o script bash pode começar a processar essas linhas assim que elas saírem e não apenas após o comando ter sido concluído.

EDIT: Como as variáveis VALx são definidas em uma subshell acima, é necessária uma modificação:

eval 'i=1
my_command | while read line; do
    # echo $line
    echo "VAL$i=\"$line\""
    i=$((i+1))
done'

Se você realmente precisar do echo $line , algumas modificações serão necessárias.

    
por 13.02.2015 / 16:41