【问题标题】:How to use SSH to run a local shell script on a remote machine?如何使用 SSH 在远程机器上运行本地 shell 脚本?
【发布时间】:2023-03-10 14:16:01
【问题描述】:

我必须在远程机器上运行本地 shell 脚本(windows/Linux)。

我在机器 A 和 B 上都配置了 SSH。我的脚本在机器 A 上,它将在远程机器 B 上运行我的一些代码。

本地和远程计算机可以是基于 Windows 或 Unix 的系统。

有没有办法使用 plink/ssh 来运行?

【问题讨论】:

  • 同样的问题已经出现在 serverfault 上:serverfault.com/questions/215756/… 所以迁移这个问题可能没有意义。
  • 关于服务器故障的问题虽然没有那么多答案。也许这个问题应该取代那个问题。
  • 我个人喜欢这个答案:unix.stackexchange.com/questions/87405/…
  • 此外,它显然应该成为话题,因为 ssh 是软件开发的主要工具。
  • 咖啡和 ssh 问题在 SO 上的离题程度不同。投票支持重新开放。

标签: shell ssh sysadmin remote-execution


【解决方案1】:

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

plink root@MachineB -m local_script.sh

如果机器 A 是基于 Unix 的系统,您可以使用:

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

您不必将脚本复制到远程服务器来运行它。

【讨论】:

  • 使用-s 选项有什么好处吗? this man page 让我相信它会在处理完选项后处理标准输入,无论是否使用-s
  • 对于需要sudo 的脚本,运行ssh root@MachineB 'echo "rootpass" | sudo -Sv &amp;&amp; bash -s' &lt; local_script.sh
  • @bradley.ayers 如果您已经以 root 身份登录,在什么情况下需要 sudo?
  • @Agostino,您可以像这样添加参数:ssh root@MachineB ARG1="arg1" ARG2="arg2" 'bash -s' &lt; local_script.sh Credits 完全转到下面的@chubbsondubs 答案。
  • @YvesVanBroekhoven 这已经很老了,但-s 的真正意义在于能够将参数传递给通过stdin 获取的脚本:ssh root@MachineB 'bash -s arg1 arg2' &lt; local_script.sh。省略-s 将导致arg1 被解释为远程脚本以arg2 作为其第一个参数执行。无需使用环境变量。
【解决方案2】:

这是一个老问题,杰森的回答很好,但我想补充一下:

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.secureftp-test.com <<'ENDFTP'
test
test
ls
ENDFTP
END2
ENDSSH

您实际上可以与一些服务进行对话,如 telnet、ftp 等。但请记住,heredoc 只是将标准输入作为文本发送,它不会等待行之间的响应

编辑:我刚刚发现如果您使用&lt;&lt;-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.secureftp-test.com <<-'ENDFTP'
            test
            test
            ls
        ENDFTP
    END2
ENDSSH

(我认为这应该可行)

另见 http://tldp.org/LDP/abs/html/here-docs.html

【讨论】:

  • 你可以通过添加如下几行来临时化一点:# $(sleep 5)
  • 请注意,用单引号括住终止符 (&lt;&lt;'ENDSSH'),字符串不会被扩展,变量不会被计算。如果需要扩展,也可以使用&lt;&lt;ENDSSH&lt;&lt;"ENDSSH"
  • Expect 可以在您需要自动化 FTP 等交互式命令时使用。
  • 请注意,我收到了 Pseudo-terminal will not be allocated because stdin is not a terminal. 消息。必须使用带有-t -t 参数的ssh 来避免这种情况。看到这个thread on SO
  • 如果您尝试使用
【解决方案3】:

另外,如果您想从目标主机获取变量,请不要忘记转义变量。

这在过去让我很失望。

例如:

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”。

【讨论】:

  • 但请注意以下几点:ssh user2@host 'bash -s' echo $HOME /home/user2 exit
  • 只是在ssh会话中运行的for循环中添加循环变量不能被转义。
  • 在许多情况下,修复上一个示例的明智方法是ssh user2@host2 'echo hello world' | awk '{ print $1 }',即在本地运行 Awk 脚本。如果远程命令产生大量输出,您当然要避免将其全部复制回本地服务器。顺便说一句,远程命令周围的单引号避免了任何转义的需要。
【解决方案4】:

这是 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'

在这种情况下,我们将 ARG1 和 ARG2 设置为本地值。将 user@host 之后的所有内容作为 remote_command 发送。当远程机器执行命令 ARG1 和 ARG2 时,设置本地值,这要归功于本地命令行评估,它定义远程服务器上的环境变量,然后使用这些变量执行 bash -s 命令。瞧。

【讨论】:

  • 请注意,如果您想传递-a 之类的参数,则可以使用--。例如'ssh user@host -- -a foo bar 'bash -s'
  • 如果任何 env var 值包含空格,请使用:ssh user@host "ARG1=\"$ARG1\" ARG2=\"$ARG2\"" 'bash -s' &lt;&lt;'ENDSSH'...
  • 就像我在另一条评论中写的那样,使用-s 的目的是能够将参数应用于通过标准输入获取的脚本。我的意思是,如果你不打算使用它,你也可以省略它。如果你确实使用它,没有理由使用环境变量:ssh user@host 'bash -s value1 value2' &lt;&lt;&lt; 'echo "$@"'
