Paramiko 是一个强大的、纯 Python 实现的 SSHv2 协议库,它允许你用 Python 代码来连接和操作远程服务器,就像你在终端里使用 ssh 命令一样,你可以执行命令、上传下载文件、设置端口转发等。

目录
- 核心概念
- 环境准备
- 核心功能详解
- 1 基础 SSH 连接
- 2 执行命令
- 3 交互式 Shell
- 4 文件传输 (SFTP)
- 5 SSH 端口转发
- 高级主题
- 1 SSH Agent 和密钥认证
- 2 密钥处理
- 3 日志调试
- 4 异常处理
- 完整代码示例
- 总结与最佳实践
核心概念
在深入代码之前,理解 Paramiko 的几个核心类非常重要:
SSHClient: 这是你最常打交道的类,它提供了高级别的 API 来建立 SSH 连接,它封装了连接、认证、执行命令等复杂操作,你可以把它看作是ssh命令本身。AutoAddPolicy: 一个策略类,用于处理服务器的主机密钥(host key),当你第一次连接一个新服务器时,ssh客户端会保存该服务器的公钥(known_hosts),以确保你下次连接时不会被“中间人攻击”。AutoAddPolicy会自动接受并保存服务器的密钥,方便但不安全(在生产环境中应避免使用)。SSHConfig: 用于解析~/.ssh/config文件,让你可以在代码中复用配置文件中的主机别名和设置。SFTPClient: 一个 SFTP (SSH File Transfer Protocol) 客户端,用于在本地和远程服务器之间安全地传输文件,它提供了类似os模块的文件操作接口(如put,get,mkdir,listdir等)。Channel: 代表一个 SSH 连接中的一个“通道”,你可以把它想象成一个独立的会话,当你执行命令或打开交互式 Shell 时,Paramiko 都会在 SSH 连接上创建一个 Channel 来传输数据。Transport: 这是 Paramiko 的底层核心,它负责建立和管理 SSH 连接本身,处理加密、认证等。SSHClient内部就是使用一个Transport对象来完成工作的,通常你不需要直接操作它。
环境准备
你需要安装 paramiko 库,如果还没有安装,可以通过 pip 安装:
pip install paramiko
核心功能详解
1 基础 SSH 连接
这是所有操作的前提,连接方式主要有两种:密码认证和密钥认证。
密码认证

import paramiko
# 创建一个SSHClient实例
client = paramiko.SSHClient()
# 设置自动添加主机密钥策略 (生产环境不推荐)
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
# 连接服务器
hostname = "your_server_ip"
port = 22
username = "your_username"
password = "your_password"
try:
client.connect(hostname, port=port, username=username, password=password)
print("连接成功!")
# 在这里执行其他操作...
except paramiko.AuthenticationException:
print("认证失败,请检查用户名或密码")
except paramiko.SSHException as e:
print(f"SSH连接失败: {e}")
except Exception as e:
print(f"发生错误: {e}")
finally:
# 无论成功与否,最后都要关闭连接
client.close()
print("连接已关闭")
重要提示: AutoAddPolicy 虽然方便,但它会接受任何服务器的密钥,这使得你容易受到中间人攻击,在生产环境中,更安全的做法是使用 SSHClient.load_system_host_keys() 来加载系统已知的 known_hosts 文件,或者手动指定服务器的主机密钥。
# 更安全的方式 client = paramiko.SSHClient() # 加载系统自带的known_hosts文件 client.load_system_host_keys() # 如果服务器不在known_hosts中,连接会抛出SSHException,这是更安全的行为 # 如果你确定要连接,并且信任该服务器,可以手动添加 # client.get_host_keys().add(hostname, 'ssh-rsa', server_public_key)
密钥认证
这是比密码认证更安全、更推荐的方式,尤其是在自动化脚本中。
import paramiko
private_key_path = "/path/to/your/private_key" # ~/.ssh/id_rsa
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
try:
# 使用密钥文件进行连接
# 如果你的密钥有密码,可以使用 passphrase 参数
private_key = paramiko.RSAKey.from_private_key_file(private_key_path)
client.connect(hostname, port=port, username=username, pkey=private_key)
print("密钥认证连接成功!")
# ...
except paramiko.AuthenticationException:
print("密钥认证失败")
except paramiko.PasswordRequiredException:
print("密钥文件受密码保护,请提供passphrase")
except paramiko.SSHException as e:
print(f"SSH连接失败: {e}")
finally:
client.close()
2 执行命令
这是最常见的用法。exec_command() 方法会在远程服务器上执行一个命令,并返回三个对象:stdin, stdout, stderr。

