Estou acostumado com a função bash
interna 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
Estou trabalhando em algum make
projeto 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 bash
o read
builtin 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
, cmd1
e cmd4
sejam executados no mesmo escopo, enquanto os comandos cmd2
e cmd3
recebam 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 -n1
não é necessário; read
lê 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 head
to bash
embutido 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 bash
sozinho. Ele cria um FIFO (chamado pipe <(...)
) ao qual head
a saída está conectada e o redireciona ( <
) para o read
processo.
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ê bash
pode 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 read
era 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.