如何检测我是否在子shell中?

jes*_*e_b 32 shell bash exit subshell

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

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

$ echo $SHLVL
1
$ ( echo $SHLVL )
1
$ bash -c 'echo $SHLVL'
2
Run Code Online (Sandbox Code Playgroud)

我的功能如下:

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

不过,这不允许我exit在子外壳中使用:

$ exit
Nice try!
$ (exit)
Nice try!
Run Code Online (Sandbox Code Playgroud)

检测我是否在子shell中的好方法是什么?

der*_*ert 45

在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
Run Code Online (Sandbox Code Playgroud)

如果您不在 bash 中,$$则应在子 shell 中保持不变,因此您需要通过其他方式获取实际进程 ID。

获取实际 pid 的一种方法是sh -c 'echo $PPID'. 如果你只是把它放在一个普通的地方,( … )它可能看起来不起作用,因为你的 shell 已经优化了 fork。尝试额外的 no-op 命令( : ; sh -c 'echo $PPID'; : ),让它认为 subshel​​l 太复杂而无法优化。这种方法归功于 Stack Overflow上的John1024。


Fre*_*ddy 43

怎么样BASH_SUBSHELL?

BASH_SUBSHELL
      当 shell
      在该环境中开始执行时,在每个子 shell 或子 shell 环境中递增 1 。初始值为 0。

$ echo $BASH_SUBSHELL
0
$ (echo $BASH_SUBSHELL)
1
Run Code Online (Sandbox Code Playgroud)

  • 在电影《盗梦空间》中,这将是一个方便的命令。 (20认同)

mos*_*svy 20

[这应该是一条评论,但我的评论往往会被版主删除,所以这将作为一个答案,即使删除我也可以将其用作参考]

UsingBASH_SUBSHELL是完全不可靠的,因为它只在某些子 shell 中设置为 1 ,而不是在所有子 shell 中。

$ (echo $BASH_SUBSHELL)
1
$ echo $BASH_SUBSHELL | cat
0
Run Code Online (Sandbox Code Playgroud)

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

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

以及实际含义 - 脚本片段是否运行在子进程中是必不可少的,而不是一些术语狡辩。

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

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

  • Nit:`BASH_SUBSHELL` 的设置非常可靠,但正确获取其值是不确定的。请注意 [the docs](https://www.gnu.org/software/bash/manual/bash.html#index-BASH_005fSUBSHELL) 所说的内容:“在每个子外壳或子外壳环境中增加 1 **当外壳开始执行时在那个环境中。**”我认为在管道示例中,当变量被扩展时,bash 还没有开始在该子 shell 中执行。您可以将 `echo $BASH_VERSION` 与 `declare -p BASH_VERSION` 进行比较 - 后者应该可以通过管道、后台作业等可靠地输出 1。 (11认同)
  • 甚至说,`eval 'echo $BASH_SUBSHELL $BASHPID' | cat` 将为 `BASH_SUBSHELL` 输出 1,因为该变量在执行开始后被扩展。 (6认同)
  • 所有这些参数也应该适用于进程和命令替换、bg 进程,但只是管道不同。查看代码,在 *foreground* 管道的情况下,增加 `subshel​​l_level` 确实是 [deferred](http://git.savannah.gnu.org/cgit/bash.git/tree/execute_cmd.c#n4468),这可能有某种原因,但我无法弄清楚;-) (4认同)
  • 你是对的。似乎切特明确打算那样做。https://lists.gnu.org/archive/html/bug-bash/2015-06/msg00050.html :“BASH_SUBSHELL 测量(...)子外壳,而不是管道元素。” https://lists.gnu.org/archive/html/bug-bash/2015-06/msg00054.html:“我将考虑是否应该记录现状或扩展‘subshel​​l’的定义$BASH_SUBSHELL 反映。” (2认同)
  • @JoL 你错了,扩展也发生在单独的过程中,请阅读上面讨论中的链接和示例;或者只是尝试使用 `echo $$ $BASHPID $BASH_SUBSHELL | 猫`。 (2认同)