【问题标题】:Why does the bash "test -n" command give the wrong result for the $@ (dollar at) positional parameter while "! test -z" works?为什么 bash "test -n" 命令在 "!test -z" 有效时对 $@ (dollar at) 位置参数给出错误的结果?
【发布时间】:2015-02-11 16:05:09
【问题描述】:

在 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

前两个部分(“空”和“空格”)用于验证testtest -ntest -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?

这很好。

$ ./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?

这也很好。

$ ./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?

一点都不好:注意不正确的“3.A3.NE”结果。

【问题讨论】:

    标签: bash shell


    【解决方案1】:

    “$@”和test不要混用

    test -n "$@"
    

    此测试未正确编写。 -n 测试需要一个参数,并告诉您该参数是否为空字符串。 "$@" 将扩展为一个词、多个词,甚至根本没有词。所以写test -n "$@"是不合适的。

    如果您想检查是否传递了参数,请使用以下之一:

    test $# -gt 0
    [[ $# -gt 0 ]]
    (($# > 0))
    (($#))
    

    如果您想检查参数是否已传递并且是否为非空,请改用"$*"$* 始终扩展为单个字符串,因此它与 test -n 兼容。

    test -n "$*"
    [[ -n $* ]]
    

    test -n的困惑案例

    $ test -n; echo $?
    0
    

    那么为什么这个测试会通过呢?好吧,如果您不将参数传递给-n,那么bash 不会将-n 视为运算符。相反,它将测试理解为

    test STRING
    

    如果您编写 test STRING 时不使用运算符,则它隐式等效于 test -n STRINGtest STRING 是测试STRING 是否为非空的简写方式。

    换句话说,test -n 等价于

    test -n '-n'
    

    并且这个测试通过了,因为'-n' 是一个非空字符串。

    “$@”太棒了

    顺便说一句,"$@" 的行为方式实际上是一件很棒的事情。 "$@" 是访问完整参数列表的最佳方式,同时正确处理空格并避免分词和通配问题。如有疑问,请使用"$@"

    如果您想将"$@" 存储在变量中,请使用数组。

    args=("$@")
    
    for arg in "${args[@]}"; do
        echo "$arg"
    done
    

    这将保留"$@"多词性

    【讨论】:

    • Re 'test -n 的令人困惑的情况':wiki.bash-hackers.org/commands/… 帮助我理解了这一点:如果您将该链接中描述的规则集与 "$@" 的概念结合起来,则“扩展为 或多个 引用的参数'。因此,该链接中描述的规则集也有助于解释另一个答案中列出的第四次测试运行中显示的错误,即当test -n "$@" 被提供多个 参数($# > 1)时。
    • 重新“混淆test -n”的案例:不仅仅是test -n;让我难过并开始这一切的是test -z 未能达到我的(不知情的)期望。
    • 我正要说没有参数的test -n 应该是语法错误,但出于历史原因需要它。 test 的别名是 [,通常的语法是 if [ $foo ]; then ...,如果 $foo 包含 -n,则需要计算为真。请注意,在任何输入不受信任的脚本中,这种用法都是非常不安全的。
    【解决方案2】:

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

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

    1。总谷歌赋失败

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

    由于这次崩溃,我去查找了这个 $@ 的官方名称(行话)可能是什么(我上一次阅读 bash 手册可能是在 2 年前;我一直在 涉足 em> 从那时起,这是我因懒惰而受到惩罚的时刻,因为我没有学习我编写(少量)代码的语言。)

    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 表达式求值规则集

    从上面的链接中复制非常重要的部分(用最少的编辑使其与这个问题相匹配)——这就是让我的大脑 click 产生预感,然后完全理解的原因:

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

    参数规则数

    test 内置函数,尤其是隐藏在它的 [ 名称下,可能看起来很简单,但实际上 有时会造成很多麻烦。困难之一是test 的行为不仅取决于其参数,还取决于其参数的数量

    这是从手册中获取的规则(注意:这是针对命令test,对于[,参数的数量是在没有最终]的情况下计算的,例如[ ]遵循“零参数" 规则):

    • 0 个参数

      表达式是false

    • 1 个参数

      表达式为true 当且仅当参数为非空

    • 2 个参数

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

      如果第一个参数是上面在语法规则下列出的一元条件运算符之一(例如-n-z),如果一元测试是@,则表达式是true 987654364@.

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

    • 3 个参数

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

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

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

    • 4 个参数

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

    • 5 个或更多参数

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

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

    (注意:下一部分是转述原文以匹配这个问题!)

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

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

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

    这些规则还解释了为什么-a-o 可以有多种含义。

    4。再试一次...

    http://www.tldp.org/LDP/abs/abs-guide.pdf 中的描述和 http://wiki.bash-hackers.org/commands/classictest#number_of_arguments_rules 中描述的 test 的非明显行为位驱使我使用问题中指定的测试脚本运行第四次测试:

    $ ./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?
    

    现在有提示了!

    5。回答“为什么?”问题

    现在我知道了。

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

    “不知何故”是由于 bash 必须在内部做些什么来保证 "$@" 中的参数数量总是等于循环语句中的轮数,例如 for f in "$@" ; do ... done总是匹配参数的数量$#

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

    一旦您意识到这一点,http://www.tldp.org/LDP/abs/abs-guide.pdf 中对$@$* 的描述就很明显了。

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

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

    [编辑]回顾说明:

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

    “不会帮助我”的短语在那里,所以您可能会理解我的大脑需要一些不同的东西来解决我的困惑和对我的机器失去信任。它是在说“我需要这个而不是其中一个”,因此表明我的内部论点是否我的这个问题是重复的。

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

    他们在这里:

    【讨论】:

    • tldp 上的一些 bash 信息相当陈旧,有一些有问题的编码技术,或者完全是错误的。如需了解最新资源,请查看BashGuide 和随附的 BashFAQ。
    • @PM2Ring:感谢 BashGuide 的参考!我检查了他们对$@ 的看法,他们列出了 github 以供编辑提交;我会看看我们是否可以改进有关 $@ 的详细信息的文档,如果 arglist 为空 ($# = 0),它将扩展到 nil
    猜你喜欢
    • 1970-01-01
    • 2023-03-09
    • 1970-01-01
    • 2016-10-12
    • 2010-10-14
    • 2016-03-03
    • 1970-01-01
    • 2019-10-03
    • 2016-10-25
    相关资源
    最近更新 更多