为什么bash"测试-n"命令为$ @(美元at)位置参数给出错误的结果,而"!测试-z"作品?

时间:2014-12-12 21:14:23

标签: 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

前两个部分('空'和'空格')在那里验证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”结果。

2 个答案:

答案 0 :(得分:7)

" $ @"和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

这将保留"$@"多措辞

答案 1 :(得分:4)

在将这种令人困惑的观察行为转换为可能适合StackOVerflow的测试脚本和问题的同时,发生了一些事情,这导致了对此问题的回答,为什么'问题

  

TL; DR答案见下文第5节;我描述了整个发现过程,因为我(重新)学习了很多东西,我认为其他人会遵循相同的,即使不是非常相似的发现路径。 Google不是总是你的朋友!

1。总谷歌失败

首先,' google'在您搜索bash test $@ odd result或类似查询时,它没有提供任何,因为它会删除所有重要的$@。将$@改为dollar at并没有吐出任何有用的东西,这让我很担心。我甚至开始怀疑我正在运行的bash(Windows上的mSysGit bash)。

  

感谢这次失败,我去看了这个$@的正式名称(行话)可能是什么(可能是20年前,我上次读过一本bash手册;我'从那以后,我一直在涉猎,这是我因为没有研究我编写的语言(小部分代码)而在懒惰中受到惩罚的那一刻。)

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

另一轮搜索导致http://www.tldp.org/LDP/abs/abs-guide.pdf告诉我$@(和$* - 哦,对,忘记了你的一切!)是'位置参数&# 39;

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的行为不仅取决于其参数,还取决于其参数的数量

     

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

     
      
  • 0参数

         

    表达式为[ ]

  •   
  • 1参数

         

    当且仅当参数不为空时,表达式为false

  •   
  • 2个参数

         

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

         

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

         

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

  •   
  • 3个参数

         

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

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

         

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

  •   
  • 4个参数

         

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

  •   
  • 5个或更多参数

         

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

  •   
     

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

(注意:下一部分是释义原文以匹配此问题!)

!
     

此代码打印"参数列表不为空",即使 function test { if [ -n "$@" ] ; then echo "argument list is not empty"; fi } test 应为-n something,如果false为空字符串something - 为什么?

     

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

     

这些规则也解释了为什么"$@"-a可以有多种含义。

4。再试一次......

http://www.tldp.org/LDP/abs/abs-guide.pdf中的描述以及http://wiki.bash-hackers.org/commands/classictest#number_of_arguments_rules中描述的-o的非显而易见的行为促使我使用问题中指定的测试脚本运行第四个测试:< / p>

test

现在有了提示!

5。回答&#39;为什么?&#39;问题

现在我知道了。

$ ./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? ,即使被引用为$@,&#39;以某种方式&#39;转换为"$@"中可用的参数的确切数量,对于空参数列表($@),表示$# = 0完全代表 nothing :{{1因此,扩展&#39;至"$@", 遵循http://wiki.bash-hackers.org/commands/classictest#number_of_arguments_rules中描述的规则,几乎与此处描述的示例相同,并遵循相同的&#1; 1参数规则&#39;:test -n "$@"中的test -n因此不是-n而是一个参数&#39;,而不是null,因此test -n会产生问题中第三次测试运行中显示的意外错误测试结果。

以某种方式&#39;是因为bash必须在内部做什么来保证test option: return true if string is non-empty 中的参数总数等于循环语句中的轮次数,例如test -n "$@" --> test -n --> test "-n" --> TRUE总是< / em>匹配参数数量"$@"

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

一旦您意识到这一点,"$@"""$@的说明就很明显了。

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

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

  

[编辑]回顾注意:

     

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

     

&#34;不会帮助我&#34;短语就在那里,所以你可能会理解我的大脑需要不同的东西来解决我的机器混乱和失去信任。它说&#34;我需要这个而不是其中一个&#34;因此,我的内部论证表明我的这个问题是否重复。

     

我自己的结论:一个问题技术上几乎相同(除了$*中的!),而它的答案非常接近我所需要的,但是,至少对我来说,这不是重复的回答因为它更加关注{em>为什么 ! test -z的详细信息。和test互动。这个答案所要求的确切问题背景是合适的,至少对我来说,回想起来,而不是一个重复的问题。虽然它接近成为一个。

他们是: