【问题标题】:HOWTO: Detect bash from shell script如何:从 shell 脚本中检测 bash
【发布时间】:2011-03-13 02:37:42
【问题描述】:

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

$ source envsetup.sh

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

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

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

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

【问题讨论】:

    标签: bash shell scripting


    【解决方案1】:

    您可以查看大量环境变量,但其中许多不会检测到是否从 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,这可能是您所能获得的最接近的结果。

    【讨论】:

    • lsof -p $$ | awk '(NR==2) {print $1}' 在 Darwin 上不可靠,所以 ps -o command -p $$ 是最好的选择。
    【解决方案2】:

    这也有效

    [ -z "$BASH_VERSION" ] && return
    

    【讨论】:

    • 对于zsh也可以这样做,所以if [ -n "${BASH_VERSION}" ]; then ... elif [ -n "${ZSH_VERSION}" ]; then ... fi
    • 拥有脚本:#!/bin/sh \n echo $BASH_VERSION,甚至使用 sh 或 csh 从 bash shell 运行它:$sh ./test.sh,您将从用户喜欢的 bash 版本中获得 env var.... 完全误导运行脚本
    【解决方案3】:

    这是一个不错的方法:

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

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

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

    讨论:

    • $SHELL 变量表示用户的首选 shell ... 您对目前正在运行的外壳一无所知。

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

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

    • 在你的脚本顶部放置一个 hash-bang 即 #!保证它将被提供给你选择的解释器。例如,我的~/.xinitrc 会被/bin/sh 解释,无论顶部出现什么hash-bang(如果有)。

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

    • 有时使用最低公分母功能集编写兼容代码是合理的……但有时并非如此。大多数添加的功能都是有原因的。由于这些解释器是“几乎”图灵完备的,因此可以保证“几乎”可以与其他解释器进行模拟……可能,但不合理。
    • 有两层不兼容:语法和语义。例如,csh 的 if-then-else 语法与 bash 是如此不同,以至于编写兼容代码的唯一方法就是完全不使用 if-then-else 语句。这是可能的,但成本很高。如果语法错误,脚本根本不会执行。一旦您克服了这一障碍,根据运行的解释器方言,外观合理的代码会通过多种方式产生不同的结果。
    • 对于大型复杂的程序,编写两个版本没有意义。用您选择的语言编写一次。如果有人在错误的解释器下启动它,您可以检测到并简单地exec 正确的解释器。

    • 5路检测器可以在这里找到:

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

      它可以区分:

      • 狂欢
      • bsd-csh
      • 破折号
      • ksh93
      • zsh5

      此外,在我的 Ubuntu Xenial 机器上,五项检查还包括以下内容:

      • ash 是 dash 的符号链接
      • csh 是 /bin/bsd-csh 的符号链接
      • ksh 是 /bin/ksh93 的符号链接
      • sh 是 dash 的符号链接

    【讨论】:

      【解决方案4】:

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

      /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'
      

      【讨论】:

      • 对于Linux,这似乎是个好主意...不确定它是否可以移植到问题中提出的OS X或Cygwin(我知道FreeBSD上的/proc默认情况下没有安装.. .)
      【解决方案5】:

      改进@Dumble0re 答案以支持更多的shell dialets:

      # 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】:

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

        另外,您可以使用ps $$ 查找当前shell,如果您想知道脚本在哪个shell 下运行(不一定是登录shell),可以使用它。将ps 输出缩减为shell 名称:ps o command= $$(不确定这是多么跨平台安全,但它适用于Mac OS X)。

        【讨论】:

        • 在依赖 $SHELL 之前从 bash 中试试这个:csh -c 'echo "$SHELL"'
        • 我对这个问题的印象是脚本需要知道登录shell。使用 bash 作为登录 shell,运行该命令应该返回 bash,这是预期的。
        • 不是登录外壳。如果你使用 bash 登录,然后更改为 csh,$SHELL 仍然是 bash,但 source 会失败。
        • ps o command= $$ 也适用于 Linux,现在我必须检查 cygwin.. 但是你知道, ps 命令有很多变体..
        • ps -o command -p $$ 可能是最便携的形式,但它仍然可能失败。要可靠地工作,这实际上是一个棘手的问题。
        【解决方案7】:

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

        【讨论】:

        • 7 年后,虽然我明白这一点,但对我来说似乎不太实用。与浏览器中的功能检测相比,弄清楚如何测试各种 shell 功能对我来说听起来比使用 Modernizr 之类的东西或检测 shell 更具挑战性。
        猜你喜欢
        • 1970-01-01
        • 2019-05-02
        • 2010-09-28
        • 1970-01-01
        • 2011-07-19
        • 1970-01-01
        • 2014-06-05
        • 2018-02-23
        • 2014-09-27
        相关资源
        最近更新 更多