zsh -z 测试“+x”的含义

zsh -z 测试“+x”的含义

我是 zsh 的新手,多年来一直是 bash 用户。

在示例 zsh 脚本中,我看到一个测试:

if [ ! -z ${ZSH_MOTD_CUSTOM+x} ]; then

在 bash 中我期望:

if [ ! -z "$ZSH_MOTD_CUSTOM" ]; then

我不明白 zsh 示例中 +x 的含义以及 if 适用于 bash。

答案1

${var+string}与 Bourne shell(从 70 年代末开始)和任何 POSIX shell(包括 bash)中的操作符相同。您可以在 zsh 文档中找到它的描述info zsh 'Parameter Expansion'

${NAME+WORD}
${NAME:+WORD}
如果NAME已设置,或者在第二种形式中为非空,则替换 WORD;否则什么都不能替代。

那里比较难找,info bash 'Parameter Expansion'但那里都是一样的。对于sh,您可以检查POSIX 规范(尽管像 bash 手册中一样,您需要注意提到在${parameter:+word}1 中省略冒号的效果的那句话)。

与 bash 和其他类似 Bourne 的 shell 的主要区别在于,${ZSH_MOTD_CUSTOM+x}不加引号不受 split+glob 的影响,因为在 zsh 中,当您确实希望在参数扩展时执行 IFS 分割和/或 globbing 时,您必须明确请求它($=var对于分割,$~var对于通配符(严格来说,将 的内容$var视为模式),$=~var对于两者,这相当于$var在其他 shell 中)。

但这是错误的,因为未引用的扩展仍然会被空删除。但在这种情况下,偶然地,这不会成为问题,并且可能是作者选择编写它[ ! -z $expansion ][ -n $expansion ]不是行不通的原因。

if为空(在if未设置$expansion的情况下),变为而非,因此它不会测试扩展是否非空,而是测试其本身是否为空字符串(显然不是),因此它实现了由于错误的原因而得到正确的结果(测试变量是否已设置)。${ZSH_MOTD_CUSTOM+x}$ZSH_MOTD_CUSTOM[ ! -z $expansion ][ ! -z ][ ! -z '' ]-z

在 bash 中,未加引号的内容${ZSH_MOTD_CUSTOM+x}会受到 split+glob 的影响,因此这会更加错误,但因为它只能扩展到空字符串或文字,所以只有当碰巧包含以下内容x时,问题才会出现:$IFSx

$ bash -xc 'IFS=y; [ ! -z ${HOME+x} ]; echo "$?"'
+ IFS=y
+ '[' '!' -z x ']'
+ echo 0
0
$ bash -xc 'IFS=x; [ ! -z ${HOME+x} ]; echo "$?"'
+ IFS=x
+ '[' '!' -z '' ']'
+ echo 1
1
$ zsh -xc 'IFS=x; [ ! -z ${HOME+x} ]; echo "$?"'
+zsh:1> IFS=x
+zsh:1> [ ! -z x ']'
+zsh:1> echo 0
0

任何 POSIX shell 中的正确语法将会:

if [ -n "${ZSH_MOTD_CUSTOM+x}" ]

甚至在 Bourne shell 中也可以工作,其中[ -n "$var" ]的值会失败,$var例如=, -gt... 因为扩展只能是x或 空字符串(因此在 的那些古老实现中没有任何有问题的字符串[)。

现在 zsh 有更惯用的方法来检查变量是否已设置,例如:

if (( $+ZSH_MOTD_CUSTOM ))

where$+var扩展为1if$var已设置,0否则。

或者:

if [[ -v ZSH_MOTD_CUSTOM ]]

À la ksh(也可在 bash 中找到)。

无论如何,[ ! -z "$ZSH_MOTD_CUSTOM" ]本身是一种复杂的编写方式,[ -n "$ZSH_MOTD_CUSTOM" ]它会做一些不同的事情:if 检查变量是否非空,无论它是否被设置。主要区别在于,如果$ZSH_MOTD_CUSTOM已设置,它将返回 false,但返回空值。与变体相反,如果未设置变量并且启用了${ZSH_MOTD_CUSTOM+x}该选项,也会导致错误。nounset

$ zsh -c 'if [ -n "$foo" ]; then echo non-empty; else echo empty; fi'
empty
$ foo= zsh -o nounset -c 'if [ -n "$foo" ]; then echo non-empty; else echo empty; fi'
empty
$ foo= zsh -o nounset -c 'if [ -n "${foo+x}" ]; then echo set; else echo unset; fi'
set
$ zsh -o nounset -c 'if [ -n "$foo" ]; then echo non-empty; else echo empty; fi'
zsh:1: foo: parameter not set
$ zsh -o nounset -c 'if [ -n "${foo+x}" ]; then echo set; else echo unset; fi'
unset
$ zsh -o nounset -c 'if [ -n "${foo-}" ]; then echo non-empty; else echo empty; fi'
empty

(在最后一个中,我们使用${foo-}which扩展为相同的东西,但避免了(又名)$foo的影响)。nounsetset -u


1 请注意,在该功能所在的 Bourne shell 中,最初(在 70 年代末的 Unix 第 7中)仅支持不带冒号的版本,后来才支持带冒号的版本在系统III中

答案2

这是 Bash 中也存在的参数(变量)扩展语法。可能在大多数其他 Bourne 风格的 shell 中也是如此。在您的示例中,zsh 的唯一特殊之处是正在测试的变量。

在 bash 手册页中我们看到两件事。该扩展语法的解释:

  ${parameter:+word}
    Use Alternate Value. If parameter is null or unset, nothing is substituted,
    otherwise the expansion of word is substituted.

前面几段,在扩展语法部分的顶部,有一些东西很多人都忽略了:

  When not performing substring expansion, using the forms documented below
  (e.g., :-),  bash  tests for a parameter that is unset or null.  Omitting
  the colon results in a test only for a parameter that is unset.

最后一句是相关部分:Omitting the colon results in a test only for a parameter that is unset. 为了说明这一点,手册页中给出的语法是:

  ${parameter:+word}

省略冒号后,它是:

  ${parameter+word}

不同的是,后一种语法返回的值是单词在所有情况下,除非变量未设置(不存在)。所以您在 zsh 脚本中看到的语法:

  ${ZSH_MOTD_CUSTOM+x}

x如果已定义则返回$ZSH_MOTD_CUSTOM(无论值是什么 - 即使是空字符串),但如果变量未定义则返回空字符串。

最后,您的脚本示例仅测试变量是否存在,而不考虑它包含的值。我通过引用 Bash 手册页来回答,因为您提到您曾经是 bash 用户并且会发现 bash 描述很熟悉。

在我(相当长的)职业生涯中见过的 Bourne 风格的 shell 脚本中,这不是常见的习惯用法,因此:-很多人不知道没有.我自己最近才了解到它们。:=:?:+:

答案3

这不是由于test,而是由于 zsh 变量扩展。

如果已设置,该构造${foo+bar}将返回。bar$foo

因此,例如:

zsh% unset foo
zsh% echo ${foo+bar}

zsh% foo=
zsh% echo ${foo+bar}
bar

相关内容