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

从命令行传递字符串时的Bash怪癖,bash,shell,scripting,sh,Bash,Shell,Scripting,Sh,这是我创建的一个示例,用于演示这种奇怪的行为。 我希望bash按原样传递引用的命令行参数 john@doe:~/tmp$ cat script.sh #! /bin/bash set -o xtrace $1 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$ cat script.sh 
#! /bin/bash
set -o xtrace
$1 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 &&

第一个回显两个单词,第二个等待更多输入。您称之为quirky的引用按照设计工作,是您真正想要的,不,除了黑客bash源代码之外,没有其他方法可以绕过它。

手册中概述了规则:

你会看到:

以下是shell在运行时的操作的简要说明 读取并执行命令。基本上,shell执行以下操作:

  • 从文件(请参阅)、作为参数提供给
    -c
    调用选项(请参阅)的字符串或用户终端读取其输入
  • 按照中描述的引用规则,将输入拆分为单词和运算符。这些代币之间用空格分隔 元字符。别名扩展通过此步骤执行(请参阅 )
  • 将标记解析为简单命令和复合命令(请参阅)
  • 执行各种shell扩展(请参阅),将扩展的令牌拆分为文件名列表(请参阅)、命令和参数列表
  • 执行任何必要的重定向(请参阅),并从参数列表中删除重定向运算符及其操作数
  • 执行命令(请参见)
  • (可选)等待命令完成并收集其退出状态(请参阅)
  • 因此,使用您的脚本:

    #! /bin/bash
    
    $1 sleep 3
    
    执行
    /script“echo hello;”
    时会发生什么

  • Bash读取您的脚本
  • 它将输入分解为单词和运算符。注意,此时,Bash没有看到控制操作符
    这是在你的论点
    回声你好。
    此时,它只看到三个令牌
    $1
    sleep
    3
  • 它将令牌解析为简单和复合命令。目前还没有简单或复合的命令
  • 它执行shell和文件名扩展:此时,您的令牌
    $1
    被扩展为
    echo
    hello
    (这里的分号并不是一个参数,它不被理解为控制运算符,因为控制运算符的扫描已在步骤2中完成,将不再执行)
  • 这里没有要执行的重定向
  • Bash现在执行命令:它看到带有参数的命令
    echo
    睡眠
    3
    。因此,它愉快地回响着
    你好;睡眠3
  • 执行
    echo
    后收集退出状态(很可能成功)
  • 有两种方法可以避免这种情况:第一种是使用
    eval
    ,但这很危险,并且有很多警告:用

    #!/bin/bash
    
    eval "$1 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

    脚本也可以实现同样的效果:调用以下脚本
    说“你好”和“执行”

    #!/bin/bash
    
    echo hello; "$@"
    
    然后
    chmod+x说你好并执行
    并以
    /script./say你好并执行
    的形式运行脚本



    这个答案试图回答你的问题。不过,如果您能准确地告诉我们您想要实现什么,那就更好了,因为可能会有更好的设计来实现这一点。

    很明显,示例应该有单引号,而不是反引号。感谢您全面的回答!(和gnu参考)。我发现我走错了方向。