bash和ksh之间的子shell差异

我一直认为,一个子壳不是一个子过程,而是另一个壳过程的环境。

我使用了一套基本的内置插件:

(echo "Hello";read) 

在另一个terminal上:

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

所以,在kornShell(ksh)中没有subprocess。

inputbash,它看起来行为不同,给定相同的命令:

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

所以,在bash中的一个subprocess。
通过阅读bash的手册页,很明显,为子壳创build了另一个过程,但是它伪造了$$,这是一个sneeky。

预计bash和ksh之间的差异,还是我不正确地阅读症状?

编辑:附加信息:在bash上运行strace -f和在Linux上运行ksh显示bash为示例命令调用了两次clone (它不调用fork )。 所以bash可能会使用线程(我试过ltrace但它被核心倾倒!)。 KornShell既不调用fork ,也不调用clone

ksh93的工作异常困难,以避免subhells。 部分原因是为了避免stdio和大量使用sfio ,它允许builtin直接进行通信。 另一个原因是ksh理论上可以有这么多的内建。 如果使用SHOPT_CMDLIB_DIR ,则默认情况下包含并启用所有cmdlib内置函数。 我不能给出一个全面的列表来避免subhells的地方,但通常情况下,只有内建的使用,没有重定向的地方。

 #!/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 -rx _ <<<$(</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处理的另一个巧妙的结果是一些缓冲问题就消失了。 这里有一个有趣的例子,读取teehead内置的线(不要在任何其他外壳尝试这个)。

  $ ksh -s <<\EOF integer -ax 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中,一个子shell可能会或可能不会导致一个新的进程。 我不知道条件是什么,但是shell在fork()比在Linux上更昂贵的系统上进行了优化,因此它可以避免在任何时候创建新进程。 规范说明了一种“新环境”,但环境分离可能正在进行中。

另一个模糊的区别是使用新的管道工艺。 在ksh和zsh中,如果管道中的最后一个命令是内置的,它将在当前的shell进程中运行,所以这个工作:

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

在bash中,所有的管道命令都是子shell,所以它不会:

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

bash manpage写道:

管道中的每个命令都作为一个单独的进程执行(即在一个子shell中)。

虽然这句话是关于管道,但它强烈暗示一个子shell是一个单独的过程。

维基百科的消歧页面也以子进程的形式描述了一个子shell。 儿童进程本身就是一个过程。

ksh联机帮助页(一目了然)并不直接涉及到它自己的子shell定义,因此它并不意味着子shell是一个不同的过程。

学习Korn Shell说他们是不同的过程。

我会说你错过了一些东西(或者书本错误或过时)。