Criação de subshell Bash com chaves

31

De acordo com isso , a colocação de uma lista de comandos entre chaves faz com que a lista seja executada no contexto atual do shell. Nenhum subshell é criado .

Usando pspara ver isso em ação

Esta é a hierarquia de processos para um pipeline de processos executado diretamente na linha de comandos. 4398 é o PID para o shell de login:

sleep 2 | ps -H;
  PID TTY          TIME CMD
   4398 pts/23   00:00:00 bash
   29696 pts/23   00:00:00   sleep
   29697 pts/23   00:00:00   ps

Agora segue a hierarquia do processo para um pipeline de processo entre chaves executadas diretamente na linha de comando. 4398 é o PID para o shell de logon. É semelhante à hierarquia acima, provando que tudo é executado no contexto atual do shell :

{ sleep 2 | ps -H; }
   PID TTY          TIME CMD
    4398 pts/23   00:00:00 bash
    29588 pts/23   00:00:00   sleep
    29589 pts/23   00:00:00   ps

Agora, essa é a hierarquia do processo quando o sleeppróprio pipeline é colocado dentro de chaves (portanto, dois níveis de chaves)

{ { sleep 2; } | ps -H; }
  PID TTY          TIME CMD
   4398 pts/23   00:00:00 bash
   29869 pts/23   00:00:00   bash
   29871 pts/23   00:00:00     sleep
   29870 pts/23   00:00:00   ps

Por que é bashnecessário criar um subshell para execução sleepno terceiro caso, quando a documentação declara que os comandos entre chaves são executados no contexto atual do shell?

iruvar
fonte
Interessante, eu acho que é porque no terceiro caso o grupo interno faz parte do pipeline e, portanto, é executado no sub-shell, por exemplo, como qualquer outra chamada de função que faça parte do pipeline. Isso faz sentido?
Miroslav Koškár
2
Eu não diria "O shell deve" apenas porque ... Os pipelines não são executados no contexto do shell. Se o pipeline consistir em nada além de comandos externos, basta criar subprocessos. { sleep 2 | command ps -H; }
Hauke ​​Laging

Respostas:

26

Em um pipeline, todos os comandos são executados simultaneamente (com seus stdout / stdin conectados por pipes), portanto, em diferentes processos.

Em

cmd1 | cmd2 | cmd3

Todos os três comandos são executados em processos diferentes, portanto, pelo menos dois deles precisam ser executados em um processo filho. Alguns shells executam um deles no processo atual do shell (se embutido como readou se o pipeline é o último comando do script), mas bashexecuta todos eles em seu próprio processo separado (exceto com a lastpipeopção nas bashversões recentes e sob algumas condições específicas )

{...}agrupa comandos. Se esse grupo fizer parte de um pipeline, ele deverá ser executado em um processo separado, como um simples comando.

Em:

{ a; b "$?"; } | c

Precisamos de um shell para avaliar que a; b "$?"é um processo separado, por isso precisamos de um subshell. O shell pode otimizar não sendo bifurcado, bjá que é o último comando a ser executado nesse grupo. Algumas conchas fazem isso, mas aparentemente não bash.

Stéphane Chazelas
fonte
" Todos os três comandos são executados em processos diferentes, portanto, pelo menos dois deles precisam ser executados em um subshell. " Por que um subshell é necessário neste caso? O shell pai não pode gerar processos em árvore? Ou, quando você diz " precisa executar em um subshell ", você quer dizer que o shell se bifurca e executa exec para cada cmd1 cmd2 e cmd3? Se eu executar isso, bash -c "sleep 112345 | cat | cat "vejo apenas uma festança criada e, em seguida, três filhos sem nenhuma outra festança secundária.
Hakan Baba
" Precisamos de um shell para avaliar que a; b" $? "É um processo separado, portanto precisamos de um subshell. " Você também pode expandir o raciocínio? Por que precisamos de uma sub-roda para entender isso? O que é preciso para entender isso? Presumo que a análise é necessária, mas o que mais? . O shell pai não pode analisar a; b "$?"? Existe realmente uma necessidade fundamental de um subsheel, ou talvez seja uma decisão / implementação de projeto no bash?
Hakan Baba
@HakanBaba, mudei isso para "processo filho" para evitar possíveis confusões.
Stéphane Chazelas
1
@HakanBaba, a análise é feita no pai (o processo que lê o código, aquele que executou o intérprete, a menos que esse código tenha sido passado eval), mas a avaliação (execute o primeiro comando, aguarde, execute o segundo) é feito na criança, aquela que está desconectada do tubo.
Stéphane Chazelas
no { sleep 2 | ps -H; }bash pai vê sleep 2que requer um fork / exec. Mas no { { sleep 2; } | ps -H; }bash pai vê { sleep 2; }em outras palavras, algum código bash. Parece que o pai pode manipular o fork / exec, sleep 2mas gera um novo bash recursivamente para manipular o código do bash encontrado. Esse é o meu entendimento, isso faz sentido?
Hakan Baba
19

