Vários comandos e execução de subshell após pipeline

Vários comandos e execução de subshell após pipeline

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=10e echo $asã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 aseus 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=10não envia nada pelo canal, não há como isso afetar echo $a.


1 Se a lastpipeopção estiver definida (está desativada por padrão e pode ser habilitada usando o shoptbuilt-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 $avocê está canalizando (usando o operador pipe; |) a=10para ecoar o comando. A tubulação conectará o stdoutcomando a stdinao comando2, ou seja command | command2.

  • a=10é uma atribuição de variável, presumo que não existe stdout, 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 $anão retorna o valor 10. Quando eu modifiquei para

    user@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 echocomando não recebe entrada stdin, ele imprime seus argumentos.

user@host$ echo "I love linux" | echo

não retornará nada. Você pode usar xargso comando para superar isso:

user@host$ echo "I love linux" | xargs echo

retornará I love linux. Portanto, o piping não funciona diretamente no echocomando, 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
    

    ao valor é atribuído inicialmente à variável 1e, em seguida, o valor da variável é alterado para 2, 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 como echonão leva stdin, mas apenas imprime seu argumento. Aqui, o argumento é $a, o valor da variável ano subshell que é 2.

  • Além disso, a=10não produz nenhuma saída, pois é uma atribuição variável. Então, na verdade, echo $aestá imprimindo o valor do seu argumento which is 2e não tirando nada de a=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 $aproduz 10.

  • 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 echoo comando são executados em um subshell. Portanto, o valor de aestá 10no sub-shell em que o comando echotambém é executado, portanto retorna 10. Depois de receber seu prompt, se você emitir o comando echo $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=10no teste A e a=20no teste B) é realmente executada, mas é executada após o echo $acomando, então você obtém o resultado do valor anterior da variável aque está em o ambiente sub-shell, após o qual as últimas atribuições de variáveis ​​são executadas. Em um pipeline, os stdine stdoutde 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. echonã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 echonã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 alado 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=999passaria 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.)

informação relacionada