Até que comecei a responder a essa pergunta, não percebi que usar o &
operador de controle para executar um trabalho em segundo plano inicia um subshell. Subshells são criados quando os comandos são colocados entre parênteses ou fazem parte de um pipeline (cada comando em um pipeline é executado em sua própria sub-rede).
A seção Listas de Comandos do manual de Bash ( obrigado jimmij ) afirma:
If a command is terminated by the control operator ‘&’, the shell executes the command asynchronously in a subshell. This is known as executing the command in the background. The shell does not wait for the command to finish, and the return status is 0 (true).
Pelo que entendi, quando você executa sleep 10 &
o garfo s do shell para criar um novo filho processo (uma cópia de si mesmo) e, em seguida, imediatamente exec s para substituir esse processo filho pelo código da parte externa comando ( sleep
). Isso é semelhante ao que acontece quando um comando é executado normalmente (em primeiro plano). Veja o artigo do Wikipedia sobre o fork-exec para uma breve descrição deste mecanismo.
Eu não conseguia entender por que o Bash executaria comandos em segundo plano em um subshell, mas faz sentido se você também deseja executar os shell builtins como exit
ou echo
para serem executados em segundo plano (não apenas comandos externos).
Quando se trata de um shell embutido em execução em segundo plano, o fork
acontece (resultando em um subshell) sem uma chamada exec
para substituir-se por um comando externo. A execução dos comandos a seguir mostra que quando o comando echo
é empacotado em chaves e executado em segundo plano (com o &
), uma subshell é realmente criada:
$ { echo $BASH_SUBSHELL $BASHPID; }
0 21516
$ { echo $BASH_SUBSHELL $BASHPID; } &
[1] 22064
$ 1 22064
No exemplo acima, coloquei o comando echo
em chaves para evitar que BASH_SUBSHELL
seja expandido pelo shell atual; chaves são usadas para agrupar comandos sem usar um subshell. A segunda versão do comando (terminando com o operador &
control) demonstra claramente que terminar o comando com o "e" comercial resultou em uma subshell (com um novo PID) sendo criada para executar o echo
builtin. (estou provavelmente simplificando o comportamento do shell aqui. Veja o comentário do mikeserv.)
Eu nunca teria pensado em executar exit &
e se eu não tivesse lido sua pergunta, eu esperaria que o shell atual fosse encerrado. Sabendo agora que esses comandos são executados em uma subcamada, sua explicação é de que é a subshell que sai faz sentido.
“Por que a subshell criada pelo operador de controle de plano de fundo (&) não é exibida em pstree”
Como mencionado acima, quando você executa sleep 10 &
, o Bash se bifurca para criar o subshell, mas como sleep
é um comando externo, ele chama a chamada de sistema exec()
que substitui imediatamente o código Bash e os dados no filho. processo com uma cópia em execução do programa sleep
. No momento em que você executar pstree
, a chamada exec
já terá sido concluída e o processo filho agora terá o nome “ sleep ”.
Enquanto estava longe do meu computador, tentei pensar em uma maneira de manter o subshell em execução por tempo suficiente para que o subshell fosse exibido por pstree
. Eu imaginei que poderíamos executar o comando através do time
builtin:
$ time sleep 11 &
[2] 4502
$ pstree -p 26793
bash(26793)─┬─bash(4502)───sleep(4503)
└─pstree(4504)
Aqui, o shell Bash (26793) se bifurca para criar um subshell (4502) para executar o comando em segundo plano. Este subshell executa seu próprio comando time
builtin que, por sua vez, se bifurca (para criar um novo processo com PID 4503) e executa execs para executar o comando sleep
externo.
Usando pipes nomeados , jimmij criou uma maneira inteligente de manter a sub-sela criada para executar exit
vivo por tempo suficiente para que seja exibido por pstree
:
$ mkfifo file
$ exit <file &
[2] 6413
$ pstree -p 26793
bash(26793)─┬─bash(6413)
└─pstree(6414)
$ echo > file
$ jobs
[2]- Done exit < file
Redirecionar stdin
de um pipe nomeado é inteligente, pois faz com que o subshell bloqueie até receber entrada do pipe nomeado. Posteriormente, redirecionar a saída de echo
(sem nenhum argumento) grava um caractere de nova linha no canal nomeado que desbloqueia o processo de subshell que, por sua vez, executa o comando exit
builtin.
Da mesma forma, para o comando sleep
:
$ mkfifo named_pipe
$ sleep 11 < named_pipe &
[1] 6600
$ pstree -p 26793
bash(26793)─┬─bash(6600)
└─pstree(6603)
Aqui vemos que o subshell criado para executar o comando em segundo plano tem um PID de 6600
. Em seguida, desbloqueamos o processo escrevendo um caractere de nova linha no pipe:
$ echo > named_pipe
A subshell então exec
s para executar o comando sleep
.
$ pstree -p 26793
bash(26793)─┬─pstree(6607)
└─sleep(6600)
Após a chamada exec()
, podemos ver que o processo filho ( 6600
) agora está executando o programa sleep
.