HOW TO:从shell脚本中检测bash

问题描述:

方案是要求用户提供脚本文件:HOW TO:从shell脚本中检测bash

$ source envsetup.sh 

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

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

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

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

有许多环境变量可供您查看,但其中许多环境变量不会检测到是否从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: 

有一些具体的不同之处在于他们拥有的由子壳被继承这是那里的环境真的东西打破了习惯的各种炮弹变量。几乎可行的唯一的事情是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的话,这可能就像你所能得到的一样。

SHELL环境变量会告诉你什么登录shell正在运行。

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

+0

在bash之前试用这个命令,然后再依赖'$ SHELL':'csh -c'echo“$ SHELL”'' – 2010-07-08 00:42:12

+0

我对这个问题的印象是脚本需要知道登录shell。使用bash作为登录shell,运行该命令应返回bash,这是预期的。 – Jeff 2010-07-08 00:50:33

+0

不是登录shell。如果你使用bash登录然后改为csh,$ SHELL仍然是bash,但是'source'会失败。 – 2010-07-08 01:13:06

这工作也

[ -z "$BASH_VERSION" ] && return 
+1

也可以为zsh做这个,所以'if [-n“$ {BASH_VERSION}”];那么... elif [-n“$ {ZSH_VERSION}”];然后... fi' – 2011-03-10 06:43:35

+0

有脚本:'#!/ bin/sh \ n echo $ BASH_VERSION',甚至用sh或csh从bash shell:'$ sh。/ test.sh'运行它,你将得到来自用户首选bash版本的env var ....完全误导运行脚本 – gcb 2013-06-03 21:36:39

什么阻止你写出移植(即壳无关)的脚本?

+0

因为上游非常强烈地依赖它。这不是我可以控制的;-) – 2010-07-08 07:17:00

+0

所以你有点修补现有的脚本的一些分布? – 2010-07-08 07:19:14

+0

出于好奇,envsetup.sh可能使用哪些特定于bash的功能?我会想象它只包含一些'export A = B'语句... – 2010-07-08 07:21:23

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

我认为这将是兼容的最实用和交叉外壳

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

说明:

/proc/self总是指向当前正在执行的进程,例如,运行以下揭示了PID readlink它自己(不是执行readlink的外壳)

$ 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 

这里是结果以短跑和zsh运行,并运行bash thr一个符号链接,甚至通过一个副本。

[email protected]:~$ cp /bin/bash ./my_bash_copy 
[email protected]:~$ ln -s /bin/bash ./hello_bash 
[email protected]:~$ 

[email protected]:~$ 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) 
[email protected]:~$ 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' 

这里是一个很好的方式:

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

,你当然可以用您想要的任何东西的“回声”的声明。

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

讨论:

  • $SHELL变量表示用户的首选外壳......它告诉你 什么关于此刻正在运行的外壳。

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

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

  • 把散列砰即#!在你的脚本的顶部确实保证它会被输送到您选择的解释。例如,我的~/.xinitrc可以被/bin/sh解释,无论什么样的hash-bang(如果有的话)出现在顶部。

  • 好的做法是测试一些可靠地存在于两种语言中但功能不同的功能。相比之下,它会而不是一般来说是安全的尝试你想要的功能,看看它是否失败。例如,如果您想使用内置的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路检查还包括以下内容:

    • 灰冲
    • CSH是一个符号链接为/ bin/BSD一个符号链接-csh
    • ksh的是一个符号链接为/ bin/ksh93的
    • sh为冲
一个符号链接