HOW TO:从shell脚本中检测bash

时间:2010-07-08 00:28:07

标签: bash shell scripting

场景是要求用户提供脚本文件:

$ source envsetup.sh

此脚本文件可能仅使用bash功能,因此我们检测到正在运行的shell是否为bash。

对于与bash共享通用语法的其他shell,例如sh,zsh,ksh,我想报告一个警告。

在Linux,Cygwin,OS X中检测当前shell的最可靠方法是什么?

我所知道的是$ BASH,但我想知道它可能失败的可能性。

7 个答案:

答案 0 :(得分:13)

你可以看到很多环境变量,但是很多环境变量都不会检测是否从bash中生成了不同的shell。请考虑以下事项:

bash$ echo "SHELL: $SHELL, shell: $shell, ARGV[0]: $0, PS1: $PS1, prompt: $prompt"
SHELL: /bin/bash, shell: , ARGV[0]: -bash, PS1: bash$ , prompt: 

bash$ csh
[lorien:~] daveshawley% echo "SHELL: $SHELL, shell: $shell, \$0: $0, PS1: $PS1, prompt: $prompt"
SHELL: /bin/bash, shell: /bin/tcsh, ARGV[0]: csh, PS1: bash$ , prompt: [%m:%c3] %n%#

[lorien:~] daveshawley% bash -r
bash$ echo "SHELL: $SHELL, shell: $shell, ARGV[0]: $0, PS1: $PS1, prompt: $prompt"
SHELL: /bin/bash, shell: , ARGV[0]: sh, PS1: bash$ , prompt:

bash$ zsh
% echo "SHELL: $SHELL, shell: $shell, ARGV[0]: $0, PS1: $PS1, prompt: $prompt"
SHELL: /bin/bash, shell: , ARGV[0]: zsh, PS1: % , prompt: % 

% ksh
$ echo "SHELL: $SHELL, shell: $shell, ARGV[0]: $0, PS1: $PS1, prompt: $prompt"
SHELL: /bin/bash, shell: , ARGV[0]: ksh, PS1: bash$ , prompt: 

有许多特定于各种shell的变量,除了它们习惯被子shell继承,这是环境真正破坏的地方。唯一有效的是ps -o command -p $$。从技术上讲,它为您提供了shell运行的命令名称。在大多数情况下,这将起作用...因为应用程序是使用exec系统调用的某种变体启动的,并且它允许命令的名称和可执行文件不同,所以它也可能失败。考虑:

bash$ exec -a "-csh" bash
bash$ echo "$0, $SHELL, $BASH"
-csh, /bin/bash, /bin/bash
bash$ ps -o command -p $$
COMMAND
-csh
bash$

另一个技巧是使用lsof -p $$ | awk '(NR==2) {print $1}'。如果您足够幸运,lsof方便,可能会尽可能接近。

答案 1 :(得分:10)

这也有效

[ -z "$BASH_VERSION" ] && return

答案 2 :(得分:7)

这是一个很好的方式:

if test -z "$(type -p)" ; then echo bash ; else echo sh ; fi

你当然可以用你想要的任何东西替换“echo”语句。

=================

讨论:

  • $SHELL变量表示用户首选的shell ...告诉我 你什么都不是正在运行的shell。

  • 测试$BASH_VERSION是一个99%的好主意,但如果有些明智的人将该名称的变量粘贴到sh环境中,它可能会失败。此外,它没有告诉你很多关于哪个非bash shell正在运行。

  • $(type -p)方法非常简单,即使有些聪明人在$PATH中创建了一个名为“-p”的文件,也能正常工作。此外,它可以作为4路歧视的基础,或5%歧视的80%,如下所述。

  • 在您的脚本顶部放置一个哈希爆炸,#!确实 保证它将被提供给您选择的解释器。例如,我的~/.xinitrc会被/bin/sh解释,无论最顶端是什么样的哈希(如果有的话)。

  • 良好做法是测试两种语言中可靠存在的某些功能,但行为方式不同。相比之下,一般来说,安全地尝试您想要的功能并查看它是否失败。例如,如果您想使用内置的declare功能并且它不存在,它可以运行程序,并且具有无限的下行潜力。

  • 有时使用最小公分母特征集编写兼容代码是合理的......但有时则不然。大多数添加的功能都是出于某种原因而添加的。由于这些解释器“几乎”图灵完成,因此“几乎”保证可以模仿另一个......可能,但不合理。
  • 有两层不兼容:语法和语义。例如,csh的if-then-else语法与bash完全不同,编写兼容代码的唯一方法就是不使用if-then-else语句。这是可能的,但它会带来很高的成本。如果语法错误,则脚本根本不会执行。一旦你克服了这个障碍,有许多方法可以使合理的代码产生不同的结果,这取决于解释器的哪种方言正在运行。
  • 对于大型复杂程序,编写两个版本没有意义。用你选择的语言写一次。如果有人在错误的翻译下启动它,你可以检测到它并且只是exec正确的翻译。

  • 可在此处找到5路探测器:

    https://www.av8n.com/computer/shell-dialect-detect

    它可以区分:

    • 的bash
    • BSD-CSH
    • 破折号
    • ksh93的
    • zsh5

    此外,在我的Ubuntu Xenial盒子上,该5向检查还包括以下内容:

    • ash是一个破折号的符号链接
    • csh是/ bin / bsd-csh
    • 的符号链接
    • ksh是/ bin / ksh93
    • 的符号链接
    • sh是破折号的符号链接

