符合checkbashisms的方法来确定当前shell


18

在我的文章中.profile,我使用以下代码来确保仅在登录shell实际上是Bash时才获得与Bash相关的别名和函数:

# If the current (login) shell is Bash, then
if [ "${BASH_VERSION:-}" ]; then
  # source ~/.bashrc if it exists.
  if [ -f "$HOME/.bashrc" ]; then
    . "$HOME/.bashrc"
  fi
fi

我目前正在将我的shell配置文件,脚本和函数置于版本控制之下。我也最近开始去除shell脚本不从击的特定功能中受益,如休闲bash化,更换的过程中function funcname()funcname()

对于我的shell文件存储库,我已经配置了一个预提交钩子,该存储库中的每个文件都运行checkbashismsDebian的devscripts包中的实用程序,sh以确保我不会无意中引入Bash特定的语法。但是,这给我带来了一个错误.profile

possible bashism in .profile line 51 ($BASH_SOMETHING):
if [ "${BASH_VERSION:-}" ]; then

我想知道是否有一种方法可以检查正在运行的shell,而不会触发checkbashisms

我检查了POSIX列出的与外壳相关的变量的列表,希望它们中的一个可以用来显示当前的外壳。我还查看了在交互式Dash shell中设置的变量,但是,再次找不到合适的候选对象。

目前,我已经排除.profile因被处理checkbashisms; 这是一个小文件,因此手动检查并不困难。但是,在研究了该问题之后,我仍然想知道是否存在POSIX兼容方法来确定正在运行哪个Shell(或者至少是一种不会导致checkbashisms失败的方法)。


进一步的背景/说明

我将Shell配置文件置于版本控制下的原因之一是,在我目前定期登录的所有系统上配置环境:Cygwin,Ubuntu和CentOS(5和7,都使用Active Directory作为用户)身份验证)。我最经常通过X Windows /桌面环境和SSH登录远程主机。但是,我希望这是面向未来的方法,并且对系统依赖性和其他工具的依赖最少。

我一直在使用checkbashisms简单,自动的完整性检查来检查与外壳相关的文件的语法。这不是一个完美的工具,例如,我已经对其应用了补丁,因此它不会抱怨command -v在脚本中使用。在研究过程中,我了解到该程序的实际目的是确保遵守Debian政策,据我所知,该政策基于POSIX 2004而不是2008(或其2013年修订版)。


当整个工作要在不同的POSIX兼容环境上以不同方式运行时,您所做的就是与POSIX兼容。你的牛肉带有杂食症。
吉尔斯(Gillles)“所以-别再邪恶了”

顺便说一句,我从未使用过checkbashisms来检查配置的可移植性。我通过在不同系统上使用它来检查它。
吉尔(Gilles)'“ SO-别再邪恶了”

2
顺便说一句,对于您在此处所做的特定操作,请编写一个.bash_profile同时.profile(和(有条件地))来源的.bashrc
吉尔(Gilles)'“ SO-别再邪恶了”

感谢@Gilles的反馈。对于特定问题,这是一个非常优雅的解决方案。
安东尼G-莫妮卡的大法官

Answers:


16

您的

# If the current (login) shell is Bash, then
if [ "${BASH_VERSION:-}" ]; then
  # source ~/.bashrc if it exists.
  if [ -f "$HOME/.bashrc" ]; then
    . "$HOME/.bashrc"
  fi
fi

代码完全符合POSIX的要求,也是检查您当前正在运行的最佳方法bash。当然,该$BASH_VERSION变量是bash特定的,但这就是为什么要使用它!要检查您是否正在跑步bash

请注意,$BASH_VERSION将以或bash调用设置。断言您正在运行之后,您可以将其用作调用shell的指示符(尽管当POSIXLY_CORRECT在环境中或用或在环境中用SHELLOPTS = posix 调用时,也会设置该选项。但在所有这些情况下,其行为都将类似于)。bashshbash[ -o posix ]shbash-o posixbashsh


另一个变量,你可以代替使用$BASH_VERSION,并且checkbashism似乎并没有抱怨,除非通过-x选项$BASH。这也是特定于此的,bash因此您还应该可以使用它来确定您是否正在运行bash


我也认为这不是对的正确使用checkbashismscheckbashisms是一种工具,可帮助您编写可移植sh脚本(按照shDebian策略中的规范,是POSIX的超集),它可帮助识别人们在sh与符号链接到的系统上编写脚本所引入的非标准语法bash

A .profile由不同的Shell解释,其中许多不符合POSIX。一般情况下,不要使用sh作为你的登录shell,但炮弹一样zshfish或者bash更先进的互动功能。

bash并且zsh,当未被调用时,sh以及它们各自的概要文件会话文件(.bash_profile.zprofile)不符合POSIX规范(尤其是zsh),但仍可以读取.profile

因此,它不是您想要的POSIX语法,.profile而是与POSIX(for sh)兼容的语法,bash并且zsh如果您曾经使用过这些shell(可能甚至是Bourne,因为Bourne shell也可以读取.profile但在基于Linux的系统上并不常见)。

checkbashisms肯定会帮助您找出bashisms,但可能不会指出与zsh或不兼容的POSIX语法bash

在这里,如果您要使用bash特定的代码(例如解决该bash错误的方法,即该错误不会~/.bashrc在交互式登录外壳中读取),则更好的方法是进行此~/.bash_profile操作(在采购~/.profile通用会话的位置之前或之后)初始化)。


