命令替换何时会比单独的相同命令产生更多的子 shell?

2024-02-29

昨天有人建议我在 bash 中使用命令替换会导致生成不必要的子 shell。该建议具体针对这个用例 https://stackoverflow.com/q/21317997/313192:

# Extra subshell spawned
foo=$(command; echo $?)

# No extra subshell
command
foo=$?

据我所知,这对于这个用例来说似乎是正确的。然而,试图验证这一点的快速搜索会导致大量令人困惑和矛盾的建议。流行的观点似乎是,所有命令替换的使用都会产生一个子 shell。例如:

命令替换扩展到命令的输出。这些命令在子 shell 中执行,它们的标准输出数据是替换语法扩展到的内容。 (source http://wiki.bash-hackers.org/syntax/expansion/cmdsubst)

这看起来很简单,除非您继续挖掘,在这种情况下,您将开始找到对建议的引用,但事实并非如此。

命令替换不一定调用子shell,并且在大多数情况下不会。它唯一保证的是乱序求值:它只是先求值替换中的表达式,然后使用替换的结果求值周围的语句。 (source https://github.com/grncdr/js-shell-parse/issues/1)

这似乎很有道理,但事实果真如此吗?这个答案 https://stackoverflow.com/a/9109654/313192一个与子外壳相关的问题让我注意到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 $?)

这些对中的每一对都会产生相同数量的子 shell 来执行吗? POSIX 与 bash 实现有区别吗?是否存在其他情况,使用命令替换会生成子 shell,而单独运行同一组命令则不会?


更新和警告:

这个答案有一个麻烦的过去,因为我自信地声称事实证明不是真的。我相信它有它的价值current形式,但请帮助我消除其他不准确之处(或说服我应该将其完全删除)。

I've 大幅修改 - 并且大部分被删除 - 这个答案在@kojiro指出我的测试方法有缺陷之后(我最初使用ps寻找子进程,但这太慢而无法始终检测到它们);下面描述一种新的测试方法。

我最初声称并非所有 bash 子 shell 都在自己的子进程中运行,但事实证明并非如此。

正如@kojiro 在他的回答中所说,someshell - 除了 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

看起来像使用命令替换($(...)) 总是在 bash 中添加额外的子 shell- 就像将任何命令包含在(...).

我相信,但不确定这些结果是否正确;这是我的测试方法(OS X 10.9.1 上的 bash 3.2.51)-请告诉我这种方法是否有缺陷:

  • 确保只有 2 个交互式 bash shell 正在运行:一个用于运行命令,另一个用于监视。
  • 在第二个 shell 中我监控了fork()第 1 次调用sudo dtruss -t fork -f -p {pidOfShell1} (the -f还需要追踪fork()调用“传递”,即包括那些由子 shell 本身创建的)。
  • 只使用内置的:(无操作)在测试命令中(以避免用额外的内容混淆图片)fork()调用外部可执行文件);具体来说:

    • :
    • $(:)
    • : | :
    • $(: | :)
    • : | :; :
    • $(: | :; :)
  • 只统计了那些dtruss包含非零 PID 的输出行(因为每个子进程还报告fork()创建它的调用,但 PID 为 0)。

  • 从结果数中减去 1,因为即使只是从交互式 shell 中运行内置函数显然也至少涉及 1fork().
  • 最后,假设结果计数代表创建的子 shell 的数量。

以下是我仍然认为我原来的帖子中正确的内容:当 bash 创建子 shell 时。


bash 在以下情况下创建子 shell:

  • for an expression surrounded by parentheses ( (...) )
    • except直接在里面[[ ... ]],其中括号仅用于逻辑分组。
  • for every segment of a pipeline (|), including the first one
    • 注意every涉及的子shell是一个克隆original外壳方面content(就进程而言,子 shell 可以从其他子 shell 中派生出来(before命令被执行))。
      因此,早期管道段中子 shell 的修改不会影响后面的管道段。
      (根据设计,管道中的命令会启动同时地- 排序仅通过其连接的 stdin/stdout 管道进行。)
    • bash 4.2+有外壳选项lastpipe(默认为关闭),这会导致last管道段不能在子 shell 中运行。
  • 对于命令替换($(...))

  • 对于过程替换(<(...))

    • 通常会创建2子壳;如果是简单的命令, @konsolebox想出了一种技术 https://stackoverflow.com/a/24655246/45375只创造1: 在简单命令前面加上exec (<(exec ...)).
  • 后台执行(&)

组合这些结构将产生多个子 shell。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

命令替换何时会比单独的相同命令产生更多的子 shell? 的相关文章

  • 从 csv 文件中删除特定列,保持输出上的相同结构[重复]

    这个问题在这里已经有答案了 我想删除第 3 列并在输出文件中保留相同的结构 输入文件 12 10 10 10 10 1 12 23 1 45 6 7 11 2 33 45 1 2 1 2 34 5 6 I tried awk F 3 fil
  • 仅打印“docker-container ls -la”输出中的“Names”列

    发出时docker container ls la命令 输出如下所示 CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES a67f0c2b1769 busybox tail f dev
  • .profile 无法从 Mac 终端运行

    我有一个 profile 文件 我正在终端中读取并使用别名 但在某些时候 别名由于没有明确的原因而停止工作 其他命令仍在工作 为了快速修复 我删除了 rm 并在用户目录中重新创建了 profile 文件 我可以看到 至少在该目录中没有 ba
  • 如何在 Linux 中编写文本模式 GUI? [关闭]

    Closed 这个问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 当我编写脚本 程序时 我经常想弹出一个简单的文本 gui 来提示输入 我该怎么做 例如 来自 Shel
  • shell脚本中的\r字符

    我在尝试执行 shell 脚本时收到以下错误 r command not found line 2 请提出同样的解决方案 以下是脚本中使用的初始行 bin sh if lt 1 then echo ERROR Environment arg
  • 有什么工具可以说明每种方法运行需要多长时间?

    我的程序的某些部分速度很慢 我想知道是否有我可以使用的工具 例如它可以告诉我可以运行 methodA 花了 100ms 等等 或者类似的有用信息 如果您使用的是 Visual Studio Team System 性能工具 中有一个内置分析
  • 如何在 shell 脚本中并行运行多个实例以提高时间效率[重复]

    这个问题在这里已经有答案了 我正在使用 shell 脚本 它读取 16000 行的输入文件 运行该脚本需要8个多小时 我需要减少它 所以我将其划分为 8 个实例并读取数据 其中我使用 for 循环迭代 8 个文件 并在其中使用 while
  • 如何执行“sudo nvm”?

    在我的 Mac 上 我想将一些需要 su 权限的包迁移到另一个节点版本 我使用 homebrew 安装 nvm 现在我需要执行 sudo nvm 或 reinstall packages将失败 me MacBook sudo nvm sud
  • C程序调用shell脚本

    我有一个小型 C 程序 调用 shell 脚本 myScript sh 我得到的 ret 值为 256 请帮助我了解系统调用出了什么问题 int main int ret ret system myScript sh ret gt gt r
  • 如何在 shell 脚本中操作 $PATH 元素?

    有没有一种惯用的方法从类似 PATH 的 shell 变量中删除元素 这就是我想要的 PATH home joe bin usr local bin usr bin bin path to app bin and remove or rep
  • if [ -z "${FILE_LIST}" ] 中的 -z 是什么

    遇到了这个 什么是 z在 shell 脚本中if z FILE LIST 是相同的test And man test gives z STRING the length of STRING is zero 注意 在某些平台上 是一个符号链接
  • 匹配模式后添加行[重复]

    这个问题在这里已经有答案了 我有一个文件说test具有以下值 Linux Solaris Fedora Ubuntu AIX HPUX 如何在匹配 AIX 的行后面添加一行系统主机名 如果我做 echo hostname gt gt tes
  • 为什么n++执行速度比n=n+1快?

    在C语言中 为什么n 执行速度快于n n 1 int n n int n n n 1 我们的老师在今天的课堂上问了这个问题 这不是家庭作业 如果您正在开发一个 石器时代 编译器 的情况下 石器时代 n比n 比n n 1 机器通常有incre
  • 如何判断 Bash 中是否存在文件?

    这会检查文件是否存在 bin bash FILE 1 if f FILE then echo File FILE exists else echo File FILE does not exist fi 我如何只检查文件是否存在not ex
  • 添加要在给定命令中运行的 .env 变量

    我有一个 env 文件 其中包含如下变量 HELLO world SOMETHING nothing 前几天我发现了这个很棒的脚本 它将这些变量放入当前会话中 所以当我运行这样的东西时 cat env grep v xargs node t
  • gcc 与 clang:符号剥离

    gcc 和 AMD Open64 opencc 都有一个 s选项 剥离符号表和重定位信息 到目前为止我还没能在 Clang LLVM 中找到相同的选项 它存在吗 您可以使用stripbinutils 中的实用程序 实际上 llvm ld 有
  • 从 shell 命令调用 SOAP 请求

    我使用curl 向Web 服务发送SOAP 请求 并使用shell 脚本获取响应 请在下面找到我正在使用的命令 curl H Content Type text xml charset utf 8 H SOAPAction d sample
  • 在 bash 脚本中提取 XML 值 [重复]

    这个问题在这里已经有答案了 我正在尝试从 xml 文档中提取一个值 该文档已作为变量读入我的脚本中 原始变量 data is
  • 如何通过保持目录结构完整来同步路径中匹配模式的文件?

    我想将所有文件从服务器 A 复制到服务器 B 这些文件在不同级别的文件系统层次结构中具有相同的父目录名称 例如 var lib data sub1 sub2 commonname filetobecopied foo var lib dat
  • 使用正则表达式模式查找 -name 并使用 cp 替换文件名

    目前我正在使用该命令cron复制 data从源到目标路径 find source path name data exec cp target path 源码结构为 source path category1 001 data source

随机推荐