【解决方案5】:
<hostA_shell_prompt>$ ssh user@hostB "ls -la"

这将提示您输入密码,除非您已将 hostA 用户的公钥复制到用户 .ssh 目录主目录上的 authorized_keys 文件中。这将允许无密码身份验证(如果在 ssh 服务器的配置中被接受为身份验证方法)

【讨论】:

  • 投了你一票。这是一个有效的解决方案。显然,密钥必须受到保护,但它们也可以像密码一样通过服务器端失效。
  • 我认为这不能回答问题。该示例显示了如何运行远程命令,而不是如何在远程计算机上执行本地脚本。
  • 不确定,但您不能使用这种方法在 hostA 上通过管道传输脚本以在 hostB 上运行吗?
【解决方案6】:

我已经开始使用Fabric 进行更复杂的操作。 Fabric 需要 Python 和其他一些依赖项,但仅在客户端计算机上。服务器只需是 ssh 服务器。我发现这个工具比交给 SSH 的 shell 脚本强大得多,而且非常值得费心去设置(特别是如果你喜欢用 Python 编程的话)。 Fabric 处理在多个主机(或特定角色的主机)上运行的脚本,有助于促进幂等操作(例如在配置脚本中添加一行,但如果它已经存在则不会),并允许构建更复杂的逻辑(例如 Python语言可以提供)。

