Use o builtin do bash sem um loop while

6

Estou acostumado com a função bash de read em loops while, por exemplo:

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

Eu tenho trabalhado em algum projeto make , e tornou-se prudente dividir arquivos e armazenar resultados intermediários. Como conseqüência, muitas vezes acabo destruindo linhas simples em variáveis. Enquanto o exemplo a seguir funciona muito bem,

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

é meio idiota, 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. Eu nunca notei esse comportamento de read , porque normalmente eu usaria loops while para processar muitas linhas similares. Como posso usar bash ' read builtin sem um loop while? Ou existe outra maneira (ou até melhor) de ler uma única linha em múltiplas variáveis (!)?

Conclusão

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

 cmd0; cmd1; cmd2 | cmd3; cmd4

é interpretado de tal forma que os comandos cmd0 , cmd1 e cmd4 são executados no mesmo escopo, enquanto os comandos cmd2 e cmd3 recebem cada um seu próprio subshell e, conseqüentemente, diferentes escopos. O shell original é o pai de ambas as subpastas.

    
por Bananguin 15.10.2014 / 11:13

3 respostas

7

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

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

Observe que, nessa sintaxe, deve haver um espaço após o { e um ; (ponto-e-vírgula) antes do } . Também -n1 não é necessário; read apenas lê 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 canal de head a bash read builtin. Um stdout de um processo para stdin de outro processo.

A segunda instrução é redirecionamento e substituição de processo. É tratado pelo próprio bash . Ele cria um FIFO (pipe nomeado, <(...) ) ao qual a saída de head está conectada e redireciona ( < ) para o processo read .

Até agora, estes parecem equivalentes. Mas quando se trabalha com variáveis, isso pode importar. 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 nessa situação. Veja esse link para o qual eles são. Em bash , você pode contornar esse comportamento com o agrupamento de comandos {} , a substituição de processo ( < <() ) ou as strings Here ( <<< ).

    
por 15.10.2014 / 11:20
3

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

This is because the commands of the pipe run in subshells that cannot modify the parent shell. As a result, the variables of the parent shell are not modified (see article: Bash and the process tree).

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

$ eval 'echo 0 1 | awk '{print "A="$1";B="$2}'';echo $B $A
$ 1 0
    
por 15.10.2014 / 11:55
0

Como você observou, o problema é que um canal para read é executado em um subshell.

Uma resposta é usar um heredoc :

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

Este método é legal porque se comportará da mesma maneira em qualquer shell parecido com POSIX.

    
por 30.01.2015 / 20:44