Use o read builtin do bash sem um loop while

Use o read builtin do bash sem um loop while

Estou acostumado com a função bashinterna readem loops while, por exemplo:

echo "0 1
      1 1
      1 2
      2 3" |\
while read A B; do
    echo $A + $B | bc;
done

Estou trabalhando em algum makeprojeto e tornou-se prudente dividir arquivos e armazenar resultados intermediários. Como consequência, muitas vezes acabo destruindo linhas únicas em variáveis. Embora o exemplo a seguir funcione muito bem,

head -n1 somefile | while read A B C D E FOO; do [... use vars here ...]; done

é meio estúpido, porque o loop while nunca será executado mais de uma vez. Mas sem o while,

head -n1 somefile | read A B C D E FOO; [... use vars here ...]

As variáveis ​​de leitura estão sempre vazias quando eu as uso. Nunca percebi esse comportamento read, porque normalmente eu usaria loops while para processar muitas linhas semelhantes. Como posso usar basho readbuiltin sem um loop while? Ou existe outra maneira (ou ainda melhor) de ler uma única linha em múltiplas (!) variáveis?

Conclusão

As respostas nos ensinam que é um problema de escopo. A declaração

 cmd0; cmd1; cmd2 | cmd3; cmd4

é interpretado de forma que os comandos cmd0, cmd1e cmd4sejam executados no mesmo escopo, enquanto os comandos cmd2e cmd3recebam cada um seu próprio subshell e, consequentemente, escopos diferentes. O shell original é o pai de ambos os subshells.

Responder1

É porque a parte onde você usa vars é um novo conjunto de comandos. Use isto em vez disso:

head somefile | { read A B C D E FOO; echo $A $B $C $D $E $FOO; }

Observe que, nesta sintaxe, deve haver um espaço após o { e um ;(ponto e vírgula) antes do }. Também -n1não é necessário; readlê apenas a primeira linha.

Para melhor compreensão, isso pode ajudá-lo; faz o mesmo que acima:

read A B C D E FOO < <(head somefile); echo $A $B $C $D $E $FOO

Editar:

Costuma-se dizer que as próximas duas afirmações fazem o mesmo:

head somefile | read A B C D E FOO
read A B C D E FOO < <(head somefile)

Bem, não exatamente. O primeiro é um pipe do headto bashembutido read. O stdout de um processo para o stdin de outro processo.

A segunda instrução é o redirecionamento e a substituição de processos. É tratado bashsozinho. Ele cria um FIFO (chamado pipe <(...)) ao qual heada saída está conectada e o redireciona ( <) para o readprocesso.

Até agora, estes parecem equivalentes. Mas ao trabalhar com variáveis ​​isso pode ser importante. No primeiro as variáveis ​​não são definidas após a execução. No segundo eles estão disponíveis no ambiente atual.

Cada shell tem outro comportamento nesta situação. Veresse linkpara o qual eles são. Você bashpode contornar esse comportamento com agrupamento de comandos {}, substituição de processo ( < <()) ou strings Here ( <<<).

Responder2

Para citar um artigo muito útilwiki.bash-hackers.org:

Isso ocorre porque os comandos do pipe são executados em subshells que não podem modificar o shell pai. Como resultado, as variáveis ​​do shell pai não são modificadas (veja o artigo:Bash e a árvore de processos).

Como a resposta já foi fornecida algumas vezes, uma forma alternativa (usando comandos não integrados...) é esta:

$ eval `echo 0 1 | awk '{print "A="$1";B="$2}'`;echo $B $A
$ 1 0

Responder3

Como você observou, o problema era que um canal readera executado em um subshell.

Uma resposta é usar umheredoc:

numbers="01 02"
read first second <<INPUT
$numbers
INPUT
echo $first
echo $second

Este método é interessante porque se comportará da mesma maneira em qualquer shell do tipo POSIX.

informação relacionada