【讨论】:

    【解决方案7】:
    cat ./script.sh | ssh <user>@<host>
    

    【讨论】:

    • 有趣的是,'cat script.sh | 这个方法ssh user@host' 是唯一一个可以让我在另一台主机上运行 shell 脚本的人。 'bash'
    【解决方案8】:

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

    【讨论】:

    • 这仅在脚本位于远程的默认(主)目录中时才有效。我认为问题是如何运行远程存储在本地的脚本。
    • ssh username@ip "chmod +x script.sh"
      ssh username@ip "远程主机中 sh 文件的路径"
    【解决方案9】:
    chmod +x script.sh    
    ssh -i key-file root@111.222.3.444 < ./script.sh
    

    【讨论】:

      【解决方案10】:

      假设您想从“本地”机器自动执行此操作,而不需要手动登录到“远程”机器,您应该查看一个名为 Expect 的 TCL 扩展程序,它专为这种情况而设计。我还提供了一个脚本链接,用于通过 SSH 登录/交互。

      https://www.nist.gov/services-resources/software/expect

      http://bash.cyberciti.biz/security/expect-ssh-login-script/

      【讨论】:

        【解决方案11】:

        我用这个在远程机器上运行一个 shell 脚本(在 /bin/bash 上测试):

        ssh deploy@host . /home/deploy/path/to/script.sh
        

        【讨论】:

          【解决方案12】:
          ssh user@hostname ".~/.bashrc;/cd path-to-file/;.filename.sh"
          

          强烈推荐源环境文件(.bashrc/.bashprofile/.profile)。在远程主机上运行某些东西之前,因为目标主机和源主机的环境变量可能有所不同。

          【讨论】:

          • 这里不解释如何将本地脚本移动到远程主机。
          • ~/.bashrc 前面好像少了一个空格
          【解决方案13】:

          如果你想执行这样的命令 temp=`ls -a` echo $temp ``中的命令会导致错误。

          下面的命令可以解决这个问题 ssh user@host ''' temp=`ls -a` echo $temp '''

          【讨论】:

            【解决方案14】:

            这里的答案 (https://*.com/a/2732991/4752883) 非常有用,如果 您正在尝试使用plinkssh 在远程Linux 机器上运行脚本。 如果脚本在linux 上有多行,它将起作用。

            **但是,如果您尝试运行位于本地的批处理脚本 linux/windows 机器和你的远程机器是Windows,它包括 多行使用**

            plink root@MachineB -m local_script.bat

            不会工作。

            只会执行脚本的第一行。这大概是一个 plink的限制。

            解决方案 1:

            运行多行批处理脚本(特别是如果它相对简单, 由几行组成):

            如果你原来的批处理脚本如下

            cd C:\Users\ipython_user\Desktop 
            python filename.py
            

            您可以使用“&&”分隔符将这些行组合在一起,如下所示 local_script.bat 文件: https://*.com/a/8055390/4752883:

            cd C:\Users\ipython_user\Desktop && python filename.py
            

            在此更改之后,您可以按照此处指出的那样运行脚本 @JasonR.Coombs:https://*.com/a/2732991/4752883 与:

            `plink root@MachineB -m local_script.bat`
            

            解决方案 2:

            如果你的批处理脚本比较复杂,使用批处理可能会更好 封装 plink 命令的脚本以及如下所示 @Martin https://*.com/a/32196999/4752883

            rem Open tunnel in the background
            start plink.exe -ssh [username]@[hostname] -L 3307:127.0.0.1:3306 -i "[SSH
            key]" -N
            
            rem Wait a second to let Plink establish the tunnel 
            timeout /t 1
            
            rem Run the task using the tunnel
            "C:\Program Files\R\R-3.2.1\bin\x64\R.exe" CMD BATCH qidash.R
            
            rem Kill the tunnel
            taskkill /im plink.exe
            

            【讨论】:

              【解决方案15】:

              这个 bash 脚本会通过 ssh 进入目标远程机器,并在远程机器上运行一些命令,在运行它之前不要忘记安装期望(在 mac 上 brew install expect

              #!/usr/bin/expect
              set username "enterusenamehere"
              set password "enterpasswordhere"
              set hosts "enteripaddressofhosthere"
              spawn ssh  $username@$hosts
              expect "$username@$hosts's password:"
              send -- "$password\n"
              expect "$"
              send -- "somecommand on target remote machine here\n"
              sleep 5
              expect "$"
              send -- "exit\n"
              

              【讨论】:

              • 如果您必须使用密码,那就太好了...但是为了方便在家观看的任何人,任何 ssh 命令都应该使用一对公钥+私钥而不是密码...一旦到位,请更新您的 ssh服务器完全关闭密码
              【解决方案16】:

              你可以使用runoverssh:

              sudo apt install runoverssh
              
              runoverssh -s localscript.sh user host1 host2 host3...
              

              -s 远程运行本地脚本


              有用的标志:
              -g 为所有主机使用全局密码(单一密码提示)
              -n 使用 SSH 而不是 sshpass,对公钥身份验证很有用

              【讨论】:

                【解决方案17】:

                如果脚本很短并且打算嵌入到您的脚本中,并且您在bash shell 下运行并且bash shell 在远程端可用,您可以使用declare 将本地上下文传输到偏僻的。定义包含将被传输到远程的状态的变量和函数。定义将在远程端执行的函数。然后在bash -s读取的here文档中,您可以使用declare -p传输变量值并使用declare -f将函数定义传输到远程。

                因为declare 负责引用并将被远程bash 解析,所以变量被正确引用并且函数被正确传输。您可能只是在本地编写脚本,通常我会在远程端完成我需要做的工作的一个长功能。必须手动选择上下文,但以下方法对于任何短脚本都“足够好”并且是安全的 - 应该正确处理所有极端情况。

                somevar="spaces or other special characters"
                somevar2="!@#$%^"
                another_func() {
                    mkdir -p "$1"
                }
                work() {
                    another_func "$somevar"
                    touch "$somevar"/"$somevar2"
                }
                ssh user@server 'bash -s' <<EOT
                $(declare -p somevar somevar2)    # transfer variables values
                $(declare -f work another_func)   # transfer function definitions
                work                              # call the function
                EOT
                

                【讨论】:

                  【解决方案18】:

                  如果是一个脚本,上面的解决方案就可以了。

                  我会设置 Ansible 来完成这项工作。它以相同的方式工作(Ansible 使用 ssh 在 Unix 或 Windows 的远程机器上执行脚本)。

                  它将更加结构化和可维护。

                  【讨论】:

                    【解决方案19】:

                    不清楚本地脚本是否使用本地设置的变量、函数或别名。

                    如果这样做应该可以:

                    myscript.sh:

                    #!/bin/bash
                    
                    myalias $myvar
                    myfunction $myvar
                    

                    它使用$myvarmyfunctionmyalias。让我们假设它们是在本地而不是在远程机器上设置的。

                    制作一个包含脚本的 bash 函数:

                    eval "myfun() { `cat myscript.sh`; }"
                    

                    设置变量、函数和别名:

                    myvar=works
                    alias myalias='echo This alias'
                    myfunction() { echo This function "$@"; }
                    

                    然后使用来自 GNU Parallel 的 env_parallel“导出”myfunmyfunctionmyvarmyaliasserver

                    env_parallel -S server -N0  --nonall myfun ::: dummy
                    

                    【讨论】:

                      【解决方案20】:

                      首先,使用 scp 将脚本复制到机器 B

                      [user@machineA]$ scp /path/to/script user@machineB:/home/user/path

                      然后,只需运行脚本

                      [user@machineA]$ ssh user@machineB "/home/user/path/script"

                      如果您已授予脚本可执行权限,这将起作用。

                      【讨论】:

                      • 嗨,我应用了推荐的建议,但它给了我以下错误 [oracle@node1 ~]$ ssh oracle@node2:./home/oracle/au/fs/conn.sh ssh: node2:./ home/oracle/au/fs/conn.sh:名称或服务未知 [oracle@node1 ~]$
                      • 'ssh oracle@node2:./home/oracle/au/fs/conn.sh'?命令行错误,命令名应该和user@host部分用空格隔开,而不是冒号。
                      • 我对此表示反对,因为它的主要主张是不复制就无法运行它是不正确的。
                      • 如果 Jason 补充说为什么这是不正确的而不是简单地陈述事实,那将会很有帮助。没用。
                      • [user@machineA]$ ssh root@MachineB 'bash -s' /path/to/script