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

Pau*_*wen 4 bash quoting variable-expansion

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

证明 - 变量扩展后,--chapters之后的单引号会被转义(我没想到):

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.
Run Code Online (Sandbox Code Playgroud)

现在由于某种原因,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.
Run Code Online (Sandbox Code Playgroud)

上面的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.
Run Code Online (Sandbox Code Playgroud)

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

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

谢谢阅读.

ric*_*ici 6

需要记住的重要一点是,在最初解析命令行时,引号只会被删除一次.作为参数substitute($foo)或命令substitution($(cmd args))的结果插入命令行的引用不被视为特殊字符.[注1]

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

其结果是,它几乎是不可能创造一个bash变量$args,使得

cmd $args
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

cmd "${args[@]}"
Run Code Online (Sandbox Code Playgroud)

它每个元素只生成一个单词$args,并抑制这些单词上的单词拆分和路径名扩展,因此它们最终成为文字.

所以,例如:

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

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

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

等等

command=(mkvpropedit "$1" --tags all: --chapters '')
"${command[@]}"
Run Code Online (Sandbox Code Playgroud)

我希望这是半透明的.

man bash(或在线版本)包含一个关于bash如何组装命令的详细说明,从"扩展"部分开始.值得阅读以获得完整的解释.


笔记:

  1. 这不适用于命令行处理后再次评估其参数的eval命令或命令bash -c.但那是因为命令行处理发生了两次.

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