在bash中,如何执行变量VERBATIM的内容,就好像它们是命令行一样?

时间:2015-02-21 20:57:33

标签: string bash variables command interpolation

我需要构造一个包含引用参数的复杂命令。碰巧它们是grep的参数,所以我将把它作为我的例子并深入简化命令以足以证明错误。

让我们从一个有效的例子开始:

> COMMAND='/usr/bin/grep _'
> echo $COMMAND
/usr/bin/grep _
> $COMMAND
foo            <- I type this, and grep filters it out. 
foo_           <- I type this, and.....
foo_           <- ... it matches, so grep emits it.

“foo”没有回显,因为它没有下划线,“foo_”有一个,所以它被返回了。让我们来看一下这个问题的演示:

> COMMAND='/usr/bin/grep "_ _"'
> echo -E $COMMAND
/usr/bin/grep "_ _"
> /usr/bin/grep "_ _"  <- The exact same command line
foo                <- fails to match
foo_ _             <- matches, so it gets echoed back
foo_ _
> $COMMAND             <- But that command doesn't work from a variable
grep: _": No such file or directory

换句话说,当通过变量名调用此命令时,bash将下划线之间的空格作为参数分隔符 - 尽管有引号。

通常情况下,我会用反斜杠解决这个问题:

> COMMAND='/usr/bin/grep "_\ _"'
> $COMMAND
grep: trailing backslash (\)

好吧,也许我需要另一层逃避反斜杠:

> COMMAND='/usr/bin/grep "_\\ _"'
12:32 (master) /Users/ronbarry> $COMMAND
grep: _": No such file or directory

现在我们又回到原点 - 命令行仍然在空间被打破。当然,我可以通过一些调试来验证所有这些,它确定反斜杠是幸存的,未转义的,并且使用多个参数调用grep:

> set -x
> $COMMAND
+ /usr/bin/grep '"_\\' '_"'  <- grep is being called with two args

我有一个利用数组的问题的解决方案,但是这种方式打包命令(在我的完整实现中,我会饶恕你)对于大多数读过我的代码的人来说都是不熟悉的。过度简化基于数组的命令的创建:

> declare -a COMMAND=('/usr/bin/grep' '-i' 'a b')
12:44 (master) /Users/ronbarry> ${COMMAND[*]}
foo           <- Same old, same old
fooa B        <- ...
fooa B        <- Matches because of case-insensitive (-i) grep.

最后我们回答了这个问题。为什么bash在将它们解释为命令时会破坏字符串中的引用参数,为什么似乎没有字符串方式让它工作?如果我有一个打包在字符串变量中的命令,它违反了最小惊喜原则,使该字符串的解释与字符串本身不同。如果有人可以指出一些涵盖所有这些内容的文档,并且会让我安心,为什么我不得不求助于使用我所有命令构建数组的无限丑陋机制,我非常感激。< / p>

1 个答案:

答案 0 :(得分:2)

免责声明:在写完以下内容之后,我几乎决定应该关闭这个问题,以鼓励基于意见的回复。这是一种基于意见的回应。自行承担风险。

  

为什么bash在将它们解释为命令

时会破坏字符串中的引用参数

因为它的作用。一个更有趣的问题可能是&#34;为什么bash会破坏字符串?&#34;,唯一可能的答案就是#34;当时这似乎是一个好主意&#34;。< / p>

或者,换句话说:一开始,没有人想过将空格放入文件名中。当你只有几个字母的文件名时,你并没有在空格上浪费任何字母。因此,将单词列表表示为空格分隔的单词列表似乎是合理的,这就是开发shell语言的基础。所以bash的默认行为,就像所有unix-y shell的默认行为一样,是考虑一个带有空格的字符串,它是一个以空格分隔的单词列表。

但是,当然,这会导致各种令人头疼的问题,因为字符串不是结构化数据。有时文件名的名称中确实有空格。并非所有实用程序参数都是文件名。有时你想给一个实用程序一个参数,例如一个句子。没有这种复杂性,贝壳能够避免让你输入引号,不像&#34;真正的&#34;需要引用字符串的编程语言。但是一旦你决定有时字符串中的空格只是另一个字符,你需要有某种引用系统。所以shell的语法添加了几个引用形式,每个形式都有略微不同的语义。最常见的是双引号,它将内容标记为单个单词,但仍允许变量扩展。

shell引用仍然是这样的,就像任何其他语言中的引号一样,只是语法结构。它们不是字符串的一部分,并且字符串中的特定字符用引号标记(或者,等效地,反斜杠)这一事实不会保留为字符串的一部分 - 再次,就像任何其他编程语言一样。字符串不是真正的单词列表;他们只是默认处理。

所有这些都不是很令人满意。 shell编程的本质是你真的想要一个数据结构,这是一个&#34;单词列表&#34; - 或者,更好的是,字符串列表。而且,最终,贝壳开始做到这一点。不幸的是,那时候shell语言中还没有多少句法空间;重要的是新功能不会改变现有shell脚本的行为。据我所知,数组的当前shell语法是由David Korn在1988年(或更早)创建的;最终,bash还使用基本相同的语法实现了数组。

语法中的一个好奇点是,有三种方法可以指定整个数组应该被替换:

  1. ${array[*]}${array[@]}:将所有数组元素连接在一起,用$IFS中的第一个字符分隔,然后将结果视为以空格分隔的单词列表

  2. "${array[*]}":将所有数组元素连接在一起,用$IFS中的第一个字符分隔,然后将结果视为一个单词。

  3. "${array[@]}":每个数组元素都作为单独的单词插入。

  4. 其中,第一个基本没用;第二个偶尔是有用的,第三个 - 也是最难打字的 - 是你几乎总想要的。

    在上面的简短讨论中,我遗漏了对glob字符和文件名扩展以及许多其他shell特性的任何考虑。因此,不要以任何方式将其作为完整的教程。

      

    为什么似乎没有一种字符串方式让它发挥作用?

    您始终可以使用eval。不幸。如果你真的想让bash解释一个字符串,好像它是一个bash程序而不是一个字符串,如果你准备打开你的脚本到各种注入攻击,那么shell会愉快地给你足够的绳索。就个人而言,我绝不会允许使用eval的脚本通过代码审查,因此我不打算在此处扩展其使用范围。但它有记录。

      

    如果我有一个打包在字符串变量中的命令,它违反了最小惊喜原则,使该字符串的解释与字符串本身不同。

    惊喜真的是旁观者的眼睛。可能有很多程序员认为换行符确实占用了两个字节,并且当发现在C中'\n' [0]不是反斜杠时会感到惊讶。但我认为如果是的话,我们大多数人都会感到惊讶。 (我试图根据这种误解回答SO问题,这并不容易。)

    Bash字符串,无论其他什么,都是字符串。它们不是bash程序。在我看来,将它们突然解释为bash程序,不仅会令人惊讶,而且会带来危险。至少如果你使用eval,代码审查员就会有一个很大的红旗。

相关问题