【问题标题】:bash quotes in variable treated different when expanded to command [duplicate]扩展为命令时变量中的bash引号处理不同[重复]
【发布时间】:2013-11-30 23:55:04
【问题描述】:

通过例子解释问题...

表明当变量展开时,--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.

现在由于某种原因,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.

谁能解释一下为什么引号的行为不符合预期。我发现搜索这个问题很困难,因为网络上有很多其他的引用讨论。如果没有例子,我什至不知道如何解释这个问题。

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

感谢阅读。

【问题讨论】:

    标签: bash quoting variable-expansion


    【解决方案1】:

    要记住的重要一点是,引号只会被删除一次,当命令行最初被解析时。作为参数替换 ($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. 这不适用于eval 或类似bash -c 的命令,它们在命令行处理后再次评估其参数。但那是因为命令行处理发生了两次。

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

    【讨论】:

    • 嗨@rici,感谢您的快速和最有帮助的回答。我对这两种方法(eval 和数组)都进行了一些尝试,但到目前为止还无法确定我更喜欢哪种方法。两者都成功了,所以我很高兴,更因为我现在终于真正理解了这种行为。荣誉
    • @PaulvanLeeuwen:尽可能避免使用eval。由于它应用了另一个完整的命令行处理过程,因此它可以完成比您想要的更多的解析。例如,假设 $1 是Joe's movie.mkv——它将撇号作为引号,找不到匹配的右引号,并出错。或者假设 $1 是 mistake $(rm *).mkv - 它实际上会执行命令 rm * 作为评估命令字符串的一部分。数组是一种更好的方法来做到这一点。顺便说一句,请阅读BashFAQ #050#048
    猜你喜欢
    • 2013-09-29
    • 2021-04-25
    • 2016-07-20
    • 2020-07-06
    • 2014-01-28
    • 2012-01-25
    相关资源
    最近更新 更多