如何检测我是否在子外壳中?


24

我正在尝试编写一个函数来替换exit内置函数,以防止自己退出终端。

我试图使用SHLVL环境变量,但是在子外壳中似乎没有改变:

$ echo $SHLVL
1
$ ( echo $SHLVL )
1
$ bash -c 'echo $SHLVL'
2

我的功能如下:

exit () {
    if [[ $SHLVL -eq 1 ]]; then
        printf '%s\n' "Nice try!" >&2
    else
        command exit
    fi
}

但是,这不允许我exit在子shell中使用:

$ exit
Nice try!
$ (exit)
Nice try!

有什么好的方法可以检测我是否在子外壳中?



1
正因为如此。$ SHLVL为1是因为即使echo $ SHLVL命令在“ subshel​​l”中运行,仍处于外壳程序级别1。根据那篇文章,带有括号的子shell (...)继承了父进程的所有属性。提供的答案是确定外壳程序级别的更可靠的解决方案。
kemotep


5
@mosvy我觉得那是一个不同的问题。例如,BASH_SUBSHELL答案(即使是有争议的)也不适用于该问题。
Sparhawk

2
在HNQ上看到标题,并认为这是一个量子力学问题……
Mehrdad

Answers:


43

在bash中,你可以比较$BASHPID,以$$

$ ( if [ "$$" -eq "$BASHPID" ]; then echo not subshell; else echo subshell; fi )
subshell
$   if [ "$$" -eq "$BASHPID" ]; then echo not subshell; else echo subshell; fi
not subshell

如果您不在bash中,$$则应在子shell中保持相同,因此您需要其他一些方式来获取实际的进程ID。

获得实际pid的一种方法是sh -c 'echo $PPID'。如果您只是简单地将其放进去,( … )则可能似乎无法正常工作,因为您的外壳已经优化了叉子。尝试使用额外的no-op命令( : ; sh -c 'echo $PPID'; : ),使其认为子外壳太复杂而无法优化。对于该方法,可归因于Stack Overflow上的John1024


您可能希望将其更改为  (sh -c 'echo $PPID'; : )-请参阅John1024答案的评论
G-Man说“恢复莫妮卡”

@ G-Man好吧,那只是为了测试它(因为在实际使用中它会以某种更复杂的方式)...但是,是的,如果测试在所有shell中都可以,那将是最好的。因此,我在此之前和之后都设置了一个不操作的对象,希望可以处理所有操作。
derobert

38

怎么BASH_SUBSHELL

BASH_SUBSHELL
      当Shell
      在该环境中开始执行时,在每个Subshel​​l或Subshel​​l环境中增加一个。初始值为0。

$ echo $BASH_SUBSHELL
0
$ (echo $BASH_SUBSHELL)
1

16
这在电影《盗梦空间》中是很方便的命令。
埃里克·杜米尼尔

在开始时,它可能是$ SHLVL
Granny Aching

19

[这本来应该是评论,但是我的评论往往会被主持人删除,因此这将作为答案,即使删除也可以用作参考]

使用BASH_SUBSHELL是完全不可靠的,因为仅在某些子shell中(而不是在所有子shell中)将其设置为1 。

$ (echo $BASH_SUBSHELL)
1
$ echo $BASH_SUBSHELL | cat
0

声称管道命令,在运行的子进程是不是之前真正实际子shell,考虑这个man bash片断:

管道中的每个命令都作为单独的进程(即,在子Shell中)执行。

以及实际的含义-这是脚本片段是否在子进程中运行,这是必不可少的,而不是某些术语上的争论。

正如该问题的答案中已经解释的那样,唯一的解决方案是检查是否$BASHPID等于$$或可移植但效率低得多:

if [ "$(exec sh -c 'echo "$PPID"')" != "$$" ]; then
    echo you\'re in a subshell
fi

11
Nit:BASH_SUBSHELL设置非常可靠,但是正确获取其值是不确定的。请注意文档中所说的内容:“ 当shell在该环境中开始执行时,在每个subshel​​l或subshel​​l环境中增加一个 ”我认为在管道示例中,当扩展变量时,bash尚未开始在该subshel​​l中执行。您可以将其echo $BASH_VERSIONdeclare -p BASH_VERSION-后者应该可靠地通过管道,后台作业等输出1
muru

6
甚至说,eval 'echo $BASH_SUBSHELL $BASHPID' | cat将为输出1 BASH_SUBSHELL,因为变量在执行开始后被扩展。
穆鲁

4
所有这些参数也应适用于流程和命令替换,bg流程,但只有管道有所不同。看代码,递增subshell_level真的被推迟的情况下前景管道,这可能有一定的道理,但我不能做出来;-)
mosvy

2
你是对的。切特似乎明确打算这样做。list.gnu.org/archive/html/bug-bash/2015-06/msg00050.html:“ BASH_SUBSHELL测量(...)子外壳,而不是管道元素。” listing.gnu.org/archive/html/bug-bash/2015-06/msg00054.html:“我将考虑应该记录现状还是扩展$ BASH_SUBSHELL反映的'subshel​​l'的定义。 ”
穆鲁

2
@JoL你错了,扩展也发生在单独的过程中,请阅读上面讨论中的链接和示例;或尝试使用echo $$ $BASHPID $BASH_SUBSHELL | cat
mosvy,
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.