@Kusalananda 已经解释了基本问题及其解决方法,@ glenn jackmann链接到的Bash FAQ条目也提供了许多有用的信息。根据这些资源,这是我问题中正在发生的事情的详细说明。
我们将使用一个小的脚本,将其每个参数打印在单独的一行上以说明事物(argtest.bash
):
#!/bin/bash
for var in "$@"
do
echo "$var"
done
“手动”传递选项:
$ ./argtest.bash -rnv --exclude='.*'
-rnv
--exclude=.*
不出所料,部分-rnv
和--exclude='.*'
被拆分为两个参数,因为它们之间用未加引号的空格隔开(这称为单词拆分)。
还要注意,周围的引号.*
已被删除:单引号告诉shell传递其内容而无需特殊解释,但是引号本身并未传递给command。
如果现在将选项作为字符串存储在变量中(而不是使用数组),则不会删除引号:
$ OPTS="--exclude='.*'"
$ ./argtest.bash $OPTS
--exclude='.*'
这是由于两个原因:定义时使用的双引号会$OPTS
阻止对单引号的特殊处理,因此后者是值的一部分:
$ echo $OPTS
--exclude='.*'
现在,当我们$OPTS
用作命令的参数时,引号会在参数扩展之前进行处理,因此引号的$OPTS
发生“太晚了”。
这意味着(在我的原始问题中)rsync
使用了排除模式'.*'
(带引号!)而不是模式.*
-它排除了名称以单引号开头,后跟一个点并以单引号结尾的文件。显然,这不是我们想要的。
解决方法是在定义时省略双引号$OPTS
:
$ OPTS2=--exclude='.*'
$ ./argtest.bash $OPTS2
--exclude=.*
但是,由于在更复杂的情况下存在细微的差异,因此始终引用变量分配是一个好习惯。
正如@Kusalananda指出的那样,不引用.*
也可以。我已经添加了引号来防止模式扩展,但是在这种特殊情况下,这并不是绝对必要的:
$ ./argtest.bash --exclude=.*
--exclude=.*
事实证明,Bash 确实执行了模式扩展,但是该模式--exclude=.*
与任何文件都不匹配,因此该模式被传递给命令。比较:
$ touch some_file
$ ./argtest.bash some_*
some_file
$ ./argtest.bash does_not_exit_*
does_not_exit_*
但是,不引用模式是很危险的,因为如果(由于某种原因)存在文件匹配,--exclude=.*
则模式将被扩展:
$ touch -- --exclude=.special-filenames-happen
$ ./argtest.bash --exclude=.*
--exclude=.special-filenames-happen
最后,让我们看看为什么使用数组可以防止我的引用问题(除了使用数组存储命令参数的其他优点)。
定义数组时,将按预期进行分词和引用处理:
$ ARRAY_OPTS=( -rnv --exclude='.*' )
$ echo length of the array: "${#ARRAY_OPTS[@]}"
length of the array: 2
$ echo first element: "${ARRAY_OPTS[0]}"
first element: -rnv
$ echo second element: "${ARRAY_OPTS[1]}"
second element: --exclude=.*
将选项传递给命令时,我们使用语法"${ARRAY[@]}"
,它将数组的每个元素扩展为一个单独的单词:
$ ./argtest.bash "${ARRAY_OPTS[@]}"
-rnv
--exclude=.*