答案 3 :(得分:3)

我认为这将是最实用和跨壳兼容的

/proc/self/exe --version 2>/dev/null | grep -q 'GNU bash' && USING_BASH=true || USING_BASH=false

<强>解释

/proc/self将始终指向当前正在执行的进程,例如,运行以下内容会显示readlink它自己的pid(不是执行readlink的shell)

$ bash -c 'echo "The shell pid = $$"; echo -n "readlink (subprocess) pid = "; readlink /proc/self; echo "And again the running shells pid = $$"'

结果:

The shell pid = 34233
readlink (subprocess) pid = 34234
And again the running shells pid = 34233

现在: /proc/self/exe是运行可执行文件的符号链接

示例:

bash -c 'echo -n "readlink binary = "; readlink /proc/self/exe; echo -n "shell binary = "; readlink /proc/$$/exe'

结果:

readlink binary = /bin/readlink
shell binary = /bin/bash

以下是在dash和zsh中运行的结果,并通过符号链接运行bash,甚至通过副本运行。

aron@aron:~$ cp /bin/bash ./my_bash_copy
aron@aron:~$ ln -s /bin/bash ./hello_bash
aron@aron:~$ 

aron@aron:~$ dash -c '/proc/self/exe -c "readlink /proc/$$/exe"; zsh -c "/proc/self/exe --version"; ./hello_bash --version | grep bash; ./my_bash_copy --version | grep bash'
/bin/dash
zsh 5.0.7 (x86_64-pc-linux-gnu)
GNU bash, version 4.3.30(1)-release (x86_64-pc-linux-gnu)
GNU bash, version 4.3.30(1)-release (x86_64-pc-linux-gnu)
aron@aron:~$ dash -c '/proc/self/exe -c "readlink /proc/$$/exe"; zsh -c "/proc/self/exe --version"; ./hello_bash --version | grep bash; ./my_bash_copy --version | grep bash'

答案 4 :(得分:1)

SHELL环境变量将告诉您正在运行的登录shell。

此外,您可以使用ps $$查找当前shell,如果您想知道脚本运行的shell(不一定是登录shell),可以使用它。要将ps输出简化为shell名称:ps o command= $$(不确定跨平台安全性如何,但它适用于Mac OS X)。

答案 5 :(得分:1)

改善@ Dumble0re的答案以支持更多的shell拨号:

# original detect shell dialet, see: https://www.av8n.com/computer/shell-dialect-detect
# improvement version: https://gist.github.com/gzm55/028912a3d4c2846790c7438d0863fd7f
# `&&` will defeat the errexit option, see: http://www.binaryphile.com/bash/2018/08/13/approach-bash-like-a-developer-part-15-strict-mode-caveats.html
eval `echo ! : 2>/dev/null` : && echo "[ERROR] must not be executed or sourced by csh!" && exit 64

__DO_NOT_SUPPORT_FISH__=1

# Now that csh and fish has been excluded, it is safe to continue

__SHELL_DIALECT__=
case "${__SHELL_DIALECT__:=$(
  PATH="/dev/null/$$" set +e
  PATH="/dev/null/$$" export PATH="/dev/null/$$"
  type -p 2>/dev/null >/dev/null
  echo $? $(type declare 2>/dev/null; echo err=$?)
)}" in
"0 "*"not found err=1") __SHELL_DIALECT__=mksh ;;
"0 "*"not found err=127") __SHELL_DIALECT__=busybox ;; # ash busybox various
"0 "*"shell builtin err="*) __SHELL_DIALECT__=bash4 ;;
"1 "*"reserved word err="*) __SHELL_DIALECT__=zsh5 ;;
"1 "*"shell builtin err="*) __SHELL_DIALECT__=bash3 ;;
"2 err="*) __SHELL_DIALECT__=ksh93 ;;
"127 "*"not found err="*) __SHELL_DIALECT__=dash ;; # ash debian various
"127 err=127") __SHELL_DIALECT__=ash-bsd ;; # ash bsd-sh various
*) __SHELL_DIALECT__="unknown:$__SHELL_DIALECT__" ;;
esac

# detect bash posix mode, on bash, SHELLOPTS is a read only variable
case "$__SHELL_DIALECT__:${SHELLOPTS-}:" in
bash*:posix:*) __SHELL_DIALECT__="$__SHELL_DIALECT__-posix" ;;
*) ;;
esac

echo "__SHELL_DIALECT__=[$__SHELL_DIALECT__]"

重要的是,bash 3具有不同的检测方法,在MacOS上应为默认的/bin/sh

另一种方法是,在检测csh时,改进的方法避免了根据test/usr/bin/test的位置,MacOS没有此路径。

答案 6 :(得分:0)

我建议尝试检测是否存在您需要的功能,而不是bash vs zsh vs等。如果该功能存在,请使用它,如果没有使用替代方案。如果除了使用它并检查错误之外没有其他方法可以检测到该功能,那么虽然这有点难看,但我没有更好的解决方案。重要的是它应该比试图检测bash更可靠。而且由于其他shell(仍处于开发阶段)可能在将来的某个时候具有这种以前仅仅bash的功能,它确实处理了重要的事情,并且允许您避免必须维护每个shell的哪个版本的数据库支持哪个功能,哪个功能没有。