【问题标题】:Bash arguments literal interpretationBash 参数字面解释
【发布时间】:2013-11-03 11:20:30
【问题描述】:

我有一个简单的 bash 脚本可以在给定的一组服务器上运行远程命令。

#!/bin/bash

echo "Command to be run:"
echo "$*"
read nothing

servers="server1 server2 server3"

for server in `echo $servers`
do
    echo $server
    ssh $server "$*"
    echo ""
done

问题是命令可以包含任意数量的参数,因此使用 $* 并且还可以包含许多不同的字符,包括引号和正则表达式。这里的基本需求是让 shell 接受参数,不管它们是什么,从字面上看,这样它们就可以原封不动地传递给远程服务器,而无需删除引号或解释括号等。

我见过许多变体,但大多数都处理特定的字符问题或使所需的脚本或参数过于复杂,我希望至少使参数不包含转义字符等。

一个使用“@”的例子:

./cmd tw_query --no-headings "search Host where name matches '(?i)peter' show summary, nodecount(traverse :::Detail where name matches 'bob')"

给予:

Command to be run:
tw_query --no-headings search Host where name matches '(?i)peter' show summary, nodecount(traverse :::Detail where name matches 'bob')

【问题讨论】:

  • 使用 "$@" 而不是 "$*" 这应该可以解决您的问题。
  • 您可以将for server in echo $servers` ` 替换为for server in $servers
  • 将这样的字符串传递给ssh 很困难,因为在调用脚本和设置$* 之前会解析一次字符串,然后在远程端接收到字符串时再解析一次。编写脚本,将其复制到远程主机,然后通过ssh 执行远程脚本通常更容易。

标签: bash shell arguments


【解决方案1】:

您似乎在寻找$@。说:

ssh $server "$@"

相反。来自manual

*

从一开始扩展到位置参数。当扩展出现在双引号内时,它会扩展为单个单词 每个参数的值由第一个字符分隔 IFS 特殊变量。即"$*"等价于"$1c$2c…", 其中 c 是 IFS 变量值的第一个字符。如果 IFS 未设置,参数以空格分隔。如果IFS 为空, 参数在没有中间分隔符的情况下连接。

@

从一开始扩展到位置参数。当扩展发生在双引号内时,每个参数扩展为 单独的词。也就是说,"$@" 等价于"$1" "$2" …。如果 双引号扩展出现在一个单词中,扩展 第一个参数与原始的开始部分连接 词,最后一个参数的扩展与最后一个连接 原词的一部分。当没有位置参数时, "$@"$@ 扩展为空(即,它们被删除)。

【讨论】:

    【解决方案2】:

    您实际上不希望将参数完整地传递给远程服务器,而是希望它们完整地传递给远程命令。但这意味着它们需要被包裹在额外的引号/转义/等层中,以便在远程 shell 解析它们之后它们将完整地出来

    bash 实际上在其printf 内置函数中有一个功能,可以为字符串添加引用/转义,但它引用了适当的引用以供 bash 本身解释——如果远程 shell 是别的东西,它可能不理解引用模式它选择。所以在这种情况下,我推荐一种简单而愚蠢的引用风格:只需在每个参数周围添加单引号,并将参数中的每个单引号替换为 '\'' (这将结束当前引用的字符串,添加转义(文字)引号,然后开始另一个带引号的字符串)。它看起来有点奇怪,但应该可以在任何 POSIX 兼容的 shell 下正确解码。

    转换为这种格式有点棘手,因为 bash 在其搜索和替换模式中使用引号做不一致的事情。这是我想出的:

    #!/bin/bash
    
    quotedquote="'\''"
    printf -v quotedcommand "'%s' " "${@//\'/$quotedquote}"
    echo "Command to be run:"
    echo "$quotedcommand"
    read nothing
    
    servers="server1 server2 server3"
    
    for server in $servers
    do
        echo $server
        ssh $server "$quotedcommand"
        echo ""
    done
    

    下面是它引用您的示例命令的方式:

    'tw_query' '--no-headings' 'search Host where name matches '\''(?i)peter'\'' show summary, nodecount(traverse :::Detail where name matches '\''bob'\'')'
    

    引用命令本身看起来很奇怪,但只要您不尝试使用别名,这不会造成任何实际问题。但是有一个明显的限制:无法将 shell 元字符(如用于输出重定向的 >)传递给远程 shell:

    ./cmd somecommand >outfile    # redirect is done on local computer
    ./cmd somecommand '>outfile'    # ">outfile" is passed to somecommand as an argument
    

    如果您需要执行远程重定向等操作,事情会变得更加复杂。

    【讨论】:

    • 你有一个你不想要的双等号,你可以在那里使用printf -v而不是=赋值。
    【解决方案3】:

    除了$*$@ 的问题之外,如果这是用于生产环境,您可能需要考虑使用诸如pdsh 之类的工具。

    否则,您可以尝试通过标准输入将命令提供给脚本,而不是将它们放在参数中,这样您就可以避免一级解析。

    #!/bin/bash
    
    read cmd
    echo "Command to be run:"
    echo $cmd
    servers="server1 server2 server3"
    
    for server in `echo $servers` do
        echo $server
        ssh $server "$cmd"
        echo "" 
    done
    

    并像这样使用它

    $ ./cmd <<'EOT' 
    > tw_query --no-headings "search Host where name matches '(?i)peter' show summary,       nodecount(traverse :::Detail where name matches 'bob')"
    > EOT
    Command to be run:
    tw_query --no-headings "search Host where name matches '(?i)peter' show summary, nodecount(traverse :::Detail where name matches 'bob')"
    

    也许有点牵强,但它可以工作。

    【讨论】:

    • 谢谢,但是在这个应用程序操作系统上安装额外的软件,充其量是不受欢迎的!还是)感谢你的建议! ;)
    猜你喜欢
    • 1970-01-01
    • 2014-12-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-01-15
    • 2013-05-11
    • 2020-07-02
    • 1970-01-01
    相关资源
    最近更新 更多