0

我有一个简单的 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')
4

3 回答 3

2
于 2013-10-24T16:16:49.700 回答
1

您实际上不希望将参数完整地传递给远程服务器,而是希望它们完整地传递给远程命令。但这意味着它们需要被包裹在额外的引号/转义/等层中,以便在远程 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

如果你需要做远程重定向之类的事情,事情就会变得复杂得多。

于 2013-10-25T02:16:03.407 回答
0

除了$*vs的问题之外$@,如果这是用于生产环境,您可能需要考虑使用诸如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')"

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

于 2013-10-24T16:26:42.033 回答