8

サブシェルは子プロセスではなく、同じプロセス内の別のシェル環境であると常に信じていました。

組み込みの基本セットを使用します。

(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 の子プロセスです。
bash の man ページを読むと、サブシェル用に別のプロセスが作成されていることは明らかですが、これは卑劣な $$ を偽造しています。

この bash と ksh の違いは予期されたものですか、それとも症状の読み方が間違っているのでしょうか?

編集: 追加情報: strace -fLinux で bash および ksh を実行するとclone、サンプル コマンドに対して bash が 2 回呼び出されることが示されます (呼び出されませんfork)。そのため、bash はスレッドを使用している可能性があります (試してみltraceましたが、コア ダンプが発生しました!)。KornShell はfork, vfork, も も呼び出しませんclone

4

4 に答える 4

12

ksh では、サブシェルによって新しいプロセスが生成される場合と生成されない場合があります。条件が何であるかはわかりませんが、シェルは、fork()通常の Linux よりも高価なシステムでのパフォーマンス用に最適化されているため、可能な限り新しいプロセスの作成を回避します。仕様には「新しい環境」と書かれていますが、その環境分離は工程内で行われる場合があります。

漠然と関連するもう 1 つの違いは、パイプに新しいプロセスを使用することです。ksh と zsh では、パイプラインの最後のコマンドがビルトインの場合、現在のシェル プロセスで実行されるため、次のように動作します。

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

bash では、最初のパイプライン コマンド以降のすべてのパイプライン コマンドがサブシェルで実行されるため、上記は機能しません。

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

$

@dave-thompson-085 が指摘しているように、ジョブ制御 ( set +o monitor) をオフにしてlastpipeオプション ( shopt -s lastpipe) をオンにすると、bash バージョン 4.2 以降で ksh/zsh の動作を得ることができます。しかし、私の通常の解決策は、代わりにプロセス置換を使用することです。

$ unset x
$ read x < <(echo foo)
$ echo $x
foo
于 2013-02-04T13:33:02.707 に答える
10

ksh93 は、サブシェルを回避するために異常にハードに動作します。その理由の一部は、標準入出力の回避と、ビルトインが直接通信できるようにするsfioの広範な使用です。もう 1 つの理由は、理論上、ksh には非常に多くのビルトインを含めることができるためです。でビルドすると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 "${1}[0]="
    else
        case "${BASH_VERSINFO[*]-${!KSH_VERSION}}" in
            .sh.version)
                nameref v=$1
                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 "_${1}=$1"
                eval "_${1}[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 "$@"

アウト:

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

このすべての内部 I/O 処理のもう 1 つの優れた結果は、いくつかのバッファリングの問題が解消されることです。teeとbuiltinsを使用して行を読み取る面白い例を次に示しheadます (これを他のシェルで試さないでください)。

 $ 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
于 2013-03-09T14:50:08.347 に答える
2

bash のマンページには次のように書かれています。

パイプライン内の各コマンドは、個別のプロセスとして (つまり、サブシェル内で) 実行されます。

この文はパイプに関するものですが、サブシェルが別のプロセスであることを強く示唆しています。

ウィキペディアの曖昧さ回避ページでも、子プロセス用語でサブシェルについて説明しています。子プロセスは確かにそれ自体がプロセスです。

ksh マンページ (一見) は、サブシェルの独自の定義について直接的ではないため、サブシェルが別のプロセスであることを意味するものではありません。

コーンシェルを学ぶことは、それらが異なるプロセスであると言います。

何かが足りない (または、本が間違っているか、古くなっている) と思います。

于 2013-02-04T13:03:02.497 に答える