Qual é a diferença exata entre um "subshell" e um "processo filho"?

16

De acordo com isso e isso , um subshell é iniciado usando parênteses (…).

( echo "Hello" )

De acordo com isto , isto e isto , um processo é bifurcado quando o comando é iniciado com um&

echo "Hello" &

A especificação Posix usa a palavra subshellnesta página, mas não a define e, também, na mesma página, não define "processo filho" .

Ambos estão usando a fork()função kernel , correto?

Qual é a diferença exata então chamar alguns garfos de "subcasca" e outros garfos de "processo filho".

NotAnUnixNazi
fonte
Não está claro por que você está vinculando o POSIX Racional: Definições de Base em vez das próprias Definições de Base : 3.93 Processo Filho "Um novo processo criado (por fork (), posix_spawn () ou ...) por um determinado processo" ; 3.376 Subshell "Um ambiente de execução de shell, distinto do ambiente de execução de shell principal ou atual" . Portanto, não instâncias do mesmo tipo de coisa. Essa é a distinção que você está procurando?
fra-san
@ fra-san A child processpoderia ter um ambiente distinto do que main: Como em ( LANG=C eval 'echo "$LANG"' ). Esse processo filho (entre parênteses) também é um subshell (ambiente diferente)?
NotAnUnixNazi 4/03/19
A expressão in ( )é, por definição, um subshell com seu próprio ambiente de execução. Meu argumento é que não é necessário que um subshell seja implementado como um processo filho (como Stéphane aponta em sua resposta com o exemplo ksh93). Parece que o subshell e o processo filho não precisam ser os dois resultados de uma fork()chamada; portanto, procurar a diferença entre dois tipos de garfo não parece o ponto de vista certo para mim. Por isso estava tentando entender melhor sua pergunta.
fra-san
Ah, agora vejo que a página tldp à qual você vinculou realmente diz que um subshell é um processo filho. Na minha opinião, essa definição é uma simplificação possivelmente enganosa.
fra-san

Respostas:

15

Na terminologia POSIX, um ambiente de subcasca está vinculado à noção de Shell Execution Environment .

Um ambiente de subshell é um ambiente de execução de shell separado, criado como uma duplicata do ambiente pai. Esse ambiente de execução inclui coisas como arquivos abertos, umask, diretório de trabalho, variáveis ​​/ funções / aliases do shell ...

As alterações nesse ambiente de subcasca não afetam o ambiente pai.

Tradicionalmente no shell Bourne ou no ksh88 no qual a especificação POSIX se baseia, isso era feito bifurcando um processo filho.

As áreas em que o POSIX requer ou permite a execução de comandos em um ambiente de subcamadas são aquelas em que o ksh88 tradicionalmente bifurcava um processo de shell filho.

No entanto, não força as implementações a usar um processo filho para isso.

Um shell pode optar por implementar esse ambiente de execução separado da maneira que desejar.

Por exemplo, o ksh93 faz isso salvando os atributos do ambiente de execução pai e restaurando-os após o término do ambiente do subshell em contextos onde a bifurcação pode ser evitada (uma otimização da bifurcação é bastante cara na maioria dos sistemas).

Por exemplo, em:

cd /foo; pwd
(cd /bar; pwd)
pwd

O POSIX exige cd /fooque seja executado em um ambiente separado e que produza algo como:

/foo
/bar
/foo

Não requer que ele seja executado em um processo separado. Por exemplo, se stdout se tornar um pipe quebrado, a pwdexecução no ambiente de subshell pode muito bem ter o SIGPIPE enviado para o primeiro e único processo de shell.

A maioria dos shells, inclusive bash, o implementará avaliando o código interno (...)em um processo filho (enquanto o processo pai aguarda sua finalização), mas o ksh93 fará isso ao executar o código interno (...), tudo no mesmo processo:

  • lembre-se de que está em um ambiente subshell.
  • em cdseguida, salve o diretório de trabalho anterior (geralmente em um descritor de arquivo aberto com O_CLOEXEC), salve o valor das variáveis ​​OLDPWD, PWD e qualquer coisa que cdpossa modificar e faça ochdir("/bar")
  • ao retornar do subshell, o diretório de trabalho atual é restaurado (com um fchdir()no fd salvo) e tudo o mais que o subshell pode ter modificado.

Existem contextos em que um processo filho não pode ser evitado. O ksh93 não entra em:

  • var=$(subshell)
  • (subshell)

