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

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

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

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即使为 null 也总是会说$test

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

答案1

您始终需要在所有变量周围加上引号列表上下文,也就是说,变量可以在任何地方扩展为多个值,除非您确实希望将变量留在未加引号的状态中而产生 3 个副作用。

列表上下文包括简单命令的参数,例如[or echo、 the for i in <here>、对数组的赋值...还有其他上下文,变量也需要引用。最好是始终引用变量,除非您有充分的理由不这样做。

考虑到没有引号(在列表上下文中)作为分割+全局操作员。

仿佛echo $testecho glob(split("$test"))

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

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

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

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

无论是否echo传递都[没有区别,只是echo无论传递空参数还是根本不传递参数,都会输出相同的内容。

也可以看看这个类似问题的答案有关[命令和[[...]]构造的更多详细信息。

答案2

@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$?将保存传递的参数数量(并且该数字将被打印)。

答案3

这就是 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 [ ... ]进行比较时,您没有使用 shell 中内置的原语。您实际上是在指示 shell 运行一个名为 的程序,[该程序是该程序的一个非常小的超集,test(1)需要其最后一个参数为]0如果测试条件为真或1不为真,两个程序都会退出。

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

之间存在语义差异foo $UNDEFINED bar,它扩展为两个参数(foobar),因为$UNDEFINED名副其实。与 进行比较foo "$UNDEFINED" bar,它扩展到参数(foo、空字符串和 `bar)。引号强制 shell 将它们解释为参数,无论它们之间是否有任何东西。

答案4

如果不加引号,空参数将被删除:

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,后面没有任何内容。不管怎样。

在某些情况下,引用确实会对 echo 产生影响:

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

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

相关内容