Bash 从命令行传递字符串时的怪癖

Bash quirk when passing string from command line

这是我为演示古怪行为而创建的示例。 我希望 bash 按原样传递引用的命令行参数。

john@doe:~/tmp$ cat script.sh 
#! /bin/bash
set -o xtrace
 sleep 3
john@doe:~/tmp$ ./script.sh "echo"
+ echo sleep 3
sleep 3
john@doe:~/tmp$ ./script.sh "echo -n"
+ echo -n sleep 3
sleep 3john@doe:~/tmp$

但有时 bash 会编辑我的字符串参数,并在符号周围添加刻度 喜欢 ;或 &&

john@doe:~/tmp$ ./script.sh "echo hello ;"
+ echo hello ';' sleep 3
hello ; sleep 3
john@doe:~/tmp$ ./script.sh "echo hello && "
+ echo hello '&&' sleep 3
hello && sleep 3

这个 bash 规则是什么以及如何规避它?非常感谢:)

规则是引用否则会有不同解释的元字符。分号是语句分隔符,&& 也是如此。引用使得剪切和粘贴特定行变得容易 并让它重现原始效果 .

想想

的区别
echo hello '&&'

对比

echo hello &&

第一个回显两个词,第二个等待更多输入。你称之为古怪的引述按设计工作,是你真正想要的,不,除了破解 bash 源代码之外别无他法。

手册中概述了该规则:Section Shell Operation

您将阅读:

The following is a brief description of the shell’s operation when it reads and executes a command. Basically, the shell does the following:

  1. Reads its input from a file (see Shell Scripts), from a string supplied as an argument to the -c invocation option (see Invoking Bash), or from the user’s terminal.
  2. Breaks the input into words and operators, obeying the quoting rules described in Quoting. These tokens are separated by metacharacters. Alias expansion is performed by this step (see Aliases).
  3. Parses the tokens into simple and compound commands (see Shell Commands).
  4. Performs the various shell expansions (see Shell Expansions), breaking the expanded tokens into lists of filenames (see Filename Expansion) and commands and arguments.
  5. Performs any necessary redirections (see Redirections) and removes the redirection operators and their operands from the argument list.
  6. Executes the command (see Executing Commands).
  7. Optionally waits for the command to complete and collects its exit status (see Exit Status).

所以,有了你的脚本:

#! /bin/bash

 sleep 3

当你执行 ./script "echo hello ;" 时会发生什么?

  1. Bash 阅读您的脚本。
  2. 它将输入分解为单词和运算符。请注意,此时 Bash 没有看到您的参数 echo hello ; 中的控制运算符 ;。 此时,它只看到三个标记 </code>、<code>sleep3.
  3. 它将标记解析为简单和复合命令。目前还没有简单或复合命令。
  4. 它执行 shell 和文件名扩展:此时,您的令牌 </code> 被扩展为 <code>echohello;(不是这里的分号只是一个参数,它不能被理解为控制运算符,因为控制运算符的扫描已经在步骤2中完成,不会再执行)。
  5. 这里没有要执行的重定向。
  6. Bash 现在执行命令:它看到带有参数 hello;sleep3 的命令 echo。所以它愉快地回应了hello ; sleep 3.
  7. 在执行 echo 后收集退出状态(很可能成功)。

有两种方法可以避免这种情况:第一种是使用 eval,但这很危险并且有很多注意事项:将您的脚本替换为

#!/bin/bash

eval " sleep 3"

使用此脚本:

$ ./script "echo hello;"
hello
$

(延迟3秒后显示最终提示)。 ./script "echo hello &&" 也一样。 但这不是一个好主意。继续阅读。

作为一般规则,您应该避免(至少在 Bash 中)混合数据和代码:变量在这里保存数据(字符串),而不是代码。所以这个技术不是一个好技术。我说这不是一个好的,不仅因为有人曾经说过你不应该将数据与代码混合,而且因为如果你开始在你的“代码”中使用特殊字符,它将变得无法使用。例如,如果你想回显 * 符号,你将不得不使用笨拙的调用 ./script "echo \"*\";"。还有许多其他注意事项。

代码保存在函数或脚本中。使用函数:

say_hello_and_execute() {
    echo hello; "$@"
}

然后导出此函数:export -f say_hello_and_execute 并将其用作脚本的参数:./script say_hello_and_execute.

同样可以用脚本实现:调用下面的脚本say_hello_and_execute:

#!/bin/bash

echo hello; "$@"

然后 chmod +x say_hello_and_execute 和 运行 您的脚本为 ./script ./say_hello_and_execute


此答案试图回答您的问题。不过,如果您能准确地告诉我们您想要实现的目标会更好,因为可能会有更好的设计来实现它。