为什么我需要为if引用变量,而不必为echo引用呢?


26

我读到您需要双引号来扩展变量,例如

if [ -n "$test" ]; then echo '$test ok'; else echo '$test null'; fi

将按预期工作,而

if [ -n $test ]; then echo '$test ok'; else echo '$test null'; fi

$test ok即使$test为null 也会一直说。

但是为什么我们不需要引号echo $test呢?


2
如果不引用变量作为arg的arg echo,则会删除多余的空格和换行符。
jordanm

Answers:


36

在所有列表上下文中,您始终需要在变量周围加上引号,即在变量可能会扩展为多个值的任何地方,除非您确实希望不加引号的3种副作用。

列表上下文包括简单命令的参数,例如[echo,,for i in <here>分配给数组...在其他上下文中也需要引用变量。最好是总是引用变量,除非您有很好的理由不这样做。

想想没有引号的(在列表上下文)的拆分+水珠运营商。

好像echo $testecho glob(split("$test"))

shell的行为对大多数人来说是令人困惑的,因为在大多数其他语言中,您将引号放在固定字符串puts("foo")(例如puts(var))周围,​​而不是在变量(例如)周围,而在外壳中则是相反:所有内容都是字符串在外壳中,因此将引号放在所有内容周围麻烦,你echo test,你不需要"echo" "test"。在shell中,引号用于其他用途:防止某些字符的特殊含义和/或影响某些扩展的行为。

[ -n $test ]或中echo $test,shell将拆分$test(默认情况下为空白),然后执行文件名生成(将所有的*','...模式扩展到匹配文件的列表中),然后将该参数列表传递给[or echo命令。 。

再次将其视为"[" "-n" glob(split("$test")) "]"。如果$test为空或仅包含空格(spc,tab,nl),则split + glob运算符将返回一个空列表,因此[ -n $test ]将为"[" "-n" "]",这是检查“ -n”是否为空字符串的测试。但是想象一下如果$test是“ *”或“ = foo” 会发生什么...

[ -n "$test" ][传递四个参数"[""-n""""]"(不带引号),这是我们想要的。

无论是echo还是[没有什么区别,它只是echo输出同样的事情,不管它是通过一个空的参数或者根本没有参数。

有关命令和构造的更多详细信息,另请参见对类似问题的答案[[[...]]


7

@ h3rrmiller的答案很好地解释了为什么您需要用引号引起来if(或更确切地说是[/ test),但是我实际上认为您的问题是不正确的。

尝试以下命令,您将明白我的意思。

export testvar="123    456"
echo $testvar
echo "$testvar"

没有引号,变量替换导致第二个命令扩展为:

echo 123    456

并将多个空格折叠为一个:

echo 123 456

用引号将保留空格。

这是因为当你引用一个参数(该参数是否被传递给echotest或一些其它命令),该参数的值作为发送一个值的命令。如果您不引用它,则Shell会正常地寻找空格来确定每个参数的开始和结束位置。

以下(非常简单)的C程序也可以说明这一点。在命令行上尝试以下操作(您可能希望在一个空目录中执行此操作,以免冒险覆盖某些内容)。

cat <<EOF >paramtest.c
#include <stdio.h>
int main(int argc, char **argv) {
  int nparams = argc-1; /* because 1 parameter means only the executable's name */
  printf("%d parameters received\n", nparams);
  return nparams;
}
EOF
cc -o paramtest paramtest.c

然后...

./paramtest 123 456
./paramtest "123 456"
./paramtest 123   456
./paramtest "123   456"

运行后paramtest$?将保留已传递的参数数量(并将打印该数量)。


2

这是关于Shell 执行程序之前如何解释行的全部内容。

如果该行显示为echo I am $USER,则Shell会将其扩展为echo I am blrfl并且echo不知道文本的起源是文字扩展还是变量扩展。同样,如果一行读取echo I am $UNDEFINED,则shell将扩展$UNDEFINED为空,并且echo的参数将为I am,到此为止。由于echo没有参数就可以正常工作,echo $UNDEFINED因此完全有效。

你有问题,if是不是真的有if,因为if只是运行任何程序和参数遵循它并执行then,如果退出程序部分0(或else一部分,如果有一个程序退出非0):

if /bin/true ; then echo True dat. ; fi
if fgrep -q blrfl /etc/passwd ; then echo Blrfl has an account. ; fi

当您使用if [ ... ]做一个比较,你不使用内置在外壳元。您实际上是在指示外壳程序运行一个名为的程序[,该程序是该程序的一个很小的超集test(1),需要它的最后一个参数be ]0如果测试条件成立,1则两个程序都退出,如果不是,则退出。

未定义变量时某些测试test失败的原因是因为看不到您正在使用变量。Ergo [ $UNDEFINED -eq 2 ]中断了,因为到外壳处理完它时,所有test对参数的看到都是-eq 2 ],这不是有效的测试。如果您使用定义的内容(例如)来执行此操作[ $DEFINED -ne 0 ],那将是有效的,因为shell会将其扩展为有效的测试(例如0 -ne 0)。

之间的语义差异foo $UNDEFINED bar,因为不辜负其名称,所以扩展为两个参数(foobar$UNDEFINED。将此与进行比较foo "$UNDEFINED" bar,它会扩展为三个参数(foo,一个空字符串和`bar)。引号强制外壳将它们解释为参数,无论它们之间是否存在任何东西。


0

没有引号的情况下$test可能会扩展到一个以上的单词,因此为了不破坏语法,需要对其加引号,因为[命令内的每个开关都期望一个引号所做的自变量(使任何内容$test扩展为一个自变量)

您不需要用引号来扩展变量echo的原因是因为它不需要一个参数。它只会打印您告诉的内容。因此,即使$test扩展到100个字,echo仍会打印出来。

看看Bash陷阱


是的,但是为什么我们不需要它echo呢?
CharlesB

@CharlesB您确实需要使用引号echo。是什么让您另想呢?
吉尔斯(Gilles)'所以

我不需要它们,我可以echo $test并且它可以工作(它输出$ test的值)
CharlesB 2013年

1
@CharlesB仅在$ test的值在任何地方都不包含多个空格的情况下才输出。请尝试用我的答案中的程序来说明原因。
CVn

0

如果未引用空参数,则将其删除:

start cmd:> strace -e trace=execve echo foo $bar baz
execve("/usr/bin/echo", ["echo", "foo", "baz"], [/* 100 vars */]) = 0

start cmd:> strace -e trace=execve echo foo "$bar" baz
execve("/usr/bin/echo", ["echo", "foo", "", "baz"], [/* 100 vars */]) = 0

被调用的命令在shell命令行上看不到空参数。似乎[被定义为-n返回0,之后没有任何内容。怎么了

在某些情况下,报价的确也会对回声有所影响:

var='*'
echo $var
echo "$var"

var="foo        bar"
echo $var
echo "$var"

2
不是echo,而是外壳。您会看到与相同的行为lstouch '*'如果您喜欢冒险,请尝试一些时间。:)
CVn

这只是措辞,因为与'if [...]`情况没有区别。[不是特殊的shell命令。这与[[(在bash中)不需要引号的地方不同。
Hauke Laging
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.