简体   繁体   English

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

[英]bash quotes in variable treated different when expanded to command

Explaining the question through examples... 通过例子解释问题......

Demonstrates that the single-quotes after --chapters is gets escaped when the variable is expanded (I didn't expect this): 证明 - 变量扩展后,--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.

And now for some reason mkvpropedit receives the double quotes as part of the filename (I didn't expect this either): 现在由于某种原因,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.

The above echo'd command seems to be correct. 上面的echo'd命令似乎是正确的。 Putting the same text in another script gives the expected result: 将相同的文本放在另一个脚本中会得到预期的结果:

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.

Could anyone please explain why the quotes are not behaving as expected. 任何人都可以解释为什么报价不符合预期。 I found searching on this issue difficult as there are so many other quoting discussions on the web. 我发现在这个问题上搜索很困难,因为网上有很多其他的引用讨论。 I wouldn't even know how to explain the question without examples. 我甚至不知道如何在没有例子的情况下解释这个问题。

I am afraid that some day the file name in the argument contains some character that breaks everything, hence the maybe excessive quoting. 我担心有一天,参数中的文件名包含一些破坏一切的字符,因此可能过多引用。 I do not understand why the same command executes differently when typed directly in the script or when provided via a variable. 我不明白为什么直接在脚本中输入或通过变量提供时相同的命令执行方式不同。 Please enlighten me. 请赐教。

Thanks for reading. 谢谢阅读。

The important thing to keep in mind is that quotes are only removed once , when the command line is originally parsed. 需要记住的重要一点是,在最初解析命令行时, 引号只会被删除一次 A quote which is inserted into the command line as a result of parameter substitution ( $foo ) or command substitution ( $(cmd args) ) is not treated as a special character. 由于参数替换( $foo )或命令替换( $(cmd args) )而插入命令行的引用不被视为特殊字符。 [Note 1] [注1]

That seems different from whitespace and glob metacharacters. 这似乎与空格和glob元字符不同。 Word splitting and pathname expansion happen after parameter/command substitution (unless the substitution occurs inside quotes). 在参数/命令替换之后发生字拆分和路径名扩展(除非替换发生在引号内)。 [Note 2] [笔记2]

The consequence is that it is almost impossible to create a bash variable $args such that 结果是,创建一个bash变量$args几乎是不可能的

cmd $args

If $args contains quotes, they are not removed. 如果$args包含引号,则不会删除它们。 Words inside $args are delimited by sequences of whitespace, not single whitespace characters. $args中的单词由空格序列分隔,而不是单个空白字符。

The only way to do it is to set $IFS to include some non-whitespace character; 唯一的方法是将$IFS设置$IFS包含一些非空白字符; that character can then be used inside $args as a single-character delimiter. 然后,该字符可以在$args作为单字符分隔符使用。 However, there is no way to quote a character inside a value , so once you do that, the character you chose cannot be used other than as a delimiter. 但是, 没有办法在值中引用字符 ,因此一旦这样做,您选择的字符除了作为分隔符之外不能使用。 This is not usually very satisfactory. 这通常不太令人满意。

There is a solution, though: bash arrays. 但是有一个解决方案:bash数组。

If you make $args into an array variable, then you can expand it with the repeated-quote syntax: 如果将$args转换为数组变量,则可以使用重复引用语法对其进行扩展:

cmd "${args[@]}"

which produces exactly one word per element of $args , and suppresses word-splitting and pathname expansion on those words, so they end up as literals. 它为$args每个元素生成一个单词,并禁止在这些单词上进行单词拆分和路径名扩展,因此它们最终成为文字。

So, for example: 所以,例如:

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

will probably do what you want. 可能会做你想要的。 So would: 那会:

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

and so would 等等

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

I hope that's semi-clear. 我希望这是半透明的。

man bash (or the online version ) contains a blow-by-blow account of how bash assembles commands, starting at the section "EXPANSION". man bash (或在线版本 )包含一个关于bash汇编命令的详细说明,从“EXPANSION”部分开始。 It's worth reading for a full explanation. 值得阅读以获得完整的解释。


Notes: 笔记:

  1. This doesn't apply to eval or commands like bash -c which evaluate their argument again after command line processing. 这不适用于evalbash -c类的命令,它们在命令行处理后再次评估它们的参数。 But that's because command-line processing happens twice. 但那是因为命令行处理发生了两次。

  2. Word splitting is not the same as "dividing the command into words", which happens when the command is parsed. 单词拆分与“将命令划分为单词”不同,这在解析命令时会发生。 For one thing, word-splitting uses as separator characters the value of $IFS , whereas command-line parsing uses whitespace. 首先,单词拆分使用$IFS的值作为分隔符,而命令行解析使用空格。 But neither of these are done inside quotes, so they are similar in that respect. 但这些都不是在引号内完成的,所以它们在这方面是相似的。 In any case, words are split in one way or another both before and after parameter substitution. 在任何情况下,在参数替换之前和之后,单词都以这种或那种方式分开。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM