Различия под-оболочки между bash и ksh

Я всегда считал, что под-оболочка не является дочерним процессом, а другим среда оболочки в том же процессе.

я использую базовый набор встроенных модулей:

(echo "Hello";read)

на другой терминал:

ps -t pts/0
  PID TTY          TIME CMD
20104 pts/0    00:00:00 ksh

итак, нет дочернего процесса в kornShell (ksh).

введите bash, он, кажется, ведет себя по-разному, учитывая ту же команду:

  PID TTY          TIME CMD
 3458 pts/0    00:00:00 bash
20067 pts/0    00:00:00 bash

Итак, дочерний процесс в bash.
Из чтения man-страниц для bash, это очевидно, что для под-оболочки создается другой процесс, однако он подделывает $$, что является sneeky.

эта разница между bash и ksh ожидается, или я неправильно читаю симптомы?

Edit: дополнительная информация: Бег!--3--> на bash и ksh на Linux показывает, что bash вызывает clone дважды для команды sample (она не вызывает fork). Поэтому bash может использовать потоки (я пробовал ltrace а это дамп памяти!). KornShell называет ни fork, vfork, nor clone.

3 ответов


ksh93 работает необычно трудно, чтобы избежать подсхемы. Частью причины является избежание stdio и широкое использование sfio что позволяет builtins связывать сразу. Другая причина заключается в том, что KSH теоретически может иметь так много builtins. Если построено с SHOPT_CMDLIB_DIR, все встроенные cmdlib включены и включены по умолчанию. Я не могу дать полный список мест, где избегаются подсхемы, но обычно это происходит в ситуациях, когда используются только встроенные элементы и где нет переадресует.

#!/usr/bin/env ksh

# doCompat arr
# "arr" is an indexed array name to be assigned an index corresponding to the detected shell.
# 0 = Bash, 1 = Ksh93, 2 = mksh
function doCompat {
    ${1:+:} return 1
    if [[ ${BASH_VERSION+_} ]]; then
        shopt -s lastpipe extglob
        eval "[0]="
    else
        case "${BASH_VERSINFO[*]-${!KSH_VERSION}}" in
            .sh.version)
                nameref v=
                v[1]=
                if builtin pids; then
                    function BASHPID.get { .sh.value=$(pids -f '%(pid)d'); }
                elif [[ -r /proc/self/stat ]]; then
                    function BASHPID.get { read -r .sh.value _ </proc/self/stat; }
                else
                    function BASHPID.get { .sh.value=$(exec sh -c 'echo $PPID'); }
                fi 2>/dev/null
                ;;
            KSH_VERSION)
                nameref "_="
                eval "_[2]="
                ;&
            *)
                if [[ ! ${BASHPID+_} ]]; then
                    echo 'BASHPID requires Bash, ksh93, or mksh >= R41' >&2
                    return 1
                fi
        esac
    fi
}

function main {
    typeset -a myShell
    doCompat myShell || exit 1 # stripped-down compat function.
    typeset x

    print -v .sh.version
    x=$(print -nv BASHPID; print -nr " $$"); print -r "$x" # comsubs are free for builtins with no redirections 
    _=$({ print -nv BASHPID; print -r " $$"; } >&2)        # but not with a redirect
    _=$({ printf '%s ' "$BASHPID" $$; } >&2); echo         # nor for expansions with a redirect
    _=$(printf '%s ' "$BASHPID" $$ >&2); echo # but if expansions aren't redirected, they occur in the same process.
    _=${ { print -nv BASHPID; print -r " $$"; } >&2; }     # However, ${ ;} is always subshell-free (obviously).
    ( printf '%s ' "$BASHPID" $$ ); echo                   # Basically the same rules apply to ( )
    read -r x _ <<<$(</proc/self/stat); print -r "$x $$"   # These are free in {{m,}k,z}sh. Only Bash forks for this.
    printf '%s ' "$BASHPID" $$ | cat # Sadly, pipes always fork. It isn't possible to precisely mimic "printf -v".
    echo
} 2>&1

main "$@"

out:

Version AJM 93v- 2013-02-22
31732 31732
31735 31732
31736 31732 
31732 31732 
31732 31732
31732 31732 
31732 31732
31738 31732

еще одно аккуратное следствие всей этой внутренней обработки ввода-вывода-некоторые проблемы буферизации просто уходят. Вот забавный пример чтения строк с tee и head builtins (не пробуйте это в любой другой оболочке).

 $ ksh -s <<\EOF
integer -a x
builtin head tee
printf %s\n {1..10} |
    while head -n 1 | [[ ${ { x+=("$(tee /dev/fd/{3,4})"); } 3>&1; } ]] 4>&1; do
        print -r -- "${x[@]}"
    done
EOF
1
0 1
2
0 1 2
3
0 1 2 3
4
0 1 2 3 4
5
0 1 2 3 4 5
6
0 1 2 3 4 5 6
7
0 1 2 3 4 5 6 7
8
0 1 2 3 4 5 6 7 8
9
0 1 2 3 4 5 6 7 8 9
10
0 1 2 3 4 5 6 7 8 9 10

в ksh подсхема может привести или не привести к новому процессу. Я не знаю, каковы условия, но оболочка была оптимизирована для производительности в системах, где fork() было дороже, чем обычно в Linux, поэтому он избегает создания нового процесса, когда это возможно. В спецификации говорится о "новой среде", но это экологическое разделение может быть сделано в процессе.

еще одно смутно связанное различие-использование новых процессов для труб. В КШ и zsh, если последняя команда в конвейере является встроенной, она выполняется в текущем процессе оболочки, поэтому это работает:

$ unset x
$ echo foo | read x
$ echo $x
foo
$

в bash все команды конвейера после первого выполняются в подрешетках, поэтому вышеизложенное не работает:

$ unset x
$ echo foo | read x
$ echo $x

$

как указывает @dave-thompson-085, вы можете получить поведение ksh/zsh в версиях bash 4.2 и новее, если вы отключите управление заданиями (set +o monitor) и включить (shopt -s lastpipe). Но мое обычное решение - использовать замену процессов вместо этого:

$ unset x
$ read x < <(echo foo)
$ echo $x
foo

страница bash читает:

каждая команда в конвейере выполняется как отдельный процесс (т. е. в подоболочку).

хотя это предложение касается труб, оно сильно подразумевает, что подрешетка-это отдельный процесс.

страница неоднозначности Википедии также описывает подсетку в терминах дочернего процесса. Детский процесс сам по себе, конечно, процесс.

KSH manpage (на первый взгляд) не является прямым о его собственное определение подэлемента, поэтому оно не подразумевает так или иначе, что подэлемент-это другой процесс.

изучение оболочки Korn говорит, что это разные процессы.

Я бы сказал, что вы что-то упускаете (или книга неверна или устарела).