Aninhar os chavetas parece indicar que você está criando um nível adicional de escopo, que exige que uma nova subcasca seja invocada. Você pode ver esse efeito com a segunda cópia do Bash em sua ps -Hsaída.

Somente os processos estipulados no primeiro nível de chaves são executados no escopo do shell Bash original. Qualquer aparelho cacheado aninhado será executado em seu próprio shell Bash com escopo.

Exemplo

$ { { { sleep 20; } | sleep 20; } | ps -H; }
  PID TTY          TIME CMD
29190 pts/1    00:00:00 bash
 5012 pts/1    00:00:00   bash
 5014 pts/1    00:00:00     bash
 5016 pts/1    00:00:00       sleep
 5015 pts/1    00:00:00     sleep
 5013 pts/1    00:00:00   ps

Retirando a | ps -Hmistura apenas para que possamos ver as chaves aninhadas, podemos executar ps auxf | lessem outro shell.

saml     29190  0.0  0.0 117056  3004 pts/1    Ss   13:39   0:00  \_ bash
saml      5191  0.0  0.0 117056  2336 pts/1    S+   14:42   0:00  |   \_ bash
saml      5193  0.0  0.0 107892   512 pts/1    S+   14:42   0:00  |   |   \_ sleep 20
saml      5192  0.0  0.0 107892   508 pts/1    S+   14:42   0:00  |   \_ sleep 20
saml      5068  0.2  0.0 116824  3416 pts/6    Ss   14:42   0:00  \_ bash
saml      5195  0.0  0.0 115020  1272 pts/6    R+   14:42   0:00      \_ ps auxf
saml      5196  0.0  0.0 110244   880 pts/6    S+   14:42   0:00      \_ less

Mas espere, tem mais!

Se você remover os pipes e usar esta forma de comando, veremos o que você realmente espera:

$ { { { sleep 10; } ; { sleep 10; } ; sleep 10; } } | watch "ps -H"

Agora, na janela de inspeção resultante, temos uma atualização a cada 2 segundos do que está acontecendo:

Aqui está o primeiro sleep 10:

  PID TTY          TIME CMD
29190 pts/1    00:00:00 bash
 5676 pts/1    00:00:00   bash
 5678 pts/1    00:00:00     sleep
 5677 pts/1    00:00:00   watch
 5681 pts/1    00:00:00     watch
 5682 pts/1    00:00:00       ps

Aqui está o segundo sleep 10:

  PID TTY          TIME CMD
29190 pts/1    00:00:00 bash
 5676 pts/1    00:00:00   bash
 5691 pts/1    00:00:00     sleep
 5677 pts/1    00:00:00   watch
 5694 pts/1    00:00:00     watch
 5695 pts/1    00:00:00       ps

Aqui está o terceiro sleep 10:

  PID TTY          TIME CMD
29190 pts/1    00:00:00 bash
 5676 pts/1    00:00:00   bash
 5704 pts/1    00:00:00     sleep
 5677 pts/1    00:00:00   watch
 5710 pts/1    00:00:00     watch
 5711 pts/1    00:00:00       ps

Observe que todos os três dormes, embora invocados em diferentes níveis de aninhamento de chaves, permanecem dentro do PID 5676 do Bash. Portanto, acredito que seu problema é auto-infligido com o uso de | ps -H.

Conclusões

O uso de | ps -H(ou seja, o tubo) está causando uma subcama adicional, portanto, não use esse método ao tentar interrogar o que está acontecendo.

slm
fonte
portanto, "Somente os processos estipulados no primeiro nível de chaves são executados no escopo do shell Bash original".
xealits
@xealits - isso é um acompanhamento Q você está me perguntando?
slm
@ SLM é apenas a ênfase no ponto principal da resposta, como eu vi. Comandos no primeiro nível de chavetas executadas no shell atual, chavetas aninhadas criam novas conchas. Os parênteses diferem na criação do subshell imediatamente, no primeiro nível. Se eu entendi errado - me corrija. Mas, como outros apontam, a pergunta inicial também tem pipelines. Assim, a criação de processos separados. E os chavetas precisam criar um shell quando usados ​​para um processo separado. Então, provavelmente, é a razão do comportamento em questão.
Xlits #
agora, depois de reler sua postagem, vejo que eu estava errado - a declaração de aninhamento refere-se apenas ao caso dos pipelines. Portanto, as chaves nunca criam novas conchas, a menos que você envolva um processo separado com elas - então elas precisam.
Xealits #
@xealits - está correto.
slm
7

Vou postar os resultados dos meus testes, o que me leva a concluir que o bash cria um sub-shell para um comando de grupo se, e somente se for parte do pipeline, é semelhante como se alguém chamasse alguma função que também seria chamada no sub-shell.

$ { A=1; { A=2; sleep 2; } ; echo $A; }
2

$ { A=1; { A=2; sleep 2; } | sleep 1; echo $A; }
1
Miroslav Koškár
fonte
Meu A também está mostrando isso.
slm