Недавно я узнал, что «подоболочка» — это не то же самое, что «процесс дочерней оболочки» (см., например, В чем точная разница между "подоболочка" и "дочерний процесс"? и определения POSIX подоболочка и дочерний процесс ).

Чтобы убедиться в этом, я ищу команду, которая иллюстрирует (доказывает), что подоболочка создается без создания дочерней оболочки.

На данный момент все, что я пробовал, казалось, порождало дочернюю оболочку всякий раз, когда создается подоболочка:

$ echo $BASHPID; (pwd; cd ..; echo $BASHPID; pwd); pwd      # `( ...)` executed in a subshell
                                                            # and in a child-shell process

$ >&2 ps | ps       # Theoretically executed in two subshells and apparently without child-shells
                    # but I cannot be sure due to the outcome of the next example

$ $ >&2 echo $BASHPID | ps      # `ps` doesn't display a child-shell for the execution of `echo`
953790                          # but `echo $BASHPID` shows a new process that is necessarily
    PID TTY         TIME CMD    # a child-shell since echo is a built-in 
 948538 pts/2   00:00:00 bash
 953791 pts/2   00:00:00 ps

Я ищу способ продемонстрировать, что наличие подоболочки не обязательно означает наличие дочерней оболочки...

Баш 5.0.17

0
The Quark 14 Ноя 2021 в 02:11
1
В bash подоболочка всегда является отдельным процессом. Не так в других оболочках, таких как ksh93. Так что вы зря тратите время, если ожидаете пример для Bash 5.0.17.
 – 
Uncle Billy
12 Ноя 2021 в 01:12
В ksh93 очень простой пример подоболочки без подпроцесса: a=3; (a=12); echo $a. Верно и обратное: по крайней мере, в Linux можно реализовать несколько процессов в одной среде подоболочки (т. е. cd может быть внешней командой, работающей в отдельном процессе, и при этом изменять cwd своего родителя). Но, насколько мне известно, ни одна из основных оболочек этого не использует.
 – 
Uncle Billy
12 Ноя 2021 в 01:19

2 ответа

Лучший ответ

В оболочке bash подоболочки реализуются путем разветвления дочернего процесса, поэтому вы не увидите случая, когда подоболочка не работает в дочернем процессе в этой оболочке.

Ksh93 — единственная известная мне оболочка, которая пропускает разветвление, когда это возможно, для подоболочек (оптимизация, которая все еще довольно глючна, и последовательные люди, которые пытались поддерживать ее после того, как AT&T распустила команду, написавшую ее, рассматривали возможность ее удаления).

Если вы сделаете, например:

 strace ksh93 -c 'pwd; (cd /; umask 0; pwd; exit 2); pwd'

Вы увидите, что ksh93 не разветвляет какой-либо процесс, а вместо этого делает что-то вроде этого:

openat(AT_FDCWD, ".", O_RDONLY|O_PATH)  = 3
fcntl(3, F_DUPFD, 10)                   = 10
close(3)                                = 0
fcntl(10, F_SETFD, FD_CLOEXEC)          = 0
[...]

Который сохраняет текущий каталог на fd 10. Затем:

chdir("/")                              = 0
umask(000)                              = 002

Что изменяет текущий каталог и umask в подоболочке. И после завершения подоболочки (exit 2 не вызывает системный вызов _exit()):

fchdir(10)                              = 0
close(10)                               = 0

Чтобы восстановить текущий рабочий каталог и:

umask(002)                              = 000

Восстановить умаск.

Некоторые оболочки, такие как sh FreeBSD, могут пропускать разветвление в особых случаях, например:

var=$(printf %04d "$n")

(здесь со встроенной printf, и там не делается никаких изменений в среде).

В конвейере все компоненты должны работать одновременно, поэтому они должны работать в отдельных процессах, даже в ksh93.

В bash все они выполняются в дочерних процессах. В AT&T ksh или zsh или с bash -O lastpipe (когда он не интерактивный) крайний правый нет (конечно, вам все равно нужно разветвить дочерний процесс для запуска внешних команд, таких как ps) .

Вы не видите дополнительный процесс bash в ps >&2 | ps или (ps), потому что ps выполняется непосредственно в этом дочернем процессе, который перед выполнением ps интерпретировал bash компонент конвейера: подоболочка. Например, в:

n=0; /bin/true "$((n=1))" | /bin/echo "$((n=2))"; echo "$n"

Вы увидите 2 и 0 в bash и 2 и 2 в zsh/ksh93. /bin/true и /bin/echo выполняются в дочерних процессах, /bin/true непосредственно в процессе подоболочки, который ранее выполнял n=1, то же самое в bash для /bin/echon=2), но в zsh/ksh/bash -O lastpipe n=2 выполнялся в основном процессе оболочки, а дочерний процесс разветвлялся только для выполнения этой внешней утилиты, точно так же, как когда вы запускаете /bin/echo "$((n=2))" не как часть конвейера.

В bash (в отличие от zsh/ksh) вы видите дополнительный процесс bash в (: anything; ps), оптимизация выполняется только в том случае, если подоболочка имеет только одна внешняя команда, вам нужно будет использовать exec, чтобы выполнить эту оптимизацию вручную: (: anything; exec ps).

То же самое касается { ps; } | cat.

4
Stéphane Chazelas 17 Ноя 2021 в 17:02
echo "$$" вы имели в виду echo "$n"? В bash -0 lastpipe я все еще вижу 2 объявление 0 (при условии, что echo "$n"), даже при использовании echo вместо /bin/echo. Но также в man bash говорится только, что "[с lastpipe] последний элемент конвейера может запускаться процессом оболочки", так что можно также ожидать, что здесь тот же результат даже с lastpipe. Я считаю, что эффект exec весьма показателен.
 – 
The Quark
13 Ноя 2021 в 01:28
Да, извините, должно было быть $n. Отредактировано сейчас.
 – 
Stéphane Chazelas
13 Ноя 2021 в 01:29

Я полагаю, что вы неправильно интерпретируете «подоболочку» и «подпроцесс» (также известный как дочерний процесс), и справочная страница bash не очень помогает в прояснении путаницы.

Подоболочка — это то, что вы получаете, когда текущая оболочка вызывает fork(). Форк создает подпроцессы; поэтому подоболочка является подпроцессом.

На странице руководства bash говорится, что bash «выполняет команды в подоболочке», но из этого неясно, что для запуска внешних процессов (например, ps) он затем вызывает exec() в подоболочке, заменяя работающая подоболочка bash с исполняемым файлом новой команды. В некоторых случаях, например, когда () используется в bash, подоболочка запускается ( и завершается при ), а промежуточные команды могут запускаться в своих собственных подоболочках/подпроцессах.

Единственный способ, которым подоболочка и дочерний процесс не совпадают, заключается в том, что дочерний процесс может быть или не быть (больше) другим экземпляром одного и того же исполняемого файла.

1
user10489 12 Ноя 2021 в 04:00
Я принял интерпретацию POSIX для subshell (" Среда выполнения оболочки, отличная от основной или текущей среды выполнения оболочки.") и дочерний процесс ("Новый процесс, созданный (с помощью fork(), posix_spawn() или posix_spawnp()) заданным процессом."). Но если bash систематически привязывает одно к другому, то действительно нет особого смысла пытаться их там различать.
 – 
The Quark
13 Ноя 2021 в 01:12
Из ответа ksh93 я бы сказал, что разница между подоболочкой и дочерним процессом - это просто деталь реализации, и почти для всех оболочек она не отличается.
 – 
user10489
13 Ноя 2021 в 01:59