我一直认为子shell不是子进程,而是另一个 shell环境在同一个过程中。
我使用一组基本的内置函数:
(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的手册页,显然为子shell创建了另一个进程, 然而它假装$$,这是一个骗局。
预期bash和ksh之间是否有区别,或者我是否错误地阅读了这些症状?
编辑:其他信息:
在bash上运行strace -f
和在Linux上运行ksh会显示bash为示例命令调用clone
两次(它不会调用fork
)。所以bash可能正在使用线程(我试过ltrace
但核心转储!)。
KornShell既不会调用fork
,vfork
,也不调用clone
。
答案 0 :(得分:12)
在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
$
正如@ dave-thompson-085所指出的那样,如果关闭作业控制(set +o monitor
)并打开lastpipe
选项,则可以在bash版本4.2和更新版本中获得ksh / zsh行为(shopt -s lastpipe
)。但我通常的解决方案是使用流程替换:
$ unset x
$ read x < <(echo foo)
$ echo $x
foo
答案 1 :(得分:9)
ksh93非常难以避免使用子弹。部分原因是避免使用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 "${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处理的另一个巧妙结果是一些缓冲问题就会消失。这是一个使用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
答案 2 :(得分:1)
bash手册页上写着:
管道中的每个命令都作为一个单独的进程执行(即在子shell中)。
虽然这句话是关于管道的,但它强烈暗示子shell是一个单独的过程。
Wikipedia's disambiguation page还描述了子进程术语中的子shell。儿童过程当然是一个过程。
ksh手册页(一目了然)并不直接关于它自己对子shell的定义,因此它并不意味着子shell是一个不同的过程。
Learning the Korn Shell说它们是不同的过程。
我会说你遗失了某些东西(或者这本书错了或过时了)。
答案 3 :(得分:1)
Korn shell 不一定使用子 shell 进行命令替换。它们通常在同一过程中处理。异常包括 I/O 操作
为了更进一步,我有一个命令给出了一个变量值,在 ksh93 中,来自一个非常旧的脚本:
my_variable=(`cat ./my_file`)
换句话说,反引号命令替换周围的括号。 “my_file”是一个 4 位八进制数的列表,一行一个。
当在 ksh93t 及更高版本中以这种方式提供时,换行符将被保留,您可以使用计数器逐步遍历变量中的数字。例如,以下代码将给出上述列表中的 4 位八进制数,之后,您将递增计数器:
data_I_want=$(echo "${my_variable[$my_counter]}")
在 ksh93 中,变量的命令也可以这样完成:
my_variable=($(cat ./my_file))
最后,消除“无用的使用 cat”,
my_variable=($(<./my_file))
如果命令的结构没有外括号,则换行符将被剥离(POSIX 标准),并且变量的第一次使用包括文件中的所有数字。使用计数器对变量的后续调用返回空值。
将命令放在括号内会强制在新进程中使用子 shell,并避免使用 IFS=""
重置默认字段分隔符的必要性。
很抱歉撞到这么旧的东西,但包含这个似乎是值得的,因为我还没有在其他地方看到过这种特殊行为。