如何使用SSH在远程机器上运行本地shell脚本?

1415
我需要在远程机器上运行一个本地的脚本(Windows/Linux)。机器A和机器B都配置了SSH。我的脚本在机器A上,将在远程机器B上运行我的代码。本地和远程计算机可以是Windows或Unix系统。有没有一种方法可以使用plink/ssh来实现这个目的?

6
同样的问题已经在Serverfault上了:http://serverfault.com/questions/215756/how-do-i-run-a-local-bash-script-on-remote-machines-via-ssh 因此迁移此问题可能没有意义。 - sleske
9
Server Fault上的那个问题没有那么多答案,也许应该用这个问题来替换它。 - Big McLargeHuge
5
我个人喜欢这个答案:http://unix.stackexchange.com/questions/87405/how-can-i-execute-local-script-on-remote-machine-and-include-arguments - mikevoermans
27
此外,显然应该与主题相关,因为SSH是软件开发的主要工具。 - static_rtti
5
在 Stack Overflow 上,咖啡和 SSH 问题并不具有相同程度的离题性。我投票支持重新开放该问题。 - Vincent Cantin
显示剩余4条评论
24个回答

1361

如果机器A是一个Windows电脑,则您可以使用Plink(PuTTY的一部分)和-m参数,在远程服务器上执行本地脚本。

plink root@MachineB -m local_script.sh

如果 Machine A 是基于 Unix 系统的,你可以使用:

ssh root@MachineB 'bash -s' < local_script.sh

您不需要将脚本复制到远程服务器上才能运行它。


13
使用-s选项是否有优势?根据该 man 手册,它似乎会在处理完选项后处理标准输入,无论是否使用 -s - aeroNotAuto
89
对于需要 sudo 权限的脚本,请运行以下命令:ssh root@MachineB 'echo "rootpass" | sudo -Sv && bash -s' < local_script.sh - bradley.ayers
11
如果您已经以root用户登录,那么在哪些情况下需要使用sudo呢? - Brian Schlenker
28
@Agostino,您可以像这样添加参数:ssh root@MachineB ARG1="arg1" ARG2="arg2" 'bash -s' < local_script.sh完全感谢@chubbsondubs下面的答案。 - Yves Van Broekhoven
18
这是一个旧帖,但-s选项的真正目的是能够向通过标准输入流被调用的脚本传递参数:ssh root@MachineB 'bash -s arg1 arg2' < local_script.sh。省略-s将导致arg1被解释为远程执行的脚本,而arg2则作为其第一个参数。不需要使用环境变量。 - JoL
显示剩余19条评论

739

这是一个旧问题,Jason的回答很好,但我想要补充:

ssh user@host <<'ENDSSH'
#commands to run on remote host
ENDSSH

