【问题标题】:How to ssh into server, and execute bash commands on that server by using python如何通过 ssh 进入服务器,并使用 python 在该服务器上执行 bash 命令
【发布时间】:2015-01-27 15:46:48
【问题描述】:

我想通过 ssh 进入服务器,并在登录到该服务器后在该服务器上执行一些 bash 命令,并且我想使用 python 脚本来执行此操作。我仅限于使用子进程(我不允许导入其他模块,如 pexpect 或 paramiko) 这是我到目前为止的代码:

import sys
import os
import subprocess
import time

user = "let's say a user"
host = "the remote server's ip"
sshCommand = "sshpass -p 'the remote server's password' ssh -o     UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no %s@%s" %(user, host)
process1 = subprocess.Popen(sshCommand, shell=True, stdout = subprocess.PIPE)
copyfileCommand = "scp afile.file user@serverip: path of server directory"
process2 = subprocess.Popen(copyfileCommand, shell=True, stdin = process1.stdout, stdout = subprocess.PIPE,  stderr=subprocess.STDOUT)

pwdcommand = "pwd"
process3 = subprocess.Popen(pwdcommand, shell=True, stdin = process2.stdout, stdout=subprocess.PIPE,  stderr=subprocess.STDOUT)
out, err = process3.communicate()[0]

据我了解,要在第一个命令之后执行第二个命令,我需要将第二个命令的标准输入设置为第一个命令的标准输出,并且按照相同的逻辑,对第三个命令执行此操作。但是,当脚本执行到第三个命令时,pwd 给了我本地计算机的路径而不是远程服务器上的路径,并且我要复制到远程服务器的文件也没有复制。我究竟做错了什么? 这只是我需要在远程服务器上执行的前几个命令,一旦我了解了它的工作原理,其他命令就很容易了。

谢谢

【问题讨论】:

  • 改用paramiko 模块。
  • 这整个问题正是 Fabric 想要解决的问题。

标签: python


【解决方案1】:

以交互方式运行远程 shell 可能具有挑战性,因为您使用的是管道而不是 pty(终端)。如果您只想发送一组固定的命令,请将它们写入您的 ssh 的标准输入,如下所示。如果您想进行交互,您需要通过 python pty 模块运行本地 ssh。您可以使用 pexpect 源来查看它是如何完成的。

编辑

添加了代码以在本地命令上转义密码。我没有向远程命令添加任何转义,假设 OP 确实想要传递环境变量之类的东西,但同样的技术也适用。

import sys
import os
import subprocess
import time
import pipes

user = "let's say a user"
host = "the remote server's ip"
password = "the remote server's password"
remote_commands = """scp afile.file user@serverip: path of server directory
pwd
"""
sshCommand = "sshpass -p %s ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no %s@%s" % (pipes.quote(password), user, host)
process1 = subprocess.Popen(sshCommand, shell=True, stdin = subprocess.PIPE, stdout = subprocess.PIPE)
out, err = process1.communicate(remote_commands)

【讨论】:

  • 我认为这里的答案可能应该显示任意文件名的正确转义(为了在本地和远程 shell 解释过程中都存在)足以/完整地向某人提供他们需要的信息从 Python 安全地使用 scp
  • 正如@CharlesDuffy 所指出的,正常的外壳转义规则适用于带有空格或外壳元字符的文件名。 sshCommand 仅由本地 shell 执行,remotes_commands 仅由远程 shell 执行。
  • remotes_commands 包含scp 时,文件名(或者,在这种情况下,“服务器目录的路径”)不仅被远程shell 解释,而且也被解释 通过serverip命名的系统上的全局扩展。
  • 使用shell=True 将远程服务器的密码作为传递给subprocess.Popen 的字符串的一部分而不是shell=False 的数组元素传递也是一种风险。如果密码包含文字字符'"$(rm -rf /)"' 怎么办?传递显式 argv 数组可以保护您免受输入的 shell 解释。
  • 回过头来看问题,我看到这些做法都是从那里抄来的,而不是引进来的。这更容易被原谅。
【解决方案2】:

你混淆了一些东西。 Popen 仅适用于本地主机;如果你想执行远程命令,你有几个选择:

  • 在命令行中将要执行的命令与ssh 一起传递。
  • 通过标准输入将命令通过标准输入传递到远程 shell。
  • 使用paramiko

【讨论】:

  • 在命令行中传递要与 ssh 一起执行的每个命令确实有效,但我的朋友通过将所有命令提供给 process.stdin 帮助我找到了一种方法。如果以后有人想做类似的事情,请参考这个链接:stackoverflow.com/questions/28214455/…
【解决方案3】:

如果你想生成一个字符串,你可以通过ssh 来运行一系列远程命令安全地,这需要正确的 shell 引用命令内部命令(必须使用shell),并且使用shell=True作为外部命令(因此允许使用本地shell - 具有相同的安全含义 - 可以避免):

# Let's say our user is Bobby Tables.
user = 'bobby_tables'
host = 'localhost'
remote_password = """ '"$(rm -rf /)"' """

# commands to run on system named in host variable
commands = [
    ['scp', '/path/to/filename', 'user@thirdhost:/remote/path'],
    ['pwd'],
]
commands_str = '; '.join([' '.join([pipes.quote(word) for word in command])
                          for command in commands])
ssh_command = [
    'sshpass',
    '-p', remote_password,
    'ssh', '-o', 'UserKnownHostsFile=/dev/null',
    '-o', 'StrictHostKeyChecking=no',
    '%s@%s' % (user, host),
    commands_str
]
process1 = subprocess.Popen(ssh_command, stdout=subprocess.PIPE)

此公式(与原始公式不同)对密码中的恶意内容具有强大的抵抗力。由于scp(复制自其前身rcp)的设计缺陷,对文件名中的恶意内容具有鲁棒性会有些困难;如果偏执,我建议检查包含: 字符的源文件名(如果这些在最终产品中被参数化)并中止操作(如果存在)。

【讨论】:

  • 当然,如果 OP 想要环境变量或全局扩展,这在远程端不起作用。
  • @tdelaney,确实——除非明确要求,否则防止此类事情发生是重点。
猜你喜欢
  • 2013-11-22
  • 2023-03-16
  • 2013-09-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多