Bash 为什么可以';我不能双引号引用一个包含多个参数的变量吗?
我正在编写一个bash脚本,它使用Bash 为什么可以';我不能双引号引用一个包含多个参数的变量吗?,bash,shell,whitespace,expansion,quoting,Bash,Shell,Whitespace,Expansion,Quoting,我正在编写一个bash脚本,它使用rsync来同步目录。根据报告: 始终引用包含变量、命令替换、空格或shell元字符的字符串,除非需要仔细的无引号扩展 使用“$@”,除非您有特定的理由使用$* 我编写了以下测试用例场景: #!/bin/bash __test1(){ echo stdbuf -i0 -o0 -e0 $@ stdbuf -i0 -o0 -e0 $@ } __test2(){ echo stdbuf -i0 -o0 -e0 "$@" stdbuf -i0 -
rsync
来同步目录。根据报告:
- 始终引用包含变量、命令替换、空格或shell元字符的字符串,除非需要仔细的无引号扩展
- 使用
,除非您有特定的理由使用“$@”
$*
#!/bin/bash
__test1(){
echo stdbuf -i0 -o0 -e0 $@
stdbuf -i0 -o0 -e0 $@
}
__test2(){
echo stdbuf -i0 -o0 -e0 "$@"
stdbuf -i0 -o0 -e0 "$@"
}
PARAM+=" --dry-run "
PARAM+=" mirror.leaseweb.net::archlinux/"
PARAM+=" /tmp/test"
echo "test A: ok"
__test1 nice -n 19 rsync $PARAM
echo "test B: ok"
__test2 nice -n 19 rsync $PARAM
echo "test C: ok"
__test1 nice -n 19 rsync "$PARAM"
echo "test D: fails"
__test2 nice -n 19 rsync "$PARAM"
(我需要stdbuf
立即观察我正在运行的较长脚本中的输出)
所以,我的问题是:为什么测试D会失败并显示以下消息
rsync: getaddrinfo: --dry-run mirror.leaseweb.net 873: Name or service not known
每个测试中的echo
看起来都一样。如果我想引用所有变量,为什么在这个特定场景中它会失败?它失败是因为“$PARAM”
扩展为单个字符串,并且不执行分词,尽管它包含命令应解释为多个参数的内容
一种非常有用的技术是使用数组而不是字符串。按如下方式构建阵列:
declare -a PARAM
PARAM+=(--dry-run)
PARAM+=(mirror.leaseweb.net::archlinux/)
PARAM+=(/tmp/test)
然后,使用阵列扩展来执行调用:
__test2 nice -n 19 rsync "${PARAM[@]}"
“${PARAM[@]}”
扩展与“$@”
扩展具有相同的属性:它扩展为一个项目列表(数组/参数列表中的每个项目一个字),不会发生分词,就像每个项目都被引用一样。它失败是因为“$PARAM”
扩展为单个字符串,并且不会执行分词,尽管它包含命令应解释为多个参数的内容
一种非常有用的技术是使用数组而不是字符串。按如下方式构建阵列:
declare -a PARAM
PARAM+=(--dry-run)
PARAM+=(mirror.leaseweb.net::archlinux/)
PARAM+=(/tmp/test)
然后,使用阵列扩展来执行调用:
__test2 nice -n 19 rsync "${PARAM[@]}"
“${PARAM[@]}”
扩展与“$@”
扩展具有相同的属性:它扩展为一个项目列表(数组/参数列表中的每个项目一个单词),不会发生分词,就像每个项目都被引用一样。我同意@Fred-使用数组是最好的。这里有一些解释和一些调试技巧
在运行测试之前,我添加了
echo "$PARAM"
set|grep '^PARAM='
要实际显示原始测试中的PARAM
是什么。**
,它是:
PARAM=' --dry-run mirror.leaseweb.net::archlinux/ /tmp/test'
也就是说,它是包含多个空格分隔的片段的单个字符串
根据经验(除了例外!*
),除非您告诉bash不要拆分单词,否则它将拆分单词。在测试A和C中,\uu test1
中未加引号的$@
为bash提供了拆分$PARAM
的机会。在测试B中,对u test2的调用中未加引号的$PARAM
具有相同的效果。因此,
rsync`将每个空格分隔的项视为测试a-C中的一个单独参数
在测试D中,由于引号的缘故,调用\uu test2
时,传递给\uu test2
的“$PARAM”
不会被拆分。因此,\uu test2
只能在$@
中看到一个参数。然后,在\uu test2
中,引用的“$@”
将该参数保存在一起,因此它不会在空格处拆分。因此,rsync
认为整个PARAM
就是主机名,所以失败了
如果使用Fred的解决方案,则sed | grep'^PARAM='
的输出为
PARAM=([0]="--dry-run" [1]="mirror.leaseweb.net::archlinux/" [2]="/tmp/test")
这是bash对数组的内部表示法:PARAM[0]
是“--dry run”
,等等。您可以单独查看每个单词echo$PARAM
对数组没有多大帮助,因为它只输出第一个字(这里,--dry run
)
编辑
*
正如弗雷德指出的,一个例外是,在作业A=$B
中,B
不会展开。也就是说,A=$B
和A=“$B”
是相同的
**
正如ghoti所指出的,您可以使用声明-p参数来代替设置| grep'^PARAM='
。带有-p
开关的将打印出一行,您可以将该行粘贴回shell以重新创建变量。在这种情况下,该输出为:
declare -a PARAM='([0]="--dry-run" [1]="mirror.leaseweb.net::archlinux/" [2]="/tmp/test")'
这是一个很好的选择。我个人更喜欢set | grep
方法,因为declare-p
为您提供了额外的引用级别,但两者都很好编辑正如@rici所指出的,如果数组中的元素可能包含换行符,请使用declare-p
作为额外引用的一个例子,请考虑<代码>未设置的PARAM;声明-参数;PARAM+=(“Jim的”)
(一个包含一个元素的新数组)。然后你会得到:
set|grep: PARAM=([0]="Jim's")
# just an apostrophe ^
declare -p: declare -a PARAM='([0]="Jim'\''s")'
# a bit uglier, in my opinion ^^^^
我同意@Fred-使用数组是最好的。这里有一些解释和一些调试技巧 在运行测试之前,我添加了
echo "$PARAM"
set|grep '^PARAM='
要实际显示原始测试中的PARAM
是什么。**
,它是:
PARAM=' --dry-run mirror.leaseweb.net::archlinux/ /tmp/test'
也就是说,它是包含多个空格分隔的片段的单个字符串
根据经验(除了例外!*
),除非您告诉bash不要拆分单词,否则它将拆分单词。在测试A和C中,\uu test1
中未加引号的$@
为bash提供了拆分$PARAM
的机会。在测试B中,对u test2的调用中未加引号的$PARAM
具有相同的效果。因此,
rsync`将每个空格分隔的项视为测试a-C中的一个单独参数
在测试D中,由于引号的缘故,调用\uu test2
时,传递给\uu test2
的“$PARAM”
不会被拆分。因此,\uu test2
只能在$@
中看到一个参数。然后,在\uu test2
中,引用的“$@”
将该参数保存在一起,因此它不会在空格处拆分。因此,rsync
认为整个PARAM
就是主机名,所以失败了
如果使用Fred的解决方案,则sed | grep'^PARAM='
的输出为
PARAM=([0]="--dry-run" [1]="mirror.leaseweb.net::archlinux/" [2]="/tmp/test")
这是bash对数组的内部表示法:PARAM[0]
是“--dry run”
,等等。您可以单独查看每个单词<代码>回声$P