Mas faz em

  • { subshell; } &
  • { subshell; } | other command

Ou seja, os casos em que as coisas precisam ser executadas em processos separados para que possam ser executadas simultaneamente.

As otimizações do ksh93 vão além disso. Por exemplo, enquanto em

var=$(pwd)

a maioria dos shells faria bifurcar um processo, fazer o filho executar o pwdcomando com seu stdout redirecionado para um canal, pwdgravar o diretório de trabalho atual nesse canal e o processo pai ler o resultado na outra extremidade do canal, ksh93virtualizando tudo isso exigindo o garfo nem o tubo. Um garfo e tubo só seriam usados ​​para comandos não integrados.

Observe que existem outros contextos que subshells para os quais shells bifurcam um processo filho. Por exemplo, para executar um comando que é armazenado em um executável separado (e que não é um script destinado ao mesmo interpretador de shell), um shell teria que bifurcar um processo para executar esse comando nele, caso contrário, não seria capaz de executar mais comandos depois que esse comando retornar.

Dentro:

/bin/echo "$((n += 1))"

Isso não é um subshell, o comando será avaliado no atual ambiente de execução do shell, a nvariável do atual ambiente de execução do shell será incrementada, mas o shell bifurcará um processo filho para executar esse /bin/echocomando nele com a expansão de $((n += 1))como argumento .

Muitos shells implementam uma otimização na medida em que não bifurcam um processo filho para executar esse comando externo, se for o último comando de um script ou um subshell (para os subshells implementados como processos filhos). ( bashno entanto, somente o faz se esse comando for o único comando do subshell).

O que isso significa é que, com esses shells, se o último comando no subshell for um comando externo, o subshell não fará com que um processo extra seja gerado. Se você comparar:

a=1; /bin/echo "$a"; a=2; /bin/echo "$a"

com

a=1; /bin/echo "$a"; (a=2; /bin/echo "$a")

haverá o mesmo número de processos criados; somente no segundo caso, o segundo fork será feito anteriormente, para que a=2seja executado em um ambiente de subshell.

Stéphane Chazelas
fonte
1

Subshell

O shell filho também é chamado de subshell. O subshell pode ser criado a partir do shell pai e de outro shell. O subshell pode ser criado usando:

1. Lista de processos

Uma lista de processos é um agrupamento de comandos entre parênteses. Exemplo:

( pwd ; (echo $BASH_SUBSHELL)) 

Isso imprimirá o diretório de trabalho atual e o número de shell gerado. OBSERVAÇÃO A chamada do subshell é cara.

2. Coprocesso

Ele gera um subshell no modo de segundo plano e executa um comando dentro desse subshell.

coproc sleep 10

Se você digitar jobscomando

[1]+  Running                 coproc COPROC sleep 10 &

você verá o sono como processo em segundo plano sendo executado em segundo plano.

Bifurcando um processo filho

Um processo filho em computação é um processo criado por outro processo. Sempre que um comando externo é executado, um processo filho é criado. Esta ação é denominada bifurcação.

$ps -f
UID        PID  PPID  C STIME TTY          TIME CMD  
umcr7     3647  3638  0 13:54 pts/0    00:00:00 bash
umcr7     3749  3647  0 13:59 pts/0    00:00:00 ps -f

Assim ps -fcomo o comando externo (isto é, um comando externo, às vezes chamado de comando do sistema de arquivos, é um programa que existe fora do shell bash.) Isso criará um processo filho com o ID pai do shell bash a partir do qual é executado.

Muhammad Umar Amanat
fonte
0

Ambos (subshell e shell filho) são um processo separado do shell pai (ambos são filhos do shell pai). Ou seja, eles têm PIDs diferentes. E ambos começam com um garfo (cópia) do shell pai.

Um subshell é uma cópia do shell pai, na qual variáveis, funções, sinalizadores e tudo estão disponíveis como estavam no shell pai. Modificações de tais valores não afetam o pai.

Um shell filho inicia como um fork, mas é redefinido para os valores padrão do shell fornecidos pelas configurações de início. Torna-se um processo usado para executar algum código (um shell ou um comando).

Um subshell pode acessar valores variáveis:

$ x=123; ( echo "$x")
123

Um shell filho não pôde (variáveis ​​não exportadas):

$ x=234; sh -c 'echo "x=$x"'
x=
NotAnUnixNazi
fonte