no bash, leia depois que um pipe não está definindo valores

22

Editar: o título original foi "falha na leitura no bash"

Com o ksh, estou usando o read como uma maneira conveniente de separar valores:

$ echo 1 2 3 4 5 | read a b dump
$ echo $b $a 
2 1
$

Mas falha no bash:

$ echo 1 2 3 4 5 | read a b dump
$ echo $b $a 

$

Não encontrei um motivo na página de manual por que falha, alguma ideia?

Emmanuel
fonte
2
Isso é discutido (de maneira um tanto obscura) na página 024 das Perguntas frequentes sobre o Greg's Bash .
1111 Scott

Respostas:

27

bash executa o lado direito de um pipeline em um contexto de subshell , para que as alterações nas variáveis ​​(que é o que readfaz) não sejam preservadas - elas morrem quando o subshell faz, no final do comando.

Em vez disso, você pode usar a substituição de processo :

$ read a b dump < <(echo 1 2 3 4 5)
$ echo $b $a
2 1

Nesse caso, readestá sendo executado em nosso shell primário e nosso comando de produção de saída é executado no subshell. A <(...)sintaxe cria um subshell e conecta sua saída a um canal, para o qual redirecionamos para a entrada readcom a <operação comum . Por ser readexecutado em nosso shell principal, as variáveis ​​estão definidas corretamente.

Conforme indicado em um comentário, se seu objetivo é literalmente dividir uma sequência em variáveis ​​de alguma forma, você pode usar uma sequência aqui :

read a b dump <<<"1 2 3 4 5"

Presumo que há mais do que isso, mas essa é uma opção melhor, se não houver.

Michael Homer
fonte
3
Ou até read a b dump <<< '1 2 3 4 5'.
choroba
Obrigado a todos, notei que o mksh (no cygwin) está fazendo o mesmo que o bash.
Emmanuel
@ Michael Homer Boa explicação! Eu encontrei um outro exemplo que pode explicar que cada comando no pipeline de correr na própria subshell: cat /etc/passwd | (read -r line ; echo $line). Mas o próximo echoitem $lineque não está no pipeline não coloca nada na tela, porque o valor existia apenas entre parênteses (subshell). Espero que ajude alguém.
Yurij Goncharuk
17

Isso não é um bashbug, pois POSIXpermite comportamentos bashe ambos ksh, levando à discrepância infeliz que você está observando.

http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_12

Além disso, cada comando de um pipeline de comandos múltiplos está em um ambiente de subcasca; como uma extensão, no entanto, qualquer um ou todos os comandos em um pipeline podem ser executados no ambiente atual. Todos os outros comandos devem ser executados no ambiente atual do shell.

No entanto, com bash 4.2e mais recentes, você pode definir a lastpipeopção em scripts não interativos para obter o resultado esperado, por exemplo:

#!/bin/bash

echo 1 2 3 4 5 | read a b dump
echo before: $b $a 
shopt -s lastpipe
echo 1 2 3 4 5 | read a b dump
echo after: $b $a 

Saída:

before:
after: 2 1
jlliagre
fonte
11
+1 obrigado pela informação "lastpipe". desculpe pelo atraso #
489 Emanuel Em
11
o problema lastpipeé que ele não funciona em outras conchas (por exemplo, traço). há basicamente nenhuma maneira de fazer este pequeno portátil de executar tudo em que subshell, consulte stackoverflow.com/questions/36268479/...
anarcat
11
@anarcat Isso está correto, mas a pergunta feita aqui era sobre bash.
jlliagre
@anarcat: Isso pode mudar no futuro uma vez que há um desejo de mudar POSIX por outra razão (Status PIPE) veja aqui: unix.stackexchange.com/questions/476834/... Alterar outras conchas não é trivial, ele me levou vários meses reescrever o analisador e o interpretador no Bourne Shell (bosh) para implementar o comportamento mais rápido do ksh moderno.
21918 schily