stdin: 标准输入流,通常用于向命令传递参数。stdout: 标准输出流,用于获取命令的执行结果。stderr: 标准错误流,用于获取命令的错误信息。
示例:
import paramiko
# ... (连接代码,假设 client 已连接)
try:
# 执行一个简单的命令
stdin, stdout, stderr = client.exec_command('ls -l /tmp')
# stdout.read() 会读取所有输出,返回 bytes 类型
# 需要解码成字符串
output = stdout.read().decode('utf-8')
error = stderr.read().decode('utf-8')
if error:
print(f"命令执行出错:\n{error}")
else:
print("命令执行结果:\n")
print(output)
# 检查命令的退出状态码 (0表示成功)
exit_status = stdout.channel.recv_exit_status()
print(f"\n命令退出状态码: {exit_status}")
finally:
client.close()
注意: exec_command 是一个阻塞调用,它会等待命令在远程服务器上完全执行完毕后才会返回,对于长时间运行的命令,你需要考虑超时设置。
# 设置超时
stdin, stdout, stderr = client.exec_command('long_running_command', timeout=10)
3 交互式 Shell
如果你需要执行需要交互的命令(如 sudo, vim, top 等),exec_command 就不够用了,这时你需要创建一个交互式的 Channel。
这比执行简单命令要复杂一些,因为你需要手动处理输入、输出和错误流的读取,以避免死锁。
示例:
import paramiko
import time
# ... (连接代码,假设 client 已连接)
channel = client.invoke_shell()
# 发送命令
channel.send("sudo ls /root\n") # 注意,命令要以换行符结尾
# 等待提示符出现 (需要根据实际情况调整)
# sudo 命令通常会提示输入密码
time.sleep(1)
if channel.recv_ready():
output = channel.recv(4096).decode('utf-8')
print(output)
# 输入 sudo 密码
channel.send("your_sudo_password\n")
time.sleep(1)
# 获取最终结果
if channel.recv_ready():
final_output = channel.recv(4096).decode('utf-8')
print("最终输出:\n", final_output)
# 关闭 channel
channel.close()
client.close()
更健壮的交互式 Shell 实现:
上面的例子很脆弱,因为它使用 time.sleep 来等待输出,一个更健壮的方法是使用循环来持续检查 channel 的状态,直到它关闭。
# 更健壮的交互式示例 (伪代码)
channel = client.invoke_shell()
channel.send("command\n")
while True:
if channel.recv_ready():
print(channel.recv(1024).decode('utf-8'), end='')
if channel.exit_status_ready():
break
time.sleep(0.1)
channel.close()
4 文件传输 (SFTP)
paramiko 通过 SFTPClient 类提供了强大的文件传输功能。
上传文件
import paramiko
# ... (连接代码,假设 client 已连接)
try:
# 从SSHClient获取SFTP客户端
sftp = client.open_sftp()
# 本地文件路径
local_path = "/path/to/local_file.txt"
# 远程文件路径
remote_path = "/path/to/remote_file.txt"
# 上传文件
print(f"正在上传 {local_path} 到 {remote_path}...")
sftp.put(local_path, remote_path)
print("上传完成!")
finally:
sftp.close()
client.close()
下载文件
# ... (连接代码,假设 client 已连接)
try:
sftp = client.open_sftp()
local_path = "/path/to/downloaded_file.txt"
remote_path = "/path/to/remote_file.txt"
print(f"正在下载 {remote_path} 到 {local_path}...")
sftp.get(remote_path, local_path)
print("下载完成!")
finally:
sftp.close()
client.close()
其他 SFTP 操作
SFTPClient 还提供了许多类似 os 模块的功能:
sftp = client.open_sftp()
# 列出目录
print(sftp.listdir('/tmp'))
# 创建目录
sftp.mkdir('/tmp/new_dir')
# 删除文件
sftp.remove('/tmp/old_file.txt')
# 重命名文件
sftp.rename('/tmp/file1.txt', '/tmp/file2.txt')
# 获取文件属性
stat = sftp.stat('/tmp/remote_file.txt')
print(f"文件大小: {stat.st_size} bytes")
print(f"文件修改时间: {time.ctime(stat.st_mtime)}")
sftp.close()
5 SSH 端口转发
Paramiko 也支持 SSH 的端口转发功能,包括本地转发、远程转发和动态转发(SOCKS 代理),这是一个非常高级的功能,但非常强大。
示例:本地转发
将本地机器的 8080 端口转发到远程服务器的 80 端口,这相当于 ssh -L 8080:localhost:80 user@remote_host。
import paramiko
import socket
# ... (连接代码,假设 client 已连接)
# transport 对象是端口转发的基础
transport = client.get_transport()
# 请求端口转发
# 参数: 本地端口, 远程主机, 远程端口
transport.request_port_forward('', 8080) # '' 表示绑定到所有接口
# 创建一个 socket 来监听本地 8080 端口
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_socket.bind(('', 8080))
server_socket.listen(1)
print("本地端口转发已建立: localhost:8080 -> remote_host:80")
print("等待本地连接...")
try:
while True:
# 等待本地客户端连接
local_client, addr = server_socket.accept()
print(f"收到来自 {addr} 的连接")
# 在 SSH 通道上创建一个端口转发
channel = transport.open_channel("direct-tcpip", ('localhost', 80), addr)
# 将本地客户端的数据转发到 SSH 通道,反之亦然
paramiko.util.channel.copy(local_client, channel)
finally:
server_socket.close()
client.close()
注意:端口转发的实现相对复杂,上面的例子是一个简化版,在实际应用中,你可能需要用多线程或 select 来处理数据流的转发。
高级主题
1 SSH Agent 和密钥认证
SSH Agent (如 ssh-agent) 是一个在后台运行的程序,用于缓存你的解密后的私钥,这样你就不需要在每次连接时都输入密钥的密码。
你可以让 Paramiko 连接到本地的 SSH Agent:
import paramiko
# 从环境变量或默认路径加载SSH Agent的socket
agent = paramiko.Agent()
# 获取Agent中所有可用的密钥
keys = agent.get_keys()
if keys:
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
try:
# 使用Agent中的第一个密钥进行连接
client.connect(hostname, username=username, pkey=keys[0])
print("通过SSH Agent连接成功!")
# ...
finally:
client.close()
else:
print("SSH Agent中没有找到可用的密钥。")
2 密钥处理
除了从文件加载,你还可以直接在内存中创建密钥对象。
from paramiko import RSAKey
# 生成一个新的RSA密钥对
key = RSAKey.generate(2048) # 2048是密钥长度
# 获取公钥字符串,可以添加到远程服务器的~/.ssh/authorized_keys文件中
public_key = key.get_name() + b' ' + key.asbytes()
print("公钥内容:\n", public_key.decode('utf-8'))
# 使用内存中的密钥对象进行连接
# client.connect(hostname, username='user', pkey=key)
3 日志调试
当连接或认证出现问题时,Paramiko 的日志非常有用,你可以使用 Python 的 logging 模块来获取底层的调试信息。
import logging
import paramiko
# 设置日志级别为DEBUG
paramiko.util.log_to_file('paramiko.log', level=logging.DEBUG)
# ... 你的连接和操作代码 ...
# 之后查看 paramiko.log 文件,会有非常详细的输出,包括握手过程、密钥交换等。
4 异常处理
Paramiko 提供了多种异常类,帮助你精确定位问题。
paramiko.AuthenticationException: 认证失败(用户名/密码错误,或密钥不被接受)。paramiko.SSHException: SSH 连接或协议层面的错误,例如服务器拒绝连接、主机密钥不匹配等。paramiko.BadHostKeyException: 服务器的主机密钥与known_hosts文件中的不匹配。paramiko.PasswordRequiredException: 需要提供密码才能加载私钥。socket.error: 底层的网络错误,如主机无法解析、连接超时等。
始终使用 try...except 块来捕获这些异常,使你的脚本更加健壮。
完整代码示例
下面是一个结合了密码认证、命令执行和文件上传的完整脚本。
import paramiko
import os
import sys
def remote_server_operation():
# --- 配置信息 ---
HOSTNAME = "your_server_ip"
PORT = 22
USERNAME = "your_username"
PASSWORD = "your_password"
REMOTE_DIR = "/tmp/upload_test"
LOCAL_FILE = "example.txt"
REMOTE_FILE = os.path.join(REMOTE_DIR, "uploaded_example.txt")
# 创建SSHClient实例
ssh_client = paramiko.SSHClient()
# --- 连接 ---
try:
# 设置自动添加主机密钥策略 (仅用于演示)
ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
print(f"正在连接到 {HOSTNAME}...")
ssh_client.connect(HOSTNAME, port=PORT, username=USERNAME, password=PASSWORD)
print("连接成功!")
# --- 执行命令 ---
print("\n--- 执行远程命令 ---")
command = f"mkdir -p {REMOTE_DIR} && echo 'Hello from Paramiko!' > {LOCAL_FILE}"
stdin, stdout, stderr = ssh_client.exec_command(command)
# 检查错误
error = stderr.read().decode()
if error:
print(f"命令执行出错: {error}")
else:
print("命令执行成功: '创建目录并生成本地示例文件'")
# 检查退出状态
exit_status = stdout.channel.recv_exit_status()
if exit_status == 0:
print(f"命令退出状态码: {exit_status} (成功)")
else:
print(f"命令退出状态码: {exit_status} (失败)")
return # 如果命令失败,则后续操作可能无意义
# --- 文件上传 ---
print("\n--- 执行文件上传 ---")
with ssh_client.open_sftp() as sftp:
print(f"正在上传本地文件 '{LOCAL_FILE}' 到远程 '{REMOTE_FILE}'...")
sftp.put(LOCAL_FILE, REMOTE_FILE)
print("文件上传成功!")
# 验证上传
print("\n--- 验证上传 ---")
stdin, stdout, stderr = ssh_client.exec_command(f"ls -l {REMOTE_FILE}")
output = stdout.read().decode()
error = stderr.read().decode()
if not error and output:
print("远程文件列表:")
print(output)
else:
print("验证失败,文件可能未正确上传。")
except paramiko.AuthenticationException:
print("错误: 认证失败,请检查用户名和密码。")
except paramiko.SSHException as e:
print(f"SSH错误: {e}")
except FileNotFoundError:
print(f"错误: 本地文件 '{LOCAL_FILE}' 不存在。")
except Exception as e:
print(f"发生未知错误: {e}")
finally:
# --- 关闭连接 ---
if ssh_client:
ssh_client.close()
print("\n连接已关闭。")
if __name__ == "__main__":
remote_server_operation()
总结与最佳实践
- 优先使用密钥认证: 密钥认证比密码认证更安全,也更适合自动化。
- 避免
AutoAddPolicy: 在生产环境中,始终使用load_system_host_keys()或手动验证主机密钥,以防止中间人攻击。 - 资源管理: 使用
try...finally或with语句来确保SSHClient和SFTPClient对象被正确关闭,防止资源泄漏。 - 处理输出:
stdout.read()返回的是bytes,通常需要用.decode('utf-8')转换成字符串。 - 检查退出状态码: 使用
stdout.channel.recv_exit_status()来检查命令是否成功执行,这是判断命令执行结果的标准方式。 - 利用日志: 遇到问题时,启用
paramiko.util.log_to_file是排查问题的利器。 - 异常处理: 对可能发生的异常(网络、认证、权限等)进行捕获和处理,让你的脚本更健壮。
- 考虑高级库: 对于非常复杂的场景,可以考虑使用封装了 Paramiko 的高级库,如
fabric或ansible,它们提供了更简洁的 API 和更丰富的功能集,但对于学习和轻量级任务,直接使用 Paramiko 是最佳选择。
