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

Cha*_*esB 29 shell quoting

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

if [ -n "$test" ]; then echo '$test ok'; else echo '$test null'; fi
Run Code Online (Sandbox Code Playgroud)

将按预期工作,而

if [ -n $test ]; then echo '$test ok'; else echo '$test null'; fi
Run Code Online (Sandbox Code Playgroud)

总是会说$test ok即使$test为空。

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

Sté*_*las 39

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

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

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

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

shell 的行为让大多数人感到困惑,因为在大多数其他语言中,您在固定字符串周围加上引号,例如puts("foo"),而不是在变量周围(例如puts(var))会很麻烦,你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", ""and "]"(不带引号),这正是我们想要的。

不管它是echo[没有区别,它只是echo输出相同的东西,无论它是传递一个空参数还是根本没有参数。

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


use*_*ser 7

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

试试下面的命令,你就会明白我的意思。

export testvar="123    456"
echo $testvar
echo "$testvar"
Run Code Online (Sandbox Code Playgroud)

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

echo 123    456
Run Code Online (Sandbox Code Playgroud)

并且多个空间被折叠为一个:

echo 123 456
Run Code Online (Sandbox Code Playgroud)

使用引号,空格被保留。

这是因为当你引用一个参数(该参数是否被传递给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
Run Code Online (Sandbox Code Playgroud)

进而...

./paramtest 123 456
./paramtest "123 456"
./paramtest 123   456
./paramtest "123   456"
Run Code Online (Sandbox Code Playgroud)

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