如何通过ssh执行带参数的远程命令?

113

在我的.bashrc文件中,我定义了一个函数,以便以后在命令行上使用:

function mycommand() {
    ssh user@123.456.789.0 cd testdir;./test.sh "$1"
}

当使用该命令时,远程主机上只执行cd命令;本地主机上执行test.sh命令。这是因为分号将两个不同的命令分开: ssh命令和test.sh命令。

我尝试按以下方式定义该函数(请注意单引号):

function mycommand() {
    ssh user@123.456.789.0 'cd testdir;./test.sh "$1"'
}

我尝试将cd命令和test.sh命令放在一起,但是无论我给函数什么参数,参数$1始终未被解析,它总是试图执行一个命令。

./test.sh $1

在远程主机上执行命令,

我应该如何正确定义mycommand,以便在进入目录testdir后,在远程主机上执行脚本test.sh并有能力将传递给mycommand的参数传递给test.sh


6
这句话与主要观点无关,但你可能想使用“&&”代替分号来连接在远程主机上执行的命令:cd testdir && ./test.sh "$1"。采用这种形式(因为在bash中,“&&”的求值会短路),如果“cd”失败,则不会执行第二个命令,并且您不会意外地在“user”的主目录中运行不同的“test.sh”。 - Alp
6个回答

136

请改用这种方式:

function mycommand {
    ssh user@123.456.789.0 "cd testdir;./test.sh \"$1\""
}

你仍然需要将整个命令作为单个字符串传递,但在这个单个字符串中,你需要在它发送到ssh之前先扩展$1,因此你需要使用""来引用它。

更新

另一种正确的方法是使用printf %q来正确引用参数。这将使参数安全地解析,即使它具有空格、单引号、双引号或任何其他可能对shell具有特殊意义的字符:

function mycommand {
    printf -v __ %q "$1"
    ssh user@123.456.789.0 "cd testdir;./test.sh $__"
}
  • 使用function声明函数时,()是不必要的。
  • 不要因为你是POSIX主义者而对此发表评论。

从Bash版本4.4开始,也可以简化为这样:

function mycommand {
    ssh user@123.456.789.0 "cd testdir;./test.sh ${1@Q}"
}

请参阅Shell参数扩展中的${parameter@operator}部分。


1
@JoSo 是的,这就是基本原理,因此根据使用情况,用户可以选择对其所需的参数进行消毒。关于基本的 ssh,除非您首先进行文件传输等操作,否则可能没有更好的方法。 - konsolebox
3
@JoSo 是的,更好的方法是定义一个引用函数,像 quote() { printf "'%q'" "$1"; },然后执行 ssh user@host "cd testdir; ./test.sh $(quote "$1")" - augurar
@augurar,几乎可以了,但是%q的结果是自引用的;用文字引号将它们括起来不是必要的也不正确。printf -v arg_str '%q ' "$1"; ssh user@host "cd testdir; ./test.sh $arg_str"就足够了。 - Charles Duffy
@CharlesDuffy 为什么你需要添加空格呢? - konsolebox
@konsolebox,就这个特定情况而言,不需要真的这样做,但这种方式可以使习语在添加更多参数时具有可扩展性,而不是将它们全部挤在一起。 - Charles Duffy
@CharlesDuffy 很好的发现,那是个打字错误。应该是 printf "%q" "$1" - augurar

12

这是一个在AWS Cloud上运行的示例。场景是需要从自动缩放启动的某台机器,在通过SSH传递新生成的实例DNS后执行其他服务器上的一些操作。

# Get the public DNS of the current machine (AWS specific)
MY_DNS=`curl -s http://169.254.169.254/latest/meta-data/public-hostname`


ssh \
    -o StrictHostKeyChecking=no \
    -i ~/.ssh/id_rsa \
    user@remotehost.example.com \
<< EOF
cd ~/
echo "Hey I was just SSHed by ${MY_DNS}"
run_other_commands
# Newline is important before final EOF!

EOF

12
我正在使用以下内容在本地计算机上执行远程命令:
ssh -i ~/.ssh/$GIT_PRIVKEY user@$IP "bash -s" < localpath/script.sh $arg1 $arg2

脚本是远程的,而不是本地的。 - undefined

10
解决方案:您想使用ssh协议远程连接机器并触发/运行一些外部操作。

在ssh中使用-t标志,根据文档:

-t 强制分配伪终端。
这可用于在远程机器上执行任意基于屏幕的程序,非常有用,例如 实现菜单服务时。多个-t选项强制tty分配,即使ssh没有本地tty。

公式

ssh -i <key-path> <user>@<remote-machine> -t '<action>'

示例: 作为管理员,我希望能够远程连接到 EC2 机器,并在多台机器上触发一个恢复进程,以解决多台机器上的错误部署问题。此外,最好将此操作实现为自动化脚本,该脚本使用 IP 地址作为参数,在不同的机器上并行运行。

ssh -i /home/admin/.ssh/key admin@10.20.30.40 -t 'cd /home/application && make revert'

8

重新激活一个旧的线程,但这种相当简洁的方法没有列出来。

function mycommand() {
    ssh user@123.456.789.0 <<+
    cd testdir;./test.sh "$1"
+
}

1
如果$1扩展为一个包含"和可扩展字符(如$\)的字符串,则此操作将失败。 - konsolebox
2
它不会“失败”。它的效果与执行任何带参数的非SSH命令相同。OP并不是在要求双重转义参数的课程,只是想知道如何让他的第二个命令执行他已经打算好的[whatever]参数。 - Ted Bigham
如果用户询问如何在ssh上执行某些操作,那么可以合理地期望任何答案都涵盖特定于ssh的注意事项(即在本地运行代码时不存在的注意事项)。双重扩展(以及由此引起的shell注入漏洞)是这样的注意事项之一,如果直接在本地shell中运行cd testdir; ./test.sh "$1",则不会存在这些问题,但是在给定的代码中确实存在。 - Charles Duffy
我不认为您会给一个展示 SQL 注入漏洞的数据库问题答案点赞,但通过 mycommand' $(rm -rf ~)' 运行任意远程代码的潜在风险同样严重。 - Charles Duffy
事实上,使您的代码更安全所需的更改非常小:printf -v argv_str'%q ' "$@"; ssh user@host"bash -s $argv_str"<<'+',其余部分可以保持不变;对这个heredoc引用“+”符号可以防止其扩展。 - Charles Duffy
显示剩余3条评论

-2
一个小技巧,使用"bash -s"命令可以允许位置参数,但是$0已经被保留了,原因不明。所以可以像下面这样使用两次相同的参数:
ssh user@host "bash -s" < ./start_app.sh -e test -e test -f docker-compose.services.yml 

脚本是远程的,而不是本地的。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接