Criação de subshell Bash com chaves

28

De acordo com este , colocar uma lista de comandos entre chaves faz com que a lista seja executada no contexto atual da shell. Nenhum subshell é criado .

Usando ps para ver isso em ação

Esta é a hierarquia de processo para um pipeline de processo executado diretamente na linha de comando. 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 login. É semelhante à hierarquia acima, provando que tudo é executado no contexto de shell atual :

{ 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 sleep no pipeline é colocado dentro de chaves (portanto, dois níveis de chaves ao todo)

{ { 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 bash tem que criar uma subshell para executar sleep no terceiro caso quando a documentação informa que os comandos entre chaves são executados no contexto de shell atual?

    
por iruvar 30.04.2014 / 20:13

3 respostas

25

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

Em

cmd1 | cmd2 | cmd3

Todos os três comandos são executados em diferentes processos, pelo menos dois deles devem ser executados em um processo filho. Alguns shells executam um deles no processo de shell atual (se incorporado como read ou se o pipeline for o último comando do script), mas bash os executa em seu próprio processo separado (exceto com o lastpipe opção nas versões bash recentes e sob algumas condições específicas).

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

Em:

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

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

    
por 30.04.2014 / 23:18
19

Aninhar as chaves parece indicar que você está criando um nível adicional de escopo que requer que uma nova sub-shell seja invocada. Você pode ver esse efeito com a segunda cópia do Bash na sua saída ps -H .

Apenas os processos estipulados no primeiro nível de chaves são executados dentro do escopo do shell Bash original. Todas as chaves aninhadas serão executadas em seu próprio shell Bash com escopo definido.

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 o | ps -H do mix apenas para que possamos ver as chaves aninhadas, podemos executar ps auxf | less em 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 mais!

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

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

Agora, na janela de exibição resultante, recebemos uma atualização a cada 2 segundos sobre o 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 períodos de inatividade, embora invocados em diferentes níveis de encadeamento, mantêm-se dentro do PID 5676 do Bash. Então eu acredito que o seu problema é auto-infligido com o uso de | ps -H .

Conclusões

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

    
por 30.04.2014 / 20:40
7

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

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

$ { A=1; { A=2; sleep 2; } | sleep 1; echo $A; }
1
    
por 30.04.2014 / 20:56

Tags