Answers:
在所有列表上下文中,您始终需要在变量周围加上引号,即在变量可能会扩展为多个值的任何地方,除非您确实希望不加引号的3种副作用。
列表上下文包括简单命令的参数,例如[
或echo
,,for i in <here>
分配给数组...在其他上下文中也需要引用变量。最好是总是引用变量,除非您有很好的理由不这样做。
想想没有引号的(在列表上下文)的拆分+水珠运营商。
好像echo $test
是echo 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
输出同样的事情,不管它是通过一个空的参数或者根本没有参数。
有关命令和构造的更多详细信息,另请参见对类似问题的答案。[
[[...]]
@ 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
,$?
将保留已传递的参数数量(并将打印该数量)。
这是关于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
,因为不辜负其名称,所以扩展为两个参数(foo
和bar
)$UNDEFINED
。将此与进行比较foo "$UNDEFINED" bar
,它会扩展为三个参数(foo
,一个空字符串和`bar)。引号强制外壳将它们解释为参数,无论它们之间是否存在任何东西。
没有引号的情况下$test
可能会扩展到一个以上的单词,因此为了不破坏语法,需要对其加引号,因为[
命令内的每个开关都期望一个引号所做的自变量(使任何内容$test
扩展为一个自变量)
您不需要用引号来扩展变量echo
的原因是因为它不需要一个参数。它只会打印您告诉的内容。因此,即使$test
扩展到100个字,echo仍会打印出来。
看看Bash陷阱
echo
呢?
echo
。是什么让您另想呢?
echo $test
并且它可以工作(它输出$ test的值)
如果未引用空参数,则将其删除:
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"
echo
,而是外壳。您会看到与相同的行为ls
。touch '*'
如果您喜欢冒险,请尝试一些时间。:)
echo
,则会删除多余的空格和换行符。