管道符号左侧是子shell吗?

14

编辑:

我下面关于 sed 's@^@ @' <(f1) 的评论是不正确的。虽然$BASH_SUBSHELL表示我们与启动程序在同一级别,但变量在主脚本中丢失了。 根据Gordon的答案,我测试了f1 > >(sed 's@^@ @'),似乎可以正确地工作。不过,第一种形式的 BASH_SUBSHELL 不应该是 1 而不是 0 吗?


考虑这个小测试

#!/bin/bash
declare -i i=0
function f1()
{
  let i++
  echo "In f1, SUBSHELL: $BASH_SUBSHELL, i=$i" >&2
}

f1
f1 | sed 's@^@     @'

echo "at end, i=$i"

以下是输出结果:

In f1, SUBSHELL: 0, i=1
In f1, SUBSHELL: 1, i=2
at end, i=1

(使用sed仅是为了将其与其他内容进行管道连接,不要期望它执行任何操作,因为f1输出到stderr)

函数f1记录当前的BASH_SUBSHELL和变量i的当前值

我知道为什么在脚本结尾时我们得到i = 1,因为第二次调用发生在子shell中,并且在子shell 1中的变量i的值已经丢失。

我不知道的是为什么左侧的管道没有在当前shell中执行

虽然我想到可以通过 sed 's@^@ @' <(f1) 来避免这种情况,但我仍然想知道为什么左侧不在与主脚本相同的级别上。


我认为在子shell中,Shell允许同时拥有管道的两端。 - sehe
快速谷歌搜索发现这个链接:http://www.linuxprogrammingblog.com/pipe-in-bash-can-be-a-trap - Brian Roach
@Brian,那篇文章没有讨论管道的左侧...我已经知道它对于管道右侧的变量赋值是个坏消息了。 - nhed
2个回答

27

来自bash man页面的内容:"管道中的每个命令都作为单独的进程执行(即在子shell中执行)"。我认为可以在当前shell(即第一个、最后一个或者中间的某个命令)中执行管道中的某个组件,但Bash不会偏袒任何一个组件:它们全部都在子shell中执行。如果你像这样修改你的脚本:

#!/bin/bash
declare -i i=0
function f1()
{
    let i++
    echo "In f1, SUBSHELL: $BASH_SUBSHELL, i=$i" >&2
}

f1
f1 | f1 | f1

echo "at end, i=$i"

它会打印出:

In f1, SUBSHELL: 0, i=1
In f1, SUBSHELL: 1, i=2
In f1, SUBSHELL: 1, i=2
In f1, SUBSHELL: 1, i=2
at end, i=1

因为管道中对 f1 的所有三次调用均在子shell中运行。


我想我的困惑来自于认为管道和文件重定向有些相等。例如,f1 < /etc/passwd并不会启动一个新的子shell(而且我想如果我们只是暂时地重定向一个描述符——为什么不能保持一致呢)。但既然在man手册中已经这样写了,我想就只能这样做了。 - nhed
1
@nhed:我不会将管道想象成输出重定向。管道是一系列通过I/O重定向串联在一起的命令,它们都同时运行;由于shell不支持多任务处理,最多只能在主shell中运行其中一个命令,而且它们本质上都是相等的,没有理由选择在主shell中运行哪个命令,为了公平起见,它们都在子shell中运行。在bash中,您可以使用< <()> >()来明确地将某些命令委托给子shell,在主shell中允许一个命令运行。 - Gordon Davisson
6
@nhed继续说道,例如,要强制管道中的第一个命令在主shell中运行,请使用cmd1 > >(cmd2 | cmd3 | ...)。要在主shell中运行cmd2,请使用cmd2 < <(cmd1) > >(cmd3 | ...)等等。 - Gordon Davisson
谢谢 - 我以为我漏掉了什么(在 <()>() 之前添加额外的 <>)。仍然想知道 BASH_SUBSHELL ... 如果它在子shell中,它不应该打印 0 - nhed
3
轻微的挑剔,因为bash确实有偏爱的一面:在管道的左侧使用jobs命令不会在子shell中运行。 - bishop
<3 @GordonDavisson,救了我的一天,我需要确保当前 shell 中只有一个。 - Dee

-2

如果有人感兴趣,这里是一个更加简洁的例子:

cd / && cd /tmp/ | pwd  ; pwd
/
/

或者:

cd / && cd /tmp/ | cd /var/  ; pwd
/

是的,这个页面说了一切。

http://linux.die.net/man/1/bash# 管道中的每个命令都作为单独的进程执行(即在子shell中执行)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接