如何防止不受支持的“shopt”选项在我的 .bashrc 中导致错误?

The*_*des 9 bash configuration shell-script shopt

我在一个相对异构的环境中工作,我可能在不同的 HPC 节点、VM 或我的个人工作站上运行不同版本的 Bash。因为我将我的登录脚本放在 Git 存储库中,所以我希望全面使用相同的(ish).bashrc,而没有很多“如果这个主机,那么......”类型的混乱。

喜欢Bash 的默认行为吗?4.1一个可扩展cd $SOMEPATHcd /the/actual/path按下时Tab键。在bash 4.2及以上,则需要shopt -s direxpand重新启用此行为,而并没有变得可用,直到4.2.29。不过,这只是一个例子;另一个可能相关的shopt选项complete_fullquote(虽然我不知道它的确切作用)可能也改变了 v4.2 的默认行为。

但是,direxpand早期版本的 Bash 无法识别,如果我尝试shopt -s direxpand在我的.bashrc.

-bash: shopt: direxpand: invalid shell option name
Run Code Online (Sandbox Code Playgroud)

我想做的是shop -s direxpand在 Bash > 4.1 上以一种稳健的方式包装一个条件来启用该选项,而不会影响旧版本的 Bash(,不仅仅是将错误输出重定向到/dev/null)。

Gil*_*il' 17

我不明白将错误重定向到/dev/null. 如果您希望您的代码健壮到set -e,请使用常见的习惯用法… || true

shopt -s direxpand 2>/dev/null || true
Run Code Online (Sandbox Code Playgroud)

如果您想在该选项不存在的情况下运行一些后备代码,请使用以下返回状态shopt

if shopt -s direxpand 2>/dev/null; then
  … # the direxpand option exists
else
  … # the direxpand option does not exist
fi
Run Code Online (Sandbox Code Playgroud)

但是如果你真的不喜欢重定向错误,你可以使用完成机制来执行自省。这假设您没有带有 bash 的过时机器?2.03 没有可编程完成。

shopt_exists () {
  compgen -A shopt -X \!"$1" "$1" >/dev/null
}
if shopt_exists direxpand; then
  shopt -s direxpand
fi
Run Code Online (Sandbox Code Playgroud)

这种方法避免了分叉,这在某些环境(如 Cygwin)上很慢。直接的也是如此2>/dev/null,我认为你无法在性能上击败它。

  • @TheDudeAbides 我读到在 [unix.se] 上以这种方式使用 `compgen`,我不知道是谁首先提出的。(在 bash 可编程完成之前,我停止使用 bash 作为我的主 shell。)在编程中,请求许可通常是一个坏主意,因为存在许可检查与您实际执行的操作不匹配的风险,无论是因为编码错误(您没有完全检查您认为要检查的内容)或因为[您在使用之前检查的内容发生了变化](https://en.wikipedia.org/wiki/Time_of_check_to_time_of_use)。 (4认同)

Luc*_*ini 14

检查direxpand输出中是否存在shopt并启用它,如果它是:

shopt | grep -q '^direxpand\b' && shopt -s direxpand
Run Code Online (Sandbox Code Playgroud)

  • Bash 允许查询特定的 shell 选项,因此可以使用 `[ -z "$(shopt -po direxpand 2>&-)" ] || shopt -s direxpand`。没有更多的正则表达式问题!:-) (5认同)
  • 最好使 `grep -q '^direxpand\b'` 以防将来的某些版本或 bash 的分支有一个包含它作为子字符串的选项 _and_ 删除了 `direxpand`。在这种特定情况下不太可能,但健壮的成本并不高。 (4认同)

The*_*des 5

当您确定某个特定shopt选项在 Bash 的某个主要/次要/补丁版本中可用时,您可以检查$BASH_VERSION变量或$BASH_VERSINFO[]数组元素以便有条件地启用它。

这是 Bash 4.2.29 或更高版本的测试,该版本direxpand 首次引入4.2 系列:

if [[ $BASH_VERSION == 4.2.* && ${BASH_VERSINFO[2]} -ge 29 ]] ||
   [[ ${BASH_VERSINFO[0]} -eq 4 && ${BASH_VERSINFO[1]} -ge 3 ]] ||
   [[ ${BASH_VERSINFO[0]} -ge 5 ]]; then
    shopt -s direxpand
fi
Run Code Online (Sandbox Code Playgroud)

编辑:需要明确的是,这是一个荒谬的过度设计的解决方案,用于简单地忽略来自您的登录脚本的错误消息,但我确实想记录它,无论如何,为了我自己的启发。

注意周围的大括号,这必需的,以及使用and ,它们进行整数而不是(依赖于语言环境的)词法比较。如果不带引号,则的RHS操作被视为猛砸内“extglob”模式/条件语句,如注意这里,这使得更美观的“开始使用”比较不是一个正则表达式将,IMO。${BASH_VERSINFO[index]}-eq-gt==[[]]

$BASH_VERSINFO数组包含您在以下输出中看到的所有信息bash --version

bash --version | head -1
# result:
# GNU bash, version 4.3.48(1)-release (x86_64-pc-linux-gnu)

declare -p BASH_VERSINFO
# result:
# declare -ar BASH_VERSINFO='([0]="4" [1]="3" [2]="48" [3]="1" [4]="release" [5]="x86_64-pc-linux-gnu")'
Run Code Online (Sandbox Code Playgroud)

当它是不是从文件明确shopt在该版本的Bash(S)成为支持或改变他们的行为,由卢西亚诺提出的方法是好的:

# note the '-q' so that the matched pattern isn't actually printed
shopt | grep -q direxpand && shopt -s direxpand
Run Code Online (Sandbox Code Playgroud)

...就像吉尔斯提出的解决方案一样,忽略错误 ( shopt -s direxpand 2>/dev/null),$?如果绝对必要,可能会检查。

参考文献:123
相关阅读:设置和禁用了javascript -为什么是两个?