我读过您需要双引号来扩展变量,例如
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 $test
是echo 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
使用引号时,空格将被保留。
发生这种情况是因为当您引用参数时(无论该参数传递给echo
,test
还是其他命令),该参数的值将作为一命令的值。如果不引用它,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
,它扩展为两个参数(foo
和bar
),因为$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"