确定当前正在运行的Shell是Bash还是Zsh

5

如何准确判断当前正在运行的 shell 是 bash 还是 zsh?

(区分其他 shell 也是一个加分项,但只有 bash 和 zsh 是必要的)

我见过一些据称可以做到这一点的方法,但它们都存在一些问题(请参见下文)。

我认为最好的方法是运行一些语法,该语法适用于其中一种 shell 而不适用于另一种 shell,然后检查错误 / 输出以查看哪个 shell 正在运行。 如果这是最佳解决方案,那么对于此测试,哪个命令最好呢?

最简单的解决方案是,如果每个 shell 都包含相同名称的只读参数来标识 shell,则会更好。 然而,如果存在这样的内容,我还没有听说过。

确定当前正在运行的 shell 的非确定性方法:

# default shell, not current shell
basename "${SHELL}"

# current script rather than current shell
basename "${0}"

# BASH_VERSINFO could be defined in any shell, including zsh
if [ -z "${BASH_VERSINFO+x}" ]; then
    echo 'zsh'
else
    echo 'bash'
fi

# executable could have been renamed; ps isn't a builtin
shell_name="$(ps -o comm= -p $$)"
echo "${shell_name##*[[:cntrl:][:punct:][:space:]]}"

# scripts can be sourced / run by any shell regardless of shebang
# shebang parsing

3
因为我现在在手机上无法测试,所以作为评论发布,但我认为类似于is_zsh=; : | is_zsh=1的东西应该可以工作(利用Bash中管道中的所有命令都在子shell中运行,因此不能影响主执行环境的事实,而在Zsh中,管道中的最后一个命令不是在子shell中运行)。 - ruakh
@ruakh 谢谢。看起来可以工作了。各种 shell 实现者还没有共同制定一个标准的只读参数,这真是疯狂。 - XDR
@ruakh 大多数其他流行的非 bash / 非 zsh shell 是否会在当前 shell 中运行上一个命令(像 zsh 一样)或在子 shell 中运行(像 bash 一样)? - XDR
1
@ruakh 如果bash的lastpipe选项被设置,那将产生错误的结果。 - oguz ismail
2
脚本为什么需要在首次运行时在bashzsh下运行?选择一种语言,或将您的脚本限制在两种语言的交集中。(将第二个选项推向极端,就变成了“编写符合POSIX标准的shell脚本”。) - chepner
显示剩余6条评论
4个回答

3
在 $ 提示符下运行以下命令:
echo $0

但是在脚本中你不能使用$0,因为$0将变成脚本的名称本身。

如果shebang / magic number可执行文件是#!/bin/bash,在脚本内部查找当前shell(比如BASH):

#!/bin/bash
echo "Script is: $0 running using $$ PID"
echo "Current shell used within the script is: `readlink /proc/$$/exe`"

script_shell="$(readlink /proc/$$/exe | sed "s/.*\///")"

echo -e "\nSHELL is = ${script_shell}\n" 

if [[ "${script_shell}" == "bash" ]]
then
    echo -e "\nI'm BASH\n"
fi

输出:

Script is: /tmp/2.sh running using 9808 PID
Current shell used within the script is: /usr/bin/bash

SHELL is = bash

I'm BASH

如果 shebang 是 #!/bin/zsh,那么这将起作用。

然后,您将获得 SHELL 的输出:

SHELL is = zsh

1
你可以从不同的shell中获取带有shebang的文件,或将该文件作为参数运行到不同的shell中。Shebang解析只是告诉你shebang是什么,而不是运行文件的内容。 - XDR
1
这对Busybox不起作用:sh 2.shash 2.shhush 2.sh都将返回SHELL is = busybox.nosuid脚本中使用的当前shell是:/bin/busybox.nosuid - phuclv
在其他的shell中,它仍然是不可靠的,因为名称很容易被更改。例如,在许多发行版上,sh通常是指向dash或bash的符号链接。 - phuclv

3
虽然没有百分百可靠的方法来实现它,但做一个

可能会有所帮助。

echo $BASH_VERSINFO
echo $ZSH_VERSION

两者都是shell变量(非环境变量),由各自的shell设置。在另一个相应的shell中,它们为空。

当然,如果有人有意创建了这个名称的变量,或者导出了这样的变量,然后创建了不同种类的子shell,即

# We are in a non-bash here (ksh, zsh, dash, ...)
export BASH_VERSINFO=5
zsh  # the new zah subshell will see BASH_VERSION even though it is zsh

这种方法会失败;但我认为如果有人真的这样做,他是想故意破坏你的代码。


1
这几乎相当于我列出的那些不确定可行的方法之一。在Bash中,BASH_VERSINFO是只读的,而BASH_VERSION则不是,因此我使用了BASH_VERSINFO,因为它在Bash中无法取消设置。 - XDR
@XDR:这是正确的。在bash中,无法重新定义BASH_VERSION和BASH_VERSINFO,并且在调用子进程之前取消定义它们。为了完整起见:zsh不为ZSH_VERSION提供此保护。我会相应地调整我的回答。 - user1934428
1
我的前一个评论是正确的。我刚刚成功地在macOS 12.6.5上更改了BASH_VERSION的值,包括homebrew中的bash 5.2.15(1)-release和macOS中的3.2.57(1)-release。但是我无法更改任何版本的BASH_VERSINFO - XDR
@XDR:确实,我可以验证这一点。只有 BASH_VERSINFO 具有此属性。我已经相应地更新了我的答案。 - user1934428

1

这应该适用于大多数Linux系统:

cat /proc/$$/comm

快速简便。


并非每个类Unix系统都有procfs。 - chepner
然后我选择“大多数Linux系统” ;) - Jonas

0

根据@ruakh和@oguzismail的评论,我认为我有解决方案。

\shopt -u lastpipe 2> /dev/null
shell_name='bash'; : | shell_name='zsh'

1
zsh 中本身没有定义 shopt,所以如果您不关心使用旧版本的 bashzsh 混淆,则可以将 return 1 替换为 { echo zsh; return 1; }。(因为 lastpipe 直到 bash 4.2 才添加。) - chepner
@chepner:我想尽可能涵盖从当前版本到远古的zsh和bash版本。 shopt 可以由外部命令或用户定义的函数提供,因此我不应完全依赖它不存在。 有没有办法指定我只想在bash中运行内置的 shopt(而不是用户覆盖),并尝试在zsh中作为内置运行 shopt(这必然会失败)? - XDR
@chepner 我的代码现在使用builtin,在zsh 5.8、bash 5.0.16(1)-release和bash 3.2.57(1)-release上似乎都可以工作。有没有办法将其改为一行代码? - XDR
@ruakh: 我最近才开始写bash和zsh的脚本。我通过您的建议解决了lastpipe问题,修复了stderr输出的问题,并修复了可能运行非内置命令的问题。我保留了您的原始建议,因为我不确定是否可以删除它。我测试了一个更短的命令,看起来适用于zsh和bash,并更新了答案。如果您有更短或更好的版本,或者知道必须解决的其他问题,请告诉我。 - XDR

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