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

Que*_*Que 5 shell bash zsh

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

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

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

在 bash 中我期望:

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

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

Sté*_*las 5

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

\n
\n

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

\n
\n

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

\n

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

\n

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

\n

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

\n

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

\n
$ bash -xc \'IFS=y; [ ! -z ${HOME+x} ]; echo "$?"\'\n+ IFS=y\n+ \'[\' \'!\' -z x \']\'\n+ echo 0\n0\n$ bash -xc \'IFS=x; [ ! -z ${HOME+x} ]; echo "$?"\'\n+ IFS=x\n+ \'[\' \'!\' -z \'\' \']\'\n+ echo 1\n1\n$ zsh -xc \'IFS=x; [ ! -z ${HOME+x} ]; echo "$?"\'\n+zsh:1> IFS=x\n+zsh:1> [ ! -z x \']\'\n+zsh:1> echo 0\n0\n
Run Code Online (Sandbox Code Playgroud)\n

任何 POSIX shell 中的正确语法是:

\n
if [ -n "${ZSH_MOTD_CUSTOM+x}" ]\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n

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

\n
if (( $+ZSH_MOTD_CUSTOM ))\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n

或者:

\n
if [[ -v ZSH_MOTD_CUSTOM ]]\n
Run Code Online (Sandbox Code Playgroud)\n

\xc3\x80 la ksh (也可在 bash 中找到)。

\n

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

\n
$ zsh -c \'if [ -n "$foo" ]; then echo non-empty; else echo empty; fi\'\nempty\n$ foo= zsh -o nounset -c \'if [ -n "$foo" ]; then echo non-empty; else echo empty; fi\'\nempty\n$ foo= zsh -o nounset -c \'if [ -n "${foo+x}" ]; then echo set; else echo unset; fi\'\nset\n$ zsh -o nounset -c \'if [ -n "$foo" ]; then echo non-empty; else echo empty; fi\'\nzsh:1: foo: parameter not set\n$ zsh -o nounset -c \'if [ -n "${foo+x}" ]; then echo set; else echo unset; fi\'\nunset\n$ zsh -o nounset -c \'if [ -n "${foo-}" ]; then echo non-empty; else echo empty; fi\'\nempty\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n
\n

\xc2\xb9 请注意,在该功能所在的 Bourne shell 中,最初(在 70 年代末的 Unix 第七版中仅支持不带冒号的版本,带冒号的版本后来出现在 SysIII 中

\n


Sot*_*oce 3

这是 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.
Run Code Online (Sandbox Code Playgroud)

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

  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.
Run Code Online (Sandbox Code Playgroud)

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

  ${parameter:+word}
Run Code Online (Sandbox Code Playgroud)

省略冒号后,它是:

  ${parameter+word}
Run Code Online (Sandbox Code Playgroud)

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

  ${ZSH_MOTD_CUSTOM+x}
Run Code Online (Sandbox Code Playgroud)

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

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

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

  • 顺便说一句,我认为 Bash 手册在这个领域写得有点过于混乱,主要是关于它几乎忽略了无冒号变体。另一方面,POSIX 文本有一个很好的组合表:https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_06_02 (2认同)