为什么bash"test -n"命令为$!($ at)位置参数提供了错误的结果而"!test -z"有效?

Ger*_*elt 7 bash shell

使用$@一个bash内test表达产生奇怪的结果.

这里有一个最小的测试脚本来重现问题(在相同的测试,其中:替代配方中通无误差):

#! /bin/bash
# file path: ./bash_weirdness.sh

echo "args: '$@'"

echo "--- empty ---"

if test "" ; then echo      "1.A1.TEST      - not empty?"; fi
if test -z "" ; then echo   "1.A2.EMPTY     - empty?"; fi
if test -n "" ; then echo   "1.A3.NE        - not empty?"; fi
if ! test "" ; then echo    "1.B1.NOT.TEST  - empty?"; fi
if ! test -z "" ; then echo "1.B2.NOT.EMPTY - not empty?"; fi
if ! test -n "" ; then echo "1.B3.NOT.NE    - empty?"; fi

echo "--- space ---"

if test " " ; then echo      "2.A1.TEST      - not empty?"; fi
if test -z " " ; then echo   "2.A2.EMPTY     - empty?"; fi
if test -n " " ; then echo   "2.A3.NE        - not empty?"; fi
if ! test " " ; then echo    "2.B1.NOT.TEST  - empty?"; fi
if ! test -z " " ; then echo "2.B2.NOT.EMPTY - not empty?"; fi
if ! test -n " " ; then echo "2.B3.NOT.NE    - empty?"; fi

echo "--- \$@ ---"

if test "$@" ; then echo      "3.A1.TEST      - not empty?"; fi
if test -z "$@" ; then echo   "3.A2.EMPTY     - empty?"; fi
if test -n "$@" ; then echo   "3.A3.NE        - not empty?"; fi
if ! test "$@" ; then echo    "3.B1.NOT.TEST  - empty?"; fi
if ! test -z "$@" ; then echo "3.B2.NOT.EMPTY - not empty?"; fi
if ! test -n "$@" ; then echo "3.B3.NOT.NE    - empty?"; fi

echo "--- \$* ---"

if test "$*" ; then echo      "4.A1.TEST      - not empty?"; fi
if test -z "$*" ; then echo   "4.A2.EMPTY     - empty?"; fi
if test -n "$*" ; then echo   "4.A3.NE        - not empty?"; fi
if ! test "$*" ; then echo    "4.B1.NOT.TEST  - empty?"; fi
if ! test -z "$*" ; then echo "4.B2.NOT.EMPTY - not empty?"; fi
if ! test -n "$*" ; then echo "4.B3.NOT.NE    - empty?"; fi
Run Code Online (Sandbox Code Playgroud)

前两个部分('空'和'空间')在那里验证test,test -n并且test -z工作正常; 那是多么糟糕的混乱.

$@表示'无参数'时$#会出现问题,即何时为零(0).

三次试运行表明出了什么问题:注意'3.A3.NE--不是空的?' 第三轮报道:

$ ./bash_weirdness.sh x
->
args: 'x'
--- empty ---
1.A2.EMPTY     - empty?
1.B1.NOT.TEST  - empty?
1.B3.NOT.NE    - empty?
--- space ---
2.A1.TEST      - not empty?
2.A3.NE        - not empty?
2.B2.NOT.EMPTY - not empty?
--- $@ ---
3.A1.TEST      - not empty?
3.A3.NE        - not empty?
3.B2.NOT.EMPTY - not empty?
--- $* ---
4.A1.TEST      - not empty?
4.A3.NE        - not empty?
4.B2.NOT.EMPTY - not empty?
Run Code Online (Sandbox Code Playgroud)

哪个好.

$ ./bash_weirdness.sh ""
->
args: ''
--- empty ---
1.A2.EMPTY     - empty?
1.B1.NOT.TEST  - empty?
1.B3.NOT.NE    - empty?
--- space ---
2.A1.TEST      - not empty?
2.A3.NE        - not empty?
2.B2.NOT.EMPTY - not empty?
--- $@ ---
3.A2.EMPTY     - empty?
3.B1.NOT.TEST  - empty?
3.B3.NOT.NE    - empty?
--- $* ---
4.A2.EMPTY     - empty?
4.B1.NOT.TEST  - empty?
4.B3.NOT.NE    - empty?
Run Code Online (Sandbox Code Playgroud)

哪个也好.

$ ./bash_weirdness.sh 
->
args: ''
--- empty ---
1.A2.EMPTY     - empty?
1.B1.NOT.TEST  - empty?
1.B3.NOT.NE    - empty?
--- space ---
2.A1.TEST      - not empty?
2.A3.NE        - not empty?
2.B2.NOT.EMPTY - not empty?
--- $@ ---
3.A2.EMPTY     - empty?
3.A3.NE        - not empty?
3.B1.NOT.TEST  - empty?
--- $* ---
4.A2.EMPTY     - empty?
4.B1.NOT.TEST  - empty?
4.B3.NOT.NE    - empty?
Run Code Online (Sandbox Code Playgroud)

这是不是罚款都:看到不正确的"3.A3.NE"的结果.

