bash 和 ksh 之间的子外壳差异
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/14686872/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Sub-shell differences between bash and ksh
提问by cdarke
I always believed that a sub-shell was not a child process, but another shell environment in the same process.
我一直认为子shell不是子进程,而是同一个进程中的另一个shell环境。
I use a basic set of built-ins:
我使用一组基本的内置函数:
(echo "Hello";read)
On another terminal:
在另一个终端上:
ps -t pts/0
PID TTY TIME CMD
20104 pts/0 00:00:00 ksh
So, no child process in kornShell (ksh).
因此,kornShell (ksh) 中没有子进程。
Enter bash, it appears to behave differently, given the same command:
输入 bash,给定相同的命令,它的行为似乎有所不同:
PID TTY TIME CMD
3458 pts/0 00:00:00 bash
20067 pts/0 00:00:00 bash
So, a child process in bash.
From reading the man pages for bash, it is obvious that another process is created for a sub-shell,
however it fakes $$, which is sneeky.
因此,bash 中的子进程。
通过阅读 bash 的手册页,很明显为子 shell 创建了另一个进程,但是它伪造了 $$,这很狡猾。
Is this difference between bash and ksh expected, or am I reading the symptoms incorrectly?
bash 和 ksh 之间的这种差异是预期的,还是我错误地阅读了症状?
Edit: additional information:
Running strace -fon bash and ksh on Linux shows that bash calls clonetwice for the sample command (it does not call fork). So bash might be using threads (I tried ltracebut it core dumped!).
KornShell calls neither fork, vfork, nor clone.
编辑:附加信息:strace -f在 Linux 上的 bash 和 ksh 上运行显示 bashclone为示例命令调用了两次(它没有调用fork)。所以 bash 可能正在使用线程(我尝试过,ltrace但它的核心已转储!)。KornShell既不叫fork,vfork也不clone。
回答by Mark Reed
In ksh, a subshell might or might not result in a new process. I don't know what the conditions are, but the shell was optimized for performance on systems where fork()was more expensive than it typically is on Linux, so it avoids creating a new process whenever it can. The specification says a "new environment", but that environmental separation may be done in-process.
在 ksh 中,子 shell 可能会也可能不会导致新进程。我不知道条件是什么,但是外壳针对性能在fork()比 Linux 上通常更昂贵的系统上进行了优化,因此它尽可能避免创建新进程。规范说是一个“新环境”,但环境分离可能会在进程中完成。
Another vaguely-related difference is the use of new processes for pipes. In ksh and zsh, if the last command in a pipeline is a builtin, it runs in the current shell process, so this works:
另一个模糊相关的差异是管道的新工艺的使用。在 ksh 和 zsh 中,如果管道中的最后一个命令是内置命令,则它会在当前的 shell 进程中运行,因此它可以工作:
$ unset x
$ echo foo | read x
$ echo $x
foo
$
In bash, all pipeline commands after the first are run in subshells, so the above doesn't work:
在 bash 中,第一个之后的所有管道命令都在子 shell 中运行,因此上述方法不起作用:
$ unset x
$ echo foo | read x
$ echo $x
$
As @dave-thompson-085 points out, you can get the ksh/zsh behavior in bash versions 4.2 and newer if you turn off job control (set +o monitor) and turn on the lastpipeoption (shopt -s lastpipe). But my usual solution is to use process substitution instead:
正如@dave-thompson-085 指出的那样,如果关闭作业控制 ( set +o monitor) 并打开lastpipe选项 ( shopt -s lastpipe),则可以在 bash 4.2 及更高版本中获得 ksh/zsh 行为。但我通常的解决方案是使用进程替换:
$ unset x
$ read x < <(echo foo)
$ echo $x
foo
回答by ormaaj
ksh93 works unusually hard to avoid subshells. Part of the reason is the avoidance of stdio and extensive use of sfiowhich allows builtins to communicate directly. Another reason is ksh can in theory have so many builtins. If built with SHOPT_CMDLIB_DIR, all of the cmdlib builtins are included and enabled by default. I can't give a comprehensive list of places where subshells are avoided, but it's typically in situations where only builtins are used, and where there are no redirects.
ksh93 非常难以避免子shell。部分原因是避免使用 stdio 和广泛使用sfio,它允许内置程序直接通信。另一个原因是 ksh 理论上可以有这么多的内置函数。如果使用 构建SHOPT_CMDLIB_DIR,则默认情况下包含并启用所有 cmdlib 内置函数。我无法给出避免使用子 shell 的地方的完整列表,但通常是在仅使用内置函数且没有重定向的情况下。
#!/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
Another neat consequence of all this internal I/O handling is some buffering issues just go away. Here's a funny example of reading lines with teeand headbuiltins (don't try this in any other shell).
所有这些内部 I/O 处理的另一个巧妙的结果是一些缓冲问题就会消失。这是一个有趣的示例,使用tee和head内置函数读取行(不要在任何其他 shell 中尝试此操作)。
$ 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
回答by kojiro
The bash manpage reads:
bash 联机帮助页写道:
Each command in a pipeline is executed as a separate process (i.e., in a subshell).
管道中的每个命令都作为单独的进程(即,在子外壳中)执行。
While this sentence is about pipes, it strongly implies a subshell is a separate process.
虽然这句话是关于管道的,但它强烈暗示子外壳是一个单独的进程。
Wikipedia's disambiguation pagealso describes a subshell in child-process terms. A child process is certainly itself a process.
维基百科的消歧页面还描述了子进程术语中的子外壳。子进程本身当然是一个进程。
The ksh manpage (at a glance) isn't direct about its own definition of a subshell, so it does not imply one way or the other that a subshell is a different process.
ksh 联机帮助页(一目了然)并不直接说明它自己对子shell 的定义,因此它并不暗示子shell 是一个不同的进程。
Learning the Korn Shellsays that they are different processes.
学习 Korn Shell说它们是不同的过程。
I'd say you're missing something (or the book is wrong or out of date).
我会说你遗漏了一些东西(或者这本书是错误的或过时的)。

