变量处理的bash引号在扩展为命令时处理不同

时间:2013-11-17 23:18:41

标签: bash quoting variable-expansion

通过例子解释问题......

证明扩展变量后转换后的单引号(我没想到这个):

prompt@ubuntu:/my/scripts$ cat test1.sh
#!/bin/bash
actions="--tags all:"
actions+=" --chapters ''"
mkvpropedit "$1" $actions

prompt@ubuntu:/my/scripts$ ./test1.sh some.mkv
Error: Could not open '''' for reading.

现在由于某种原因,mkvpropedit接收双引号作为文件名的一部分(我也没想到):

prompt@ubuntu:/my/scripts$ cat test1x.sh
#!/bin/bash
command="mkvpropedit \"$1\""
command+=" --tags all:"
command+=" --chapters ''"
echo "$command"
$command

prompt@ubuntu:/my/scripts$ ./test1x.sh some.mkv
mkvpropedit "some.mkv" --tags all: --chapters ''
Error: Could not open '''' for reading.

上面的echo'd命令似乎是正确的。将相同的文本放在另一个脚本中会得到预期的结果:

prompt@ubuntu:/my/scripts$ cat test2.sh
#!/bin/bash
mkvpropedit "$1" --tags all: --chapters ''

prompt@ubuntu:/my/scripts$ ./test2.sh some.mkv
The file is being analyzed.
The changes are written to the file.
Done.

有人可以解释为什么报价不符合预期。我发现在这个问题上搜索很困难,因为网上有很多其他的引用讨论。我甚至不知道如何在没有例子的情况下解释这个问题。

恐怕有一天,参数中的文件名包含一些破坏一切的字符,因此可能过多引用。我不明白为什么直接在脚本中输入或通过变量提供时相同的命令执行方式不同。请赐教。

感谢阅读。

1 个答案:

答案 0 :(得分:6)

要记住的重要一点是,引号只会在最初解析命令行时删除。由于参数替换($foo)或命令替换($(cmd args))而插入命令行的引用不被视为特殊字符。 [注1]

这似乎与空格和glob元字符不同。在参数/命令替换之后发生字拆分和路径名扩展(除非替换发生在引号内)。 [注2]

结果是创建一个bash变量$args几乎是不可能的

cmd $args

如果$args包含引号,则不会删除它们。 $args中的单词由空格序列分隔,而不是单个空格字符。

唯一的方法是将$IFS设置为包含一些非空白字符;然后,可以在$args内将该字符用作单字符分隔符。但是,无法在值中引用字符,因此一旦这样做,您选择的字符除了作为分隔符之外不能使用。这通常不太令人满意。

但是有一个解决方案:bash数组。

如果将$args放入数组变量中,则可以使用重复引用语法对其进行扩展:

cmd "${args[@]}"

每个$args元素只生成一个单词,并禁止对这些单词进行单词拆分和路径名扩展,因此它们最终成为文字。

所以,例如:

actions=(--tags all:)
actions+=(--chapters '')
mkvpropedit "$1" "${actions[@]}"

可能会做你想要的。那会:

args=("$1")
args+=(--tags)
args+=(all:)
args+=(--chapters)
args+=('')
mkvpropedit "${args[@]}"

等等

command=(mkvpropedit "$1" --tags all: --chapters '')
"${command[@]}"

我希望这是半透明的。

man bash(或online version)包含一个关于bash如何组装命令的详细说明,从“扩展”部分开始。值得一读的是完整的解释。


注意:

  1. 这不适用于evalbash -c之类的命令,它们会在命令行处理后再次评估其参数。但那是因为命令行处理发生了两次。

  2. 单词拆分与“将命令划分为单词”不同,这在解析命令时会发生。首先,单词拆分使用$IFS的值作为分隔符,而命令行解析使用空格。但这些都不是在引号内完成的,所以它们在这方面是相似的。在任何情况下,在参数替换之前和之后,单词都会以这种或那种方式分开。