checkbashisms由于使用了变量,因此无法通过。
Thomas Dickey'3

2
@ThomasDickey,是的,但是在这种情况下,应忽略checkbashisms报告。到目前为止给出的所有其他解决方案都非常糟糕。
斯特凡Chazelas

@StéphaneChazelas您说其他所有解决方案都更糟。$0在这种情况下使用会出现什么问题?
Anthony G-Monica

2
@AnthonyGeoghegan不能区分bash sh和as sh
吉尔斯(Gillles)“所以-别再邪恶了”

如果dash或错误地使用调用了另一个非bash shell,这也将给出错误肯定argv[0] == "bash",如果不太可能的话,这是完全合法的。
凯文

17

通常一个$0用于此目的。在您链接的网站上,它表示:

0
   (Zero.) Expands to the name of the shell or shell script.

很简单!我已经习惯于使用$0shell脚本的名称了,以至于我忘了它也可以引用当前的shell。谢谢!
Anthony G-Monica

1
像往常一样,所有行为良好的程序都遵守此约定,但是恶意程序可以使用exec家族中的各种系统调用来惹恼您,因为它们允许调用程序识别要与作为参数传递的字符串分开运行的二进制文件0.
dmckee '16

这确实适用于.profile :) cute
Rob

5

通常,SHELL环境变量会告诉您默认的Shell。您不需要手动获取.bashrc文件(不是真的,请参见下面的更新),bash应该自动执行此操作,只需确保它位于$ HOME目录中即可。

另一种选择是做类似的事情:

 ps -o cmd= $$

它将告诉您当前进程的命令(不带参数,=不带值将不显示列标题)。输出示例:

 $ps -o cmd= $$
 bash
 $sh
 $ps -o cmd= $$
 sh

更新:

我纠正了!:)

.bashrc并不总是像下面的注释和/programming/415403/whats-the-difference-between-bashrc-bash-profile-and-environment中提到的那样来源

因此,您可以将.bashrc移至.bash_profile,然后查看它是否有效而无需进行测试。如果没有,您需要进行上述测试。


1
.bashrc实际上不是由登录shell来源,而是.profile(如果存在)或是.bash_profileSHELL并非由POSIX指定,很遗憾,的cmdformat选项也没有ps
Anthony G-Monica

1
这是我要使用的,但是也容易受到恶意操纵。
dmckee'3

请注意,虽然ps -o cmd= $$应该在几乎所有地方都可以使用,但$SHELL变量完全无关紧要。那是用户的默认外壳程序,与当前正在运行的外壳程序或正在运行外壳程序脚本的外壳程序无关。
terdon

2
不是,不是 ~/.profile作为登录Shell启动时,许多Shell将读取。因此,例如,您$SHELL可能是csh并且您运行bash -l。它将以bash作为登录外壳启动,因此它将读取~/.profile,但您$SHELL仍将指向csh。另外,.profile可能是由另一个脚本显式提供的。由于OP旨在获得最大的鲁棒性,因此应考虑各种情况。无论如何,$SHELL不提供有关当前正在运行的Shell的任何有用信息。
terdon

2
FWIW,我也接受纠正-关于SHELL未由POSIX指定:pubs.opengroup.org/onlinepubs/9699919799/basedefs/…–
Anthony G-Monica的正义,

4

问题以安抚方式询问用户的登录 shell以及当前 shell checkbashisms。如果那是用户登录的外壳,则可以使用/etc/passwd,例如,

MY_UID=$(id -u)
MYSHELL=$(awk -F: '$3 == '$MY_UID'{ print $7; }' </etc/passwd )

用户当然可以在登录后启动新的Shell。当然,如果一个是bash而另一个不是,则对bash的环境变量进行测试可能无济于事。

有些人可能想要使用getent而不只是passwd文件(但这不在问题范围内)。

基于对LDAP的评论和对的建议logname,可以使用以下替代形式:

MY_NAME=$(logname)
MYSHELL=$(getent passwd | awk -F: '$1 ~ /^'$MY_NAME'$/ {print $7;}' )

在测试它时,我注意到它logname不喜欢它的输入被重定向(所以我把表达式分开了)。快速检查显示getent应该可以在提到的平台上工作(尽管应该在原始问题中提供):


1
假设用户数据库在/ etc / passwd中(不是LDAP / NIS / mysql ...),并且每个用户ID只有一个用户名。(最好对照来检查第一列$(logname))。
斯特凡Chazelas

iirc,POSIX没有定义所需的实用程序(或者我会在答案中使用它)。是使用id变量还是用户可修改的变量是另一种选择。
Thomas Dickey'3

1
注意我说的$(logname)不是$LOGNAME。用户ID和登录Shell均来自登录时使用的用户名。除了在每个用户ID只有一个用户名的系统上,您无法从用户ID返回登录外壳。或IOW,用户数据库中的主键是用户名,而不是uid。
斯特凡Chazelas

感谢@ThomasDickey提供了不同角度的答案。但是,它比我想的要复杂一些(更像是jimmijStéphane的答案)。我将Shell配置文件置于版本控制下的原因之一是,在我目前定期登录的所有系统上配置环境:Cygwin,Ubuntu和CentOS(5和7,都使用Active Directory作为用户)身份验证)。因此,我宁愿保持逻辑尽可能简单。
Anthony G-Monica
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.