【问题标题】:Bash subshell/pipelines - which parts are executing in subshells?Bash 子外壳/管道 - 哪些部分在子外壳中执行?
【发布时间】:2012-02-02 08:47:27
【问题描述】:

@JonathanLeffler 在comment on another post 中表示:

{ ... } | somecommand 在子 shell 中运行,不影响 父壳。演示:

X=PQR; echo $X; { X=ABC; echo $X; } | cat; echo $X

(三行输出 PQR、ABC、PQR)

确实:

james@bodacious-wired:tmp$X=PQR; echo $X; { X=ABC; echo $X; } | cat; echo $X
PQR
ABC
PQR

但是,man bash 表示 { .. } 不在子 shell 中执行:

   { list; }
          list  is  simply executed in the current shell environment.  list must be 
          terminated with a newline or semicolon.  This is known as a group command. 

那么这里发生了什么? man bash 错了吗?我知道管道的每个部分都在子shell中执行;但我不明白这是如何导致观察到的行为的。例如:

james@bodacious-wired:tmp$X=PQR; echo $X | sed;  X=ABC; echo $X | sed; echo $X
PQR
ABC
ABC

编辑添加:

一些人建议使用echo $$ 来表明事物是(或不是)子shell 的一部分。这一点用处都没有,因为$$ 在参数扩展阶段会被扩展,这在任何命令执行之前很久就发生了。

举个例子:

james@bodacious-wired:tmp$echo 1$$; ps; ( echo 2$$; ps ); echo 3$$; ps
11194
  PID TTY           TIME CMD
 1194 ttys000    0:00.22 -bash
21194
  PID TTY           TIME CMD
 1194 ttys000    0:00.22 -bash
 7894 ttys000    0:00.00 -bash
31194
  PID TTY           TIME CMD
 1194 ttys000    0:00.22 -bash
james@bodacious-wired:tmp$

您可以看到ps 的第二次调用发生在子shell 中,pid 为7894;但是echo 2$$ 仍然显示 bash 在变量扩展阶段替换的值,在它产生子shell之前

为了对比,并证明{ .. } 确实没有生成子shell:

james@bodacious-wired:tmp$echo 1$$; ps; { echo 2$$; ps; }; echo 3$$; ps
11194
  PID TTY           TIME CMD
 1194 ttys000    0:00.22 -bash
21194
  PID TTY           TIME CMD
 1194 ttys000    0:00.22 -bash
31194
  PID TTY           TIME CMD
 1194 ttys000    0:00.23 -bash

只是为了证明@nos是正确的,在上面加一个管道:

james@bodacious-wired:tmp$echo 1$$; ps; { echo 2$$; ps; } | sed ; echo 3$$; ps
11194
  PID TTY           TIME CMD
 1194 ttys000    0:00.25 -bash
21194
  PID TTY           TIME CMD
 1194 ttys000    0:00.25 -bash
 7945 ttys000    0:00.00 -bash
 7946 ttys000    0:00.00 sed
31194
  PID TTY           TIME CMD
 1194 ttys000    0:00.25 -bash

正如预期的那样,shell 生成了两个子shell,一个用于管道的每一侧。

【问题讨论】:

    标签: bash


    【解决方案1】:

    管道的每一侧至少成为一个子外壳。

    X=PQR; echo $X; { X=ABC; echo $X; } | cat; echo $X
    

    将创建一个至少为{ X=ABC; echo $X; }cat 的子shell/进程。

    “管道中的每个命令都作为单独的进程执行(即,在子外壳中)。” , 来自 man bash

    如果你改为这样做

    X=PQR; echo $X; { X=ABC; echo $X; } ; echo | cat; echo $X
    

    之后你会看到echo $X 显示ABC。

    还有其他方式可以在子 shell 中执行命令,例如如果您在后台运行子命令:{ X=SUB ; sleep 1; } &,则该组将在子 shell 中运行,而 { X=SUB ; sleep 1; } 则不会。

    如果要将始终在子 shell 中执行的命令分组,请使用括号 (X=ABC ; echo $X) 而不是大括号。

    【讨论】:

    • 我认为我们有一个赢家。创建子外壳的不是{ .. },而是管道。我几乎一开始就直接站出来说。
    • { 后面需要一个空格,至少在我的bash 版本中。
    • $(cmd args) 构造还创建一个子shell:echo $(echo $BASH_SUBSHELL)
    【解决方案2】:

    事实上,大括号是在一个新的子shell中执行的,但只有在管道中。第一个命令带有cat,第二个没有:

    xxx@yyy:~$ ps; X=PQR; echo $X; { ps; X=ABC; echo $X; } | cat; ps; echo $X
      PID TTY          TIME CMD
     6768 pts/7    00:00:00 bash
    13158 pts/7    00:00:00 ps
    PQR
      PID TTY          TIME CMD
     6768 pts/7    00:00:00 bash
    13159 pts/7    00:00:00 bash
    13160 pts/7    00:00:00 cat
    13161 pts/7    00:00:00 ps
    ABC
      PID TTY          TIME CMD
     6768 pts/7    00:00:00 bash
    13162 pts/7    00:00:00 ps
    PQR
    xxx@yyy:~$ ps; X=PQR; echo $X; { ps; X=ABC; echo $X; }; ps; echo $X
      PID TTY          TIME CMD
     6768 pts/7    00:00:00 bash
    13239 pts/7    00:00:00 ps
    PQR
      PID TTY          TIME CMD
     6768 pts/7    00:00:00 bash
    13240 pts/7    00:00:00 ps
    ABC
      PID TTY          TIME CMD
     6768 pts/7    00:00:00 bash
    13245 pts/7    00:00:00 ps
    ABC
    

    在这种情况下,6768 是终端 shell 的 PID,13159 是为大括号启动的子 shell 的 PID。

    似乎{} 在(且仅当)管道时在子shell 中执行。

    【讨论】:

    • 管道创建子外壳,而不是大括号。
    • 不,不是管道,而是与管道连接的支架。检查psps | cat{ ps; } | cat 的输出。.
    • 大括号从不调用子shell!只有管​​道会这样做:unix.stackexchange.com/questions/127334/…
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-21
    • 2012-11-23
    相关资源
    最近更新 更多