【问题标题】:How to copy a file to a remote server in Python using SCP or SSH?如何使用 SCP 或 SSH 将文件复制到 Python 中的远程服务器?
【发布时间】:2010-09-09 06:15:16
【问题描述】:

我的本​​地机器上有一个文本文件,它是由在 cron 中运行的日常 Python 脚本生成的。

我想添加一些代码以将该文件通过 SSH 安全地发送到我的服务器。

【问题讨论】:

    标签: python ssh automation scp


    【解决方案1】:

    您可以使用subprocess.run 调用scp bash 命令(它通过SSH 复制文件):

    import subprocess
    subprocess.run(["scp", FILE, "USER@SERVER:PATH"])
    #e.g. subprocess.run(["scp", "foo.bar", "joe@srvr.net:/path/to/foo.bar"])
    

    如果您正在创建要在同一个 Python 程序中发送的文件,您需要在您用来打开文件的 with 块之外调用 subprocess.run 命令(或调用 @987654329如果您没有使用 with 块,请首先在文件上使用 @),​​因此您知道它已从 Python 刷新到磁盘。

    您需要预先生成(在源计算机上)并安装(在目标计算机上)一个 ssh 密钥,以便 scp 自动使用您的公共 ssh 密钥进行身份验证(换句话说,因此您的脚本不会要求密码)。

    【讨论】:

    • @CharlesDuffy: subprocess.check_call(['scp', srcfile, dest]) 可以从 Python 2.5 开始使用,而不是 rc = Popen(..).wait(); if rc != 0: raise ..
    • 在不需要时添加 ssh 密钥并不是一个好的解决方案。例如,如果您需要一次性通信,出于安全原因,您无需设置 ssh 密钥。
    【解决方案2】:

    有点老套,但以下应该可以工作:)

    import os
    filePath = "/foo/bar/baz.py"
    serverPath = "/blah/boo/boom.py"
    os.system("scp "+filePath+" user@myserver.com:"+serverPath)
    

    【讨论】:

      【解决方案3】:

      您可能会使用subprocess module。像这样的:

      import subprocess
      p = subprocess.Popen(["scp", myfile, destination])
      sts = os.waitpid(p.pid, 0)
      

      destination 的形式可能是user@remotehost:remotepath。谢谢 @Charles Duffy 指出了我原始答案中的弱点,它使用单个字符串参数来指定 scp 操作 shell=True - 这不会处理路径中的空格。

      模块文档有examples of error checking that you may want to perform in conjunction with this operation.

      确保您已设置正确的凭据,以便您可以执行unattended, passwordless scp between the machines。有一个stackoverflow question for this already

      【讨论】:

      • 使用 subprocess.Popen 是正确的。将字符串而不是数组传递给它(并使用 shell=True)是错误的,因为这意味着带空格的文件名无法正常工作。
      • 我们可以使用“sts = p.wait()”来代替“sts = os.waitpid(p.pid, 0)”。
      • 这个协议有没有直接的 python API 的免费执行方式?
      • subprocess.check_call(['scp', myfile, destination]) 自 Python 2.5 (2006) 起可改为使用
      • @J.F.Sebastian:是的,我在 12 月检查过。我的赞成票至少证明了这一点。 :) 感谢您的跟进。
      【解决方案4】:

      有几种不同的方法可以解决这个问题:

      1. 包装命令行程序
      2. 使用提供 SSH 功能的 Python 库(例如 - ParamikoTwisted Conch

      每种方法都有自己的怪癖。如果要包装系统命令,如“ssh”、“scp”或“rsync”,则需要设置 SSH 密钥以启用无密码登录。您可以使用 Paramiko 或其他一些库在脚本中嵌入密码,但您可能会发现缺少文档令人沮丧,尤其是如果您不熟悉 SSH 连接的基础知识(例如 - 密钥交换、代理等)。毋庸置疑,对于这类东西,SSH 密钥几乎总是比密码更好。

      注意:如果您打算通过 SSH 传输文件,则很难击败 rsync,尤其是当替代方案是普通的旧 scp 时。

      我在使用 Paramiko 时着眼于替换系统调用,但由于易于使用和立即熟悉,我发现自己又回到了包装命令。你可能不一样。我前段时间给海螺做过一次,但它对我没有吸引力。

      如果选择系统调用路径,Python 会提供一系列选项,例如 os.system 或 commands/subprocess 模块。如果使用 2.4+ 版本,我会使用 subprocess 模块。

      【讨论】:

      • 好奇:rsync 与 scp 的故事是什么?
      【解决方案5】:

      要在 Python 中使用 Paramiko 库执行此操作(即不通过 subprocess.Popen 或类似方法包装 scp),您可以执行以下操作:

      import os
      import paramiko
      
      ssh = paramiko.SSHClient() 
      ssh.load_host_keys(os.path.expanduser(os.path.join("~", ".ssh", "known_hosts")))
      ssh.connect(server, username=username, password=password)
      sftp = ssh.open_sftp()
      sftp.put(localpath, remotepath)
      sftp.close()
      ssh.close()
      

      (您可能想要处理未知主机、错误、创建任何必要的目录等等)。

      【讨论】:

      • paramiko 也有一个不错的 sftp.put(self, localpath, remotepath, callback=None) 函数,因此您不必打开 write 并关闭每个文件。
      • 我会注意到 SFTP 与 SCP 不同。
      • @Drahkar 问题要求通过 SSH 发送文件。这就是它的作用。
      • 注意:要使这项工作开箱即用,请在实例化 ssh 后添加 ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
      • 各位,使用服务器密码安全吗?有没有可能使用私钥?
      【解决方案6】:

      fabric 可用于通过 ssh 上传文件:

      #!/usr/bin/env python
      from fabric.api import execute, put
      from fabric.network import disconnect_all
      
      if __name__=="__main__":
          import sys
          # specify hostname to connect to and the remote/local paths
          srcdir, remote_dirname, hostname = sys.argv[1:]
          try:
              s = execute(put, srcdir, remote_dirname, host=hostname)
              print(repr(s))
          finally:
              disconnect_all()
      

      【讨论】:

        【解决方案7】:

        通过子进程调用scp 命令不允许在脚本中接收进度报告。 pexpect 可用于提取该信息:

        import pipes
        import re
        import pexpect # $ pip install pexpect
        
        def progress(locals):
            # extract percents
            print(int(re.search(br'(\d+)%$', locals['child'].after).group(1)))
        
        command = "scp %s %s" % tuple(map(pipes.quote, [srcfile, destination]))
        pexpect.run(command, events={r'\d+%': progress})
        

        python copy file in local network (linux -> linux)

        【讨论】:

          【解决方案8】:

          遇到了同样的问题,但不是“破解”或模拟命令行:

          找到这个答案here

          from paramiko import SSHClient
          from scp import SCPClient
          
          ssh = SSHClient()
          ssh.load_system_host_keys()
          ssh.connect('example.com')
          
          with SCPClient(ssh.get_transport()) as scp:
              scp.put('test.txt', 'test2.txt')
              scp.get('test2.txt')
          

          【讨论】:

          • 注意 - 这依赖于 scp 包。截至 2017 年 12 月,该软件包的最新更新是 2015 年,版本号为 0.1.x。不确定我是否要添加此依赖项。
          • 所以,现在是 2018 年,5 月他们发布了 0.11.0 版。那么,看来 SCPClient 终究没有死?
          • scp 模块定期更新(最后一次是在 2021 年 6 月),在 github 上有 380 颗星。这是一个合法的解决方案。
          【解决方案9】:

          使用外部资源 paramiko;

              from paramiko import SSHClient
              from scp import SCPClient
              import os
          
              ssh = SSHClient() 
              ssh.load_host_keys(os.path.expanduser(os.path.join("~", ".ssh", "known_hosts")))
              ssh.connect(server, username='username', password='password')
              with SCPClient(ssh.get_transport()) as scp:
                      scp.put('test.txt', 'test2.txt')
          

          【讨论】:

            【解决方案10】:

            一个非常简单的方法如下:

            import os
            os.system('sshpass -p "password" scp user@host:/path/to/file ./')
            

            不需要 python 库(仅 os),它可以工作,但是使用此方法需要安装另一个 ssh 客户端。如果在另一个系统上运行,这可能会导致不良行为。

            【讨论】:

              【解决方案11】:

              我用sshfs通过ssh挂载远程目录,用shutil复制文件:

              $ mkdir ~/sshmount
              $ sshfs user@remotehost:/path/to/remote/dst ~/sshmount
              

              然后在python中:

              import shutil
              shutil.copy('a.txt', '~/sshmount')
              

              此方法的优点是,如果您正在生成数据而不是在本地缓存并发送单个大文件,则可以将数据流式传输。

              【讨论】:

                【解决方案12】:

                您可以使用专为此设计的 vassal 包。

                你只需要安装vassal并做

                from vassal.terminal import Terminal
                shell = Terminal(["scp username@host:/home/foo.txt foo_local.txt"])
                shell.run()
                

                此外,它会保存您的身份验证凭据,并且不需要一次又一次地键入它们。

                【讨论】:

                  【解决方案13】:

                  如果您不想使用 SSL 证书,请尝试以下操作:

                  import subprocess
                  
                  try:
                      # Set scp and ssh data.
                      connUser = 'john'
                      connHost = 'my.host.com'
                      connPath = '/home/john/'
                      connPrivateKey = '/home/user/myKey.pem'
                  
                      # Use scp to send file from local to host.
                      scp = subprocess.Popen(['scp', '-i', connPrivateKey, 'myFile.txt', '{}@{}:{}'.format(connUser, connHost, connPath)])
                  
                  except CalledProcessError:
                      print('ERROR: Connection to host failed!')
                  

                  【讨论】:

                    【解决方案14】:

                    您也可以执行类似的操作来处理主机密钥检查

                    import os
                    os.system("sshpass -p password scp -o StrictHostKeyChecking=no local_file_path username@hostname:remote_path")
                    

                    【讨论】:

                      猜你喜欢
                      • 2019-03-28
                      • 2019-11-07
                      • 2016-04-19
                      • 2020-02-25
                      • 2011-12-11
                      • 1970-01-01
                      • 1970-01-01
                      • 2012-02-17
                      • 1970-01-01
                      相关资源
                      最近更新 更多