Cal*_*leb 24 bash shell optimization subshell command-substitution
昨天有人向我建议在bash中使用命令替换会导致产生不必要的子shell.该建议仅针对此用例:
# Extra subshell spawned
foo=$(command; echo $?)
# No extra subshell
command
foo=$?
Run Code Online (Sandbox Code Playgroud)
我认为这对于这个用例似乎是正确的.但是,快速搜索试图验证这会导致大量令人困惑和矛盾的建议.似乎流行的智慧说所有命令替换的使用都会产生一个子shell.例如:
命令替换扩展为命令的输出.这些命令在子shell中执行,其stdout数据是替换语法扩展的内容.(来源)
这看起来很简单,除非你继续挖掘,在这种情况下你会开始找到建议的参考,但事实并非如此.
命令替换不一定会调用子shell,在大多数情况下也不会.它唯一保证的是乱序评估:它只是首先评估替换中的表达式,然后使用替换结果评估周围的语句.(来源)
这似乎是合理的,但这是真的吗?这个与子shell相关的问题的回答让我觉得有点man bash需要注意:
管道中的每个命令都作为单独的进程执行(即,在子shell中).
这让我想到了主要问题.究竟是什么导致命令替换产生一个副本,这个子shell无论如何都不会被隔离地执行相同的命令?
请考虑以下情况并解释哪些情况会产生额外子shell的开销:
# Case #1
command1
var=$(command1)
# Case #2
command1 | command2
var=$(command1 | command2)
# Case #3
command1 | command 2 ; var=$?
var=$(command1 | command2 ; echo $?)
Run Code Online (Sandbox Code Playgroud)
这些对中的每一对都会产生相同数量的子壳来执行吗?POSIX与bash实现有区别吗?是否有其他情况下使用命令替换会产生一个子shell,在这种情况下运行同一组命令不会?
mkl*_*nt0 15
更新和警告:
这个答案有一个困难的过去,因为我自信地声称事情变得不真实.我相信它目前的形式有价值,但请帮助我消除其他不准确之处(或说服我完全删除它).
在@kojiro指出我的测试方法存在缺陷(我最初用于查找子进程,但这太慢而无法始终检测到它们)之后,我已经对这个答案进行了大幅修改 - 并且大部分已经去掉了ps.下面描述一种新的测试方法.
我最初声称并非所有bash子shell都在他们自己的子进程中运行,但事实证明并非如此.
正如@kojiro在他的回答中所说,有些 shell(除了bash)有时会避免为子shell创建子进程,因此,一般来说,在shell的世界中,人们不应该认为子shell意味着子进程.
至于OP在bash中的情况(假设command{n}实例是简单的命令):
# Case #1
command1 # NO subshell
var=$(command1) # 1 subshell (command substitution)
# Case #2
command1 | command2 # 2 subshells (1 for each pipeline segment)
var=$(command1 | command2) # 3 subshells: + 1 for command subst.
# Case #3
command1 | command2 ; var=$? # 2 subshells (due to the pipeline)
var=$(command1 | command2 ; echo $?) # 3 subshells: + 1 for command subst.;
# note that the extra command doesn't add
# one
Run Code Online (Sandbox Code Playgroud)
看起来像使用命令substitution($(...))总是在bash中添加一个额外的子shell - 就像包含任何命令一样(...).
我相信,但不确定这些结果是否正确 ; 这是我测试的方法(OS X 10.9.1上的bash 3.2.51) - 请告诉我这种方法是否存在缺陷:
fork()第一个中的调用sudo dtruss -t fork -f -p {pidOfShell1}(-f还fork()需要"传递" 跟踪调用,即包括由子shell本身创建的调用).:在测试命令中仅使用内置(无操作)(以避免使用fork()外部可执行文件的额外调用混淆图片); 特别:
:$(:): | :$(: | :): | :; :$(: | :; :)仅计算dtruss包含非零PID的输出行(因为每个子进程也报告fork()创建它的调用,但PID为0).
fork().以下是我仍然认为从我的原始帖子中正确的:当bash创建子shell时.
bash在以下情况下创建子shell:
(...))
[[ ... ]],括号仅用于逻辑分组.|,包括第一个段
bash 4.2+具有shell选项lastpipe(默认为OFF),这会导致最后一个管道段不在子shell中运行.用于命令替换($(...))
进程替换(<(...))
exec(<(exec ...))之前添加简单命令.&)组合这些结构将导致多个子shell.
在Bash中,子shell总是在新的进程空间中执行.您可以在Bash 4中相当简单地验证这一点,它具有$BASHPID和$$环境变量:
在实践中:
$ type echo
echo is a shell builtin
$ echo $$-$BASHPID
4671-4671
$ ( echo $$-$BASHPID )
4671-4929
$ echo $( echo $$-$BASHPID )
4671-4930
$ echo $$-$BASHPID | { read; echo $REPLY:$$-$BASHPID; }
4671-5086:4671-5087
$ var=$(echo $$-$BASHPID ); echo $var
4671-5006
Run Code Online (Sandbox Code Playgroud)
关于shell可以省略额外子shell 的唯一情况是当你管道到一个显式的子shell时:
$ echo $$-$BASHPID | ( read; echo $REPLY:$$-$BASHPID; )
4671-5118:4671-5119
Run Code Online (Sandbox Code Playgroud)
这里,管道隐含的子shell明确应用,但不重复.
这从一些变化是非常努力,以避免其他炮弹fork-ing.因此,虽然我觉得这个论点js-shell-parse具有误导性,但事实并非所有的贝壳总是fork适用于所有的子壳.
| 归档时间: |
|
| 查看次数: |
2596 次 |
| 最近记录: |