Ok, eu sei que no Bash (por padrão, sem a opção bash 'lastpipe' habilitada) toda variável atribuída após um pipeline é, de fato, executada em um subshell e a própria variável morre após a execução do subshell. Ele não permanece disponível para o processo pai. Mas fazendo alguns testes, descobri este comportamento:
A) Segundo comando (a=2) atribui o valor e é retornado:
[root@centos01]# a=1; a=2; a=10 | echo $a
2
B) Terceiro comando (a=10) atribui o valor e é retornado:
[root@centos01]# a=1; a=2; a=10; a=20 | echo $a
10
C) Quarto comando (a=20) atribui o valor e é retornado:
[root@centos01]# a=1; a=2; a=10; a=20; touch fileA.txt | echo $a
20
Então:
Por que sempre a última atribuição de variável na sequência do comando não é realmente executada? (ou se for, por que não está sendo capturado pelo subshell e retornado pelo comando echo?)
No teste C, o comando ‘touch’ realmente criou o arquivo ‘fileA.txt’ no diretório. Então, por que o último comando da sequência de atribuição de variáveis feita no passo A e no conjunto B não funcionou? Alguém sabe a explicação técnica para isso?
Responder1
Primeiro, para concordar com alguns nomes, veja como o shell interpreta sua entrada:
$ a=1; a=2; a=10 | echo $a
^^^ ^^^ ^^^^^^^^^^^^^^
\ \ \_ Pipeline
\ \_ Simple command
\_ Simple command
O pipeline é composto por dois comandos simples:
$ a=10 | echo $a
^^^^ ^^^^^^^
\ \_ Simple command
\_ Simple command
(Observe que, embora possa não estar claramente indicado no manual do Bash, oGramática de shell POSIXpermite que um simples comando seja constituído de uma mera atribuição de variável).
a=1;
e a=2;
não fazem parte de nenhum pipeline. A ;
encerraria um pipeline, exceto quando aparecesse como parte de umcomando composto. Como em, por exemplo:
{ a=1; a=2; a=10; } | echo $a
No seu exemplo, a=10
e echo $a
são executados emdois distintos, ambientes de subshell independentes 1 , ambos criados como cópias do ambiente principal. Os subshells são obrigados a não alterar seu ambiente de execução pai 2 . Citando o relevanteSeção POSIX:
Um ambiente subshell deve ser criado como uma duplicata do ambiente shell [...] As alterações feitas no ambiente subshell não devem afetar o ambiente shell.
e
Além disso, cada comando de um pipeline multicomando está em um ambiente subshell; como uma extensão, entretanto, qualquer um ou todos os comandos em um pipeline podem ser executados no ambiente atual. Todos os outros comandos devem ser executados no ambiente shell atual.
Assim, embora todos os comandos em seus exemplos sejam realmente executados, as atribuições na parte esquerda de seus pipelines não têm efeito visível: elas apenas alteram as cópias em a
seus respectivos ambientes de subshell, que são perdidas assim que os subshells terminam.
A única maneira de os subcascos nas duas extremidades de um tubo interagirem diretamente entre si é por meio do próprio tubo – a saída padrão do lado esquerdo conectada à entrada padrão do lado direito. Como a=10
não envia nada pelo canal, não há como isso afetar echo $a
.
1 Se a lastpipe
opção estiver definida (está desativada por padrão e pode ser habilitada usando o shopt
built-in), o Bash poderá executar o último comando de um pipeline no shell atual. VerGasodutosno manual do Bash. No entanto, isso não é relevante no contexto da sua pergunta.
2 Você pode encontrar mais detalhes de uma perspectiva prática/histórica sobre U&L, por exemplo, emesta respostaparaPor que a última função executada em um pipeline de script de shell POSIX não retém valores de variáveis?
Responder2
Por favor, desculpe meu inglês, ainda estou aprendendo. Também sou um aluno iniciante em Bash, então corrija quaisquer erros que cometi em minha resposta, obrigado.
Primeiro, vou apontar um erro.
Em
a=10 | echo $a
você está canalizando (usando o operador pipe;|
)a=10
para ecoar o comando. A tubulação conectará ostdout
comando astdin
ao comando2, ou sejacommand | command2
.a=10
é uma atribuição de variável, presumo que não existestdout
, pois não é um comando. Se você realizar uma substituição de comando no valor de uma atribuição de variável, isso não funcionará. Se eu tentar o seguinte:user@host$ a=$(b=10); echo $a
o
echo $a
não retorna o valor10
. Quando eu modifiquei parauser@host$ a=$(b=10; echo $b)
uma chamada de
$ echo $a
devolvida
10
. Então, provavelmente estou certo ao assumir que a atribuição de variável não é um comando (mesmo pela definição manual do bash, não é um comando).
Em segundo lugar, o echo
comando não recebe entrada stdin
, ele imprime seus argumentos.
user@host$ echo "I love linux" | echo
não retornará nada. Você pode usar xargs
o comando para superar isso:
user@host$ echo "I love linux" | xargs echo
retornará I love linux
. Portanto, o piping não funciona diretamente no echo
comando, pois imprime seus argumentos e não stdin
.
Agora, para seus testes
No seu comando
user@host$ a=1; a=2; a=10 | echo $a
a
o valor é atribuído inicialmente à variável1
e, em seguida, o valor da variável é alterado para2
, ambos no ambiente shell atual. Os comandos geralmente são executados em sub-shell.a=10 | echo $a
é uma lista, ou seja, equivalente à(a=10 | echo $a)
que é executada em um sub-shell, mas não funciona comoecho
não levastdin
, mas apenas imprime seu argumento. Aqui, o argumento é$a
, o valor da variávela
no subshell que é2
.Além disso,
a=10
não produz nenhuma saída, pois é uma atribuição variável. Então, na verdade,echo $a
está imprimindo o valor do seu argumento which is2
e não tirando nada dea=10 | < ... >
. Portanto, o operador pipe não deve ser usado aqui; em vez disso, você pode separar o nome do comando e a atribuição da variável com um terminador (, ponto e vírgula) e funcionará bem, como em(a=10; echo $a)
.
Para entender isso melhor, você pode tentar estes exemplos a seguir com a opção de depuração bash habilitada:
user@host$ a=1; a=2; a=10; echo $a;
Na linha de comando acima,
echo $a
produz10
.Se eu mudar para
user@host$ a=1; a=2; (a=10; echo $a)
a primeira e a segunda atribuição de variável são definidas no shell atual, a terceira atribuição de variável e
echo
o comando são executados em um subshell. Portanto, o valor dea
está10
no sub-shell em que o comandoecho
também é executado, portanto retorna10
. Depois de receber seu prompt, se você emitir o comandoecho $a
, ele retornará2
, pois as atribuições de variáveis do subshell não serão transportadas de volta para o shell pai.- Mais uma coisa a observar: o
;
separador de comandos executa comandos sequencialmente.
Nos casos de teste "A" e "B", a última atribuição de variável ( a=10
no teste A e a=20
no teste B) é realmente executada, mas é executada após o echo $a
comando, então você obtém o resultado do valor anterior da variável a
que está em o ambiente sub-shell, após o qual as últimas atribuições de variáveis são executadas. Em um pipeline, os stdin
e stdout
de dois comandos são conectados antes da execução do comando; também as atribuições de variáveis não produzem nada no stdout.
dr.: A atribuição de variáveis não deve ser usada em um pipeline. echo
não funciona diretamente em um pipeline.
Responder3
Aqui,
a=20 | echo $a
o cano apenas adiciona confusão. A atribuição à esquerda não imprime nada no stdout e echo
não lê nada do stdin, portanto, nenhum dado é movido pelo canal. O argumento para echo
é apenas expandido a partir do que foi definido anteriormente.
Como você disse, as partes de um pipeline são executadas em subcamadas distintas, portanto a atribuição no a
lado esquerdo não influencia a expansão no lado direito, e tal comunicação também não aconteceria no caso inverso.
Em vez disso, se você fez isso:
{ a=999; echo a=$a; } | cat
o cano faria mais sentido e o barbante a=999
passaria por ele.
O touch fileA.txt
último exemplo funciona porque afeta o sistema fora do shell. Da mesma forma, você poderia escrever para stderr a partir dos comandos no pipe e ver o resultado aparecer no terminal:
$ echo a >&2 | echo b >&2 | echo c >&2
b
c
a
(Essa é realmente a ordem de saída que obtive com o Bash no meu sistema.)