这也可以与su和需要用户输入的命令一起使用。(请注意带转义符号'的heredoc)

由于此答案不断获得访问量,我想为这种heredoc的精彩用法添加更多信息:

您可以使用此语法嵌套命令,这似乎是嵌套以合理方式工作的唯一方法。

ssh user@host <<'ENDSSH'
#commands to run on remote host
ssh user@host2 <<'END2'
# Another bunch of commands on another host
wall <<'ENDWALL'
Error: Out of cheese
ENDWALL
ftp ftp.example.com <<'ENDFTP'
test
test
ls
ENDFTP
END2
ENDSSH

你实际上可以与一些服务(如telnet、ftp等)进行交互。但请记住,heredoc只是将stdin作为文本发送,它不会在行之间等待响应。

我刚刚发现如果您使用<<-END,您可以使用制表符缩进内部内容!

ssh user@host <<-'ENDSSH'
    #commands to run on remote host
    ssh user@host2 <<-'END2'
        # Another bunch of commands on another host
        wall <<-'ENDWALL'
            Error: Out of cheese
        ENDWALL
        ftp ftp.example.com <<-'ENDFTP'
            test
            test
            ls
        ENDFTP
    END2
ENDSSH

(我认为这应该可以工作)

还可以参考http://tldp.org/LDP/abs/html/here-docs.html


5
你可以通过添加类似以下的行来拖延时间:

$(sleep 5)

- Olivier Dulac
56
请注意,在终止符号(<<'ENDSSH')周围加上单引号,字符串将不会被展开,变量也不会被评估。如果要进行展开,您还可以使用<<ENDSSH<<"ENDSSH" - maackle
3
Expect 可用于自动化交互式命令,比如 FTP。 - programaths
6
请注意,我收到了“伪终端将无法分配,因为标准输入不是终端”的提示信息。我们需要使用ssh并加入参数-t -t来避免该问题。请参考SO上的这个帖子 - Buzut
10
如果你想使用 "<<-'END' " 语法,请确保你的行内文档结束分隔符是使用 TAB 键进行缩进而不是空格。请注意,从 StackExchange 复制/粘贴会产生空格。将它们更改为 TAB 键,缩进功能就应该能正常工作了。 - fbicknel
显示剩余11条评论

270

另外,如果您想从目标主机接收变量,请不要忘记转义它们。

这在过去曾让我出了岔子。

例如:

user@host> ssh user2@host2 "echo \$HOME"

打印出 /home/user2

同时

user@host> ssh user2@host2 "echo $HOME"
打印出 /home/user 另一个例子:
user@host> ssh user2@host2 "echo hello world | awk '{print \$1}'"

正确打印出 "hello"。


2
但请注意以下内容: ssh user2@host 'bash -s' echo $HOME /home/user2 exit - curious_prism
1
只是补充一下,在运行于ssh会话中的for循环中,循环变量不应该被转义。 - AlexeyDaryin
1
在许多情况下,修复您最后一个示例的理智方法是 ssh user2@host2 'echo hello world' | awk '{ print $1 }' 即在本地运行 Awk 脚本。如果远程命令产生大量输出,您肯定希望避免将所有内容都复制回本地服务器。顺便说一句,单引号包含远程命令可以避免任何转义的需要。 - tripleee

173
这是对YarekT答案的扩展,结合本地机器传递ENV变量和内联远程命令,以便您可以在远程端参数化脚本。
ssh user@host ARG1=$ARG1 ARG2=$ARG2 'bash -s' <<'ENDSSH'
  # commands to run on remote host
  echo $ARG1 $ARG2
ENDSSH

我发现这个方法非常有用,因为将所有内容都保存在一个脚本中,使得它易于阅读和维护。

这个方法的原理是:ssh支持以下语法:

ssh user@host remote_command

在bash中,我们可以在单行命令中指定要定义的环境变量,如下所示:

ENV_VAR_1='value1' ENV_VAR_2='value2' bash -c 'echo $ENV_VAR_1 $ENV_VAR_2'

这样就可以很容易地在运行命令之前定义变量。在这种情况下,echo是我们要运行的命令。在echo之前的所有内容都定义了环境变量。

因此,我们将这两个特性与YarekT的答案结合起来,得到:

ssh user@host ARG1=$ARG1 ARG2=$ARG2 'bash -s' <<'ENDSSH'...

在这种情况下,我们将ARG1和ARG2设置为本地值。将user@host之后的所有内容作为remote_command发送。当远程机器执行该命令时,由于本地命令行评估,ARG1和ARG2将被设置为本地值,并且通过定义环境变量在远程服务器上执行bash -s命令。完成。


1
请注意,如果您想传递像-a这样的参数,则可以使用--。例如:'ssh user@host -- -a foo bar 'bash -s' <script.sh'。参数也可以放在重定向之后,例如:'ssh user@host 'bash -s' <script.sh -- -a foo bar'。 - gaoithe
9
如果任何环境变量的值包含空格,请使用以下命令:ssh user@host "ARG1=\"$ARG1\" ARG2=\"$ARG2\"" 'bash -s' <<'ENDSSH'... - TalkLittle
3
就像我在另一条评论中写的那样,使用“-s”的关键在于能够将参数应用到通过stdin源的脚本中。我的意思是,如果你不打算使用它,也可以省略它。 如果你使用它,就没有必要使用环境变量:ssh user@host 'bash -s value1 value2' <<< 'echo "$@"' - JoL

112
<hostA_shell_prompt>$ ssh user@hostB "ls -la"

如果您没有将hostA用户的公钥复制到用户.ssh目录下的authorized_keys文件中,那么这将提示您输入密码。如果在ssh服务器的配置中接受作为身份验证方法,则可以实现免密码身份验证。


3
谢谢您的投票支持。这是一个有效的解决方案。显然,密钥必须受到保护,但它们也可以像密码一样通过服务器端进行无效化。 - willasaywhat
18
我认为这并未回答问题。示例展示了如何运行远程命令,但没有展示如何在远程机器上执行本地脚本。 - Jason R. Coombs
不确定,但是你不能使用这种方法将在主机A上的脚本传输到主机B并运行吗? - nevets1219

28

我开始使用Fabric进行更复杂的操作。 Fabric只在客户端需要Python和其他一些依赖项,而服务器只需要是ssh服务器即可。 我发现这个工具比传递给SSH的shell脚本要强大得多,并且非常值得安装(特别是如果您喜欢用Python编程)。Fabric处理在多个主机(或特定角色的主机)上运行脚本,帮助促进幂等操作(例如将一行添加到配置脚本中,但如果已经存在则不添加),并允许构建更复杂的逻辑(就像Python语言可以提供的那样)。


24
cat ./script.sh | ssh <user>@<host>

1
有趣的是,'cat script.sh | ssh user@host' 这种方法是我在另一台主机上运行 shell 脚本的唯一可行方法。使用 'bash' < script.sh 方法无法正常工作,但我没有花时间排查问题。我甚至将脚本复制到远程主机上,但仍然显示 '文件未找到',即使我登录到主机并运行脚本也能正常工作。 - Jared Still
请问以下命令如何处理:cat ./script.sh | ssh -i ~\.ssh\id_rsa <user>@<host> 'bash -' - LWS

15
chmod +x script.sh    
ssh -i key-file root@111.222.3.444 < ./script.sh

10

尝试运行命令 ssh user@remote sh ./script.unx


12
仅当脚本位于远程主机的默认(home)目录中时,此方法才适用。我认为问题是如何在远程主机上运行存储在本地的脚本。 - metasim
1
ssh username@ip "chmod +x script.sh" <br/> ssh username@ip "远程主机中sh文件的路径" - mani deepak

8

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