Joh*_*ica 7

"$ @"并且test不要混

test -n "$@"
Run Code Online (Sandbox Code Playgroud)

此测试未正确编写.该-n试验只需要一个参数,并告诉你,如果这样的说法就是一个空字符串或没有."$@"将扩展为一个单词,多个单词,甚至根本没有单词.因此写作是不合适的test -n "$@".

如果要检查参数是否已通过,请使用以下方法之一:

test $# -gt 0
[[ $# -gt 0 ]]
(($# > 0))
(($#))
Run Code Online (Sandbox Code Playgroud)

如果要检查参数是否已传递且非空,请"$*"改用.$*总是扩展为单个字符串,因此它兼容test -n.

test -n "$*"
[[ -n $* ]]
Run Code Online (Sandbox Code Playgroud)


混乱的情况 test -n

$ test -n; echo $?
0
Run Code Online (Sandbox Code Playgroud)

那么为什么这个测试通过呢?好吧,如果你没有传递参数,-n那么bash不会将其-n视为运算符.相反,它将测试理解为一种形式

test STRING
Run Code Online (Sandbox Code Playgroud)

如果你在test STRING没有运算符的情况下编写,则它隐含等效于test -n STRING.是非空test STRING的测试的简便方法STRING.

换句话说,test -n相当于

test -n '-n'
Run Code Online (Sandbox Code Playgroud)

此测试通过,因为'-n'是非空字符串.


"$ @"太棒了

顺便说一下,"$@"行为方式实际上是一件好事."$@"是在正确处理空格并避免单词拆分和通配的问题时访问完整参数列表的最佳方法.如有疑问,请使用"$@".

如果要存储"$@"在变量中,请使用数组.

args=("$@")

for arg in "${args[@]}"; do
    echo "$arg"
done
Run Code Online (Sandbox Code Playgroud)

这将保留多wordedness"$@".


Ger*_*elt 5

在将这种令人困惑的观察行为转换为可能适合 StackOverflow 的测试脚本和问题时,发生了几件事情,导致了这个“为什么”问题的答案。

TL;DR 答案在下面的第 5 节中;我描述了发现的整个过程,因为我在此过程中(重新)学习了很多东西,我认为其他人会遵循相同的发现路径,如果不是非常相似的话。谷歌并不总是你的朋友!

1.总Google Fu失败

首先,“google”在您搜索或类似查询时没有提供任何内容bash test $@ odd result因为它去掉了最重要的$@. 改写$@asdollar at也没有吐出任何有用的东西,这让我很担心。我什至开始怀疑我正在运行的 bash(Windows 上的 mSysGit bash)。

由于这次失败,我去查找了它的官方名称(行话)$@可能是什么(我上次阅读 bash 手册可能是在 20 年前;从那以后我一直在涉足它,这就是我因我懒惰而不学习我编写(少量)代码的语言而受到惩罚。)

2. 名称为:'位置参数'。$@(和$*

另一轮搜索导致http://www.tldp.org/LDP/abs/abs-guide.pdf告诉我$@(并且$*- 哦,对,忘记了关于你的一切!)是“位置参数”。

3.什么确切的行为test

在这次搜索热潮中,我还发现了http://wiki.bash-hackers.org/commands/classictest#number_of_arguments_rules,它解释了test当参数数量可能不是您所期望的时的行为方式。

但后来我确实有引号$@,不是吗?所以它必须是一个单一的参数test,即使参数列表为空!?( $# = 0)

(错!不是!)

test表达式求值规则集

复制(用最少的编辑来匹配这个问题)来自上面链接的非常重要的部分——这让我的大脑点击了一个预感,然后完全理解:

http://wiki.bash-hackers.org/commands/classictest#number_of_arguments_rules

参数数量规则

test内置,根据其特别是隐藏[的名字,看似简单,但实际上有时会引起很多麻烦。困难之一是 的行为test不仅取决于其参数,还取决于其参数的数量

以下是手册中的规则(注意:这是针对 command test,因为[参数的数量是在没有 final 的情况下计算的],例如[ ]遵循“零参数”规则):

  • 0 个参数

    表达式是false

  • 1 个参数

    表达式是true当且仅当参数不为 null

  • 2 个参数

    如果第一个参数是!(感叹号),则表达式为true当且仅当第二个参数为null

    如果第一个参数是上面在语法规则(例如and )下列出的一元条件运算符之一,则表达式为if 一元测试为。-n-ztruetrue

    如果第一个参数不是有效的一元条件运算符,则表达式为false

  • 3 个参数

    如果第二个参数是上面在语法规则下列出的二进制条件运算符之一,则表达式的结果是使用第一个和第三个参数作为操作数的二进制测试的结果。

    如果第一个参数是!,则该值是使用第二个和第三个参数的双参数测试的否定。

    如果第一个参数恰好为(,第三个参数恰好为),则结果是第二个参数的单参数测试。否则,表达式为false。在-a-o运营商被认为是二元运算符在这种情况下,(注意:这意味着运营商-a不是一个文件,运营商在这种情况下!)

  • 4 个参数

    如果第一个参数是!,则结果是由其余参数组成的三参数表达式的否定。否则,将使用上面列出的规则根据优先级解析和评估表达式。

  • 5 个或更多参数

    根据优先级使用上面列出的规则解析和评估表达式。

这些规则可能看起来很复杂,但在实践中并没有那么糟糕。了解它们可能会帮助您解释您可能遇到的一些“无法解释的”行为:

(注意:下一部分将解释原文以匹配这个问题!)

 function test {
   if [ -n "$@" ] ; then echo "argument list is not empty"; fi
 }

 test
Run Code Online (Sandbox Code Playgroud)

此代码打印“参数列表不为空”,即使-n something应该是falseifsomething是空字符串""- 为什么?

在这里,"$@"扩展为一个空的参数列表,即"$@"实际上没有结果(Bash 将它从命令的参数列表中删除!)。所以测试实际上[ -n ]属于“一个参数”规则,唯一的参数是“ -n”,它不为空,因此测试返回true。因此,通常的解决方案是引用参数扩展,例如,[ -n "$var" ]这样测试总是有 2 个参数,即使第二个参数是空字符串,也不适用于"$@".

例如,这些规则还解释了原因,-a并且-o可以有多种含义。

4. 再试一次...

在说明书http://www.tldp.org/LDP/abs/abs-guide.pdf和行为的非显而易见的比特test中所描述http://wiki.bash-hackers.org/commands/classictest# number_of_arguments_rules驱使我使用问题中指定的测试脚本运行第四个测试:

$ ./bash_weirdness.sh x y z
->
args: 'x y z'
--- empty ---
1.A2.EMPTY     - empty?
1.B1.NOT.TEST  - empty?
1.B3.NOT.NE    - empty?
--- space ---
2.A1.TEST      - not empty?
2.A3.NE        - not empty?
2.B2.NOT.EMPTY - not empty?
--- $@ ---
util/bash_weirdness.sh: line 25: test: y: binary operator expected
util/bash_weirdness.sh: line 26: test: too many arguments
util/bash_weirdness.sh: line 27: test: too many arguments
util/bash_weirdness.sh: line 28: test: y: binary operator expected
3.B1.NOT.TEST  - empty?
util/bash_weirdness.sh: line 29: test: too many arguments
3.B2.NOT.EMPTY - not empty?
util/bash_weirdness.sh: line 30: test: too many arguments
3.B3.NOT.NE    - empty?
--- $* ---
4.A1.TEST      - not empty?
4.A3.NE        - not empty?
4.B2.NOT.EMPTY - not empty?
Run Code Online (Sandbox Code Playgroud)

现在有提示!

5. 回答“为什么?” 题

现在我知道了。

$@,即使引用为"$@",“某种方式”也被转换为 中可用参数的确切数量$@,这对于空参数列表 ( $# = 0) 意味着它"$@"完全不代表任何内容test -n "$@"因此,“扩展”到test -n其中,遵循http:// 中描述的规则wiki.bash-hackers.org/commands/classictest#number_of_arguments_rules几乎与那里描述的示例相同,并遵循相同的“1 个参数规则”:因此-nintest -n不是test option: return true if string is non-empty“一个参数”,而是“一个参数”,不为空,因此test -n "$@" --> test -n --> test "-n" --> TRUE这会产生问题中第三次测试运行中显示的意外错误测试结果。

“以某种方式”是由于 bash 必须在内部执行什么操作以保证 in 中的参数数量"$@" 始终等于循环语句中的轮数,例如for f in "$@" ; do ... done并且始终与 parameters 数量匹配$#

如果处理"$@"一个空的参数列表会产生一个空字符串"",而不是什么都没有,那么这样的循环语句将执行一(1)圆,而不是预期的零(0)轮,这将是非常反直觉。

一旦你意识到这一点,http://www.tldp.org/LDP/abs/abs-guide.pdf 中关于$@vs的描述 $*是显而易见的。

6.似曾相识。现在我知道了,所以突然提供了类似的问题(和答案)

这感觉就像我在使用 VMS 后阅读 UNIX 手册页的过去一样:那些“手册页”非常简洁,而且在我通过其他渠道获得知识后,不仅对我有用。

[编辑] 回顾说明:

对于下面列出的许多 SO 问题,我说“不会帮助我”只是因为在我写这个答案的时候,我反思并想知道我的问题是否真的是一个重复的问题。这些其他问题以及那里提供的答案本身都非常有价值。

“不会帮助我”这句话在那里,所以你可能明白我的大脑需要一些不同的东西来解决我的困惑和对我的机器失去信任的问题。它是说“我完全需要这个而不是其中之一”,从而显示了我的内部论点是否我的这个问题是重复的。

我现在自己的结论:一个问题在技术上几乎相同(除了!in ! test -z),虽然它的答案非常接近我需要的,但是,至少对我来说,这不是一个重复的答案,因为它更加关注到的细节的深度原因test$@互动。回想起来,此答案适合所需的精确问题上下文使这不是重复的问题,至少对我而言。尽管它接近于一体。

他们来了: