Paramiko 是一个纯 Python 实现的 SSHv2 协议库,提供了客户端和服务器功能,支持加密连接、远程命令执行、SFTP 文件传输等。

主要特性

  1. SSH 客户端功能:连接远程服务器执行命令
  2. SFTP 客户端/服务器:安全的文件传输
  3. SSH 服务器功能:创建 SSH 服务器
  4. 密钥认证支持:RSA、DSA、ECDSA、Ed25519
  5. 代理支持:通过代理连接
  6. 端口转发:本地和远程端口转发

安装

# 基础安装
pip install paramiko

# 包含所有依赖(包括加密库)
pip install paramiko[all]

# 安装特定版本
pip install paramiko==3.2.0

核心功能与示例

1. 基础 SSH 连接

密码认证连接
import paramiko
import socket
from typing import Optional, Tuple

def basic_ssh_connection():
    """基础SSH连接示例"""
    
    # SSH连接参数
    hostname = 'your_server.com'  # 替换为实际主机
    port = 22
    username = 'your_username'
    password = 'your_password'
    
    # 创建SSH客户端
    client = paramiko.SSHClient()
    
    try:
        # 自动添加主机密钥(生产环境应验证)
        client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        
        print(f"正在连接到 {hostname}:{port}...")
        
        # 建立连接
        client.connect(
            hostname=hostname,
            port=port,
            username=username,
            password=password,
            timeout=10
        )
        
        print("✓ 连接成功!")
        
        # 获取连接信息
        transport = client.get_transport()
        if transport:
            print(f"加密算法: {transport.get_cipher()}")
            print(f"MAC算法: {transport.get_mac()}")
            print(f"密钥交换算法: {transport.get_kex()}")
        
        return client
        
    except paramiko.AuthenticationException:
        print("✗ 认证失败: 用户名或密码错误")
    except paramiko.SSHException as e:
        print(f"✗ SSH连接失败: {e}")
    except socket.timeout:
        print("✗ 连接超时")
    except Exception as e:
        print(f"✗ 连接错误: {e}")
    
    return None

# 替代示例:本地测试
def local_test_connection():
    """本地测试连接(使用Docker或本地SSH服务器)"""
    
    # 注意:实际使用时需要真实的SSH服务器
    print("SSH连接示例需要真实的SSH服务器")
    print("以下示例展示代码结构,实际运行时需要修改连接参数")
    
    # 示例代码结构
    client = paramiko.SSHClient()
    client.set_missing_host_key_policy(paramiko.WarningPolicy())
    
    return client

# 运行示例
if __name__ == "__main__":
    # 由于需要真实服务器,这里只展示代码结构
    print("基础SSH连接示例:")
    print("=" * 60)
    # basic_ssh_connection()  # 需要真实服务器
    local_test_connection()
密钥认证连接
import paramiko
import os
from pathlib import Path

def key_based_authentication():
    """密钥认证连接示例"""
    
    # 连接参数
    hostname = 'your_server.com'
    port = 22
    username = 'your_username'
    
    # 密钥文件路径
    private_key_path = Path.home() / '.ssh' / 'id_rsa'
    
    client = paramiko.SSHClient()
    
    try:
        # 设置主机密钥策略
        client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        
        print(f"使用密钥认证连接到 {hostname}...")
        
        # 方法1: 直接使用密钥文件
        if private_key_path.exists():
            print(f"使用密钥文件: {private_key_path}")
            private_key = paramiko.RSAKey.from_private_key_file(str(private_key_path))
            
            client.connect(
                hostname=hostname,
                port=port,
                username=username,
                pkey=private_key,
                timeout=10
            )
        
        # 方法2: 使用密码保护的密钥
        elif private_key_path.with_suffix('.pub').exists():
            print("尝试使用带密码的密钥...")
            # 需要密码来解密密钥
            key_password = "your_key_password"  # 从安全的地方获取
            
            private_key = paramiko.RSAKey.from_private_key_file(
                str(private_key_path),
                password=key_password
            )
            
            client.connect(
                hostname=hostname,
                port=port,
                username=username,
                pkey=private_key
            )
        
        else:
            # 方法3: 使用SSH代理 (ssh-agent)
            print("尝试使用SSH代理...")
            
            # 连接到SSH代理
            agent = paramiko.Agent()
            agent_keys = agent.get_keys()
            
            if len(agent_keys) > 0:
                print(f"找到 {len(agent_keys)} 个代理密钥")
                
                # 尝试每个密钥
                for key in agent_keys:
                    try:
                        client.connect(
                            hostname=hostname,
                            port=port,
                            username=username,
                            pkey=key
                        )
                        print("✓ 使用代理密钥连接成功")
                        break
                    except paramiko.AuthenticationException:
                        continue
                else:
                    print("✗ 所有代理密钥都认证失败")
                    return None
            else:
                print("✗ 未找到SSH代理或代理中没有密钥")
                return None
        
        print("✓ 密钥认证成功!")
        
        # 显示连接信息
        transport = client.get_transport()
        if transport:
            remote_version = transport.remote_version
            print(f"远程SSH版本: {remote_version}")
            print(f"会话ID: {transport.get_hex_session_id()}")
        
        return client
        
    except paramiko.AuthenticationException:
        print("✗ 密钥认证失败")
    except FileNotFoundError:
        print(f"✗ 密钥文件未找到: {private_key_path}")
    except paramiko.SSHException as e:
        print(f"✗ SSH错误: {e}")
    except Exception as e:
        print(f"✗ 错误: {e}")
    
    return None

def generate_ssh_keypair():
    """生成SSH密钥对示例"""
    
    from paramiko.rsakey import RSAKey
    import io
    
    print("生成RSA密钥对...")
    
    try:
        # 生成2048位RSA密钥
        key = RSAKey.generate(bits=2048)
        
        # 获取私钥
        private_key_io = io.StringIO()
        key.write_private_key(private_key_io)
        private_key = private_key_io.getvalue()
        
        # 获取公钥
        public_key = f"{key.get_name()} {key.get_base64()}"
        
        print("✓ 密钥对生成成功!")
        print(f"密钥类型: {key.get_name()}")
        print(f"密钥长度: {key.get_bits()} 位")
        print(f"公钥指纹: {key.get_fingerprint().hex()}")
        
        # 保存到文件(示例)
        print("\n公钥 (添加到 ~/.ssh/authorized_keys):")
        print(public_key)
        
        print("\n私钥 (保存到安全位置):")
        # 注意:实际应用中不要打印私钥
        # print(private_key[:100] + "...")
        
        return key
        
    except Exception as e:
        print(f"✗ 生成密钥失败: {e}")
        return None

def test_multiple_auth_methods():
    """测试多种认证方法"""
    
    hostname = 'your_server.com'
    port = 22
    username = 'your_username'
    
    client = paramiko.SSHClient()
    client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    
    # 尝试多种认证方法
    auth_methods = []
    
    # 1. 密钥认证
    private_key_path = Path.home() / '.ssh' / 'id_rsa'
    if private_key_path.exists():
        try:
            key = paramiko.RSAKey.from_private_key_file(str(private_key_path))
            auth_methods.append({'pkey': key})
        except:
            pass
    
    # 2. 密码认证(最后尝试)
    # 从安全的地方获取密码
    password = "your_password"  # 示例,实际应从安全的地方获取
    auth_methods.append({'password': password})
    
    # 3. SSH代理
    try:
        agent = paramiko.Agent()
        agent_keys = agent.get_keys()
        for key in agent_keys:
            auth_methods.append({'pkey': key})
    except:
        pass
    
    print(f"尝试 {len(auth_methods)} 种认证方法...")
    
    for i, auth in enumerate(auth_methods, 1):
        method_name = list(auth.keys())[0]
        print(f"\n尝试方法 {i}: {method_name}")
        
        try:
            client.connect(
                hostname=hostname,
                port=port,
                username=username,
                **auth,
                timeout=5
            )
            print(f"✓ 认证成功! 使用方法: {method_name}")
            return client
            
        except paramiko.AuthenticationException:
            print(f"✗ 认证失败: {method_name}")
            continue
        except Exception as e:
            print(f"✗ 连接错误: {e}")
            continue
    
    print("\n✗ 所有认证方法都失败")
    return None

# 运行示例
if __name__ == "__main__":
    print("SSH密钥认证示例:")
    print("=" * 60)
    
    # 生成密钥对示例
    print("1. 生成SSH密钥对:")
    key = generate_ssh_keypair()
    
    print("\n" + "=" * 60)
    print("2. 密钥认证连接:")
    # key_based_authentication()  # 需要真实服务器
    
    print("\n" + "=" * 60)
    print("3. 多认证方法测试:")
    # test_multiple_auth_methods()  # 需要真实服务器

2. 远程命令执行

基本命令执行
import paramiko
import time
from typing import List, Tuple, Optional

class RemoteCommandExecutor:
    """远程命令执行器"""
    
    def __init__(self, hostname: str, username: str, 
                 password: str = None, pkey=None, port: int = 22):
        self.hostname = hostname
        self.port = port
        self.username = username
        self.password = password
        self.pkey = pkey
        self.client = None
        self.transport = None
        
    def connect(self) -> bool:
        """建立SSH连接"""
        try:
            self.client = paramiko.SSHClient()
            self.client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
            
            connect_kwargs = {
                'hostname': self.hostname,
                'port': self.port,
                'username': self.username,
                'timeout': 10
            }
            
            if self.password:
                connect_kwargs['password'] = self.password
            if self.pkey:
                connect_kwargs['pkey'] = self.pkey
            
            self.client.connect(**connect_kwargs)
            
            self.transport = self.client.get_transport()
            print(f"✓ 连接到 {self.hostname}:{self.port}")
            return True
            
        except Exception as e:
            print(f"✗ 连接失败: {e}")
            return False
    
    def execute_command(self, command: str, 
                       timeout: int = 30,
                       get_pty: bool = False) -> Tuple[int, str, str]:
        """
        执行远程命令
        
        Args:
            command: 要执行的命令
            timeout: 超时时间(秒)
            get_pty: 是否获取伪终端
            
        Returns:
            (退出码, 标准输出, 标准错误)
        """
        if not self.client:
            raise RuntimeError("未建立连接,请先调用connect()")
        
        stdin, stdout, stderr = None, None, None
        
        try:
            print(f"执行命令: {command}")
            
            # 执行命令
            stdin, stdout, stderr = self.client.exec_command(
                command,
                timeout=timeout,
                get_pty=get_pty
            )
            
            # 读取输出
            output = stdout.read().decode('utf-8', errors='ignore')
            error = stderr.read().decode('utf-8', errors='ignore')
            
            # 获取退出码
            exit_status = stdout.channel.recv_exit_status()
            
            return exit_status, output, error
            
        except socket.timeout:
            print(f"✗ 命令执行超时: {command}")
            return -1, "", "命令执行超时"
        except Exception as e:
            print(f"✗ 命令执行错误: {e}")
            return -1, "", str(e)
        finally:
            # 清理
            for stream in [stdin, stdout, stderr]:
                if stream:
                    stream.close()
    
    def execute_command_with_sudo(self, command: str, 
                                 sudo_password: str = None,
                                 timeout: int = 30) -> Tuple[int, str, str]:
        """
        使用sudo执行命令
        
        Args:
            command: 要执行的命令(不需要包含sudo)
            sudo_password: sudo密码
            timeout: 超时时间
            
        Returns:
            (退出码, 标准输出, 标准错误)
        """
        sudo_command = f"sudo -S {command}"
        
        stdin, stdout, stderr = None, None, None
        
        try:
            print(f"使用sudo执行: {command}")
            
            stdin, stdout, stderr = self.client.exec_command(
                sudo_command,
                timeout=timeout,
                get_pty=True
            )
            
            # 如果需要,提供sudo密码
            if sudo_password:
                stdin.write(sudo_password + '\n')
                stdin.flush()
            
            # 读取输出
            output = stdout.read().decode('utf-8', errors='ignore')
            error = stderr.read().decode('utf-8', errors='ignore')
            
            # 获取退出码
            exit_status = stdout.channel.recv_exit_status()
            
            return exit_status, output, error
            
        except Exception as e:
            print(f"✗ sudo命令执行错误: {e}")
            return -1, "", str(e)
        finally:
            for stream in [stdin, stdout, stderr]:
                if stream:
                    stream.close()
    
    def execute_interactive_command(self, command: str) -> Tuple[int, str]:
        """
        执行交互式命令
        
        Args:
            command: 要执行的命令
            
        Returns:
            (退出码, 输出)
        """
        print(f"执行交互式命令: {command}")
        
        # 创建交互式会话
        channel = self.transport.open_session()
        channel.get_pty()
        channel.exec_command(command)
        
        output = []
        
        try:
            while True:
                if channel.recv_ready():
                    data = channel.recv(1024).decode('utf-8', errors='ignore')
                    output.append(data)
                    print(data, end='')
                
                if channel.recv_stderr_ready():
                    data = channel.recv_stderr(1024).decode('utf-8', errors='ignore')
                    output.append(f"[STDERR] {data}")
                    print(f"[STDERR] {data}", end='')
                
                if channel.exit_status_ready():
                    break
                
                time.sleep(0.1)
                
        except Exception as e:
            print(f"✗ 交互命令错误: {e}")
        
        exit_status = channel.recv_exit_status()
        channel.close()
        
        return exit_status, ''.join(output)
    
    def execute_long_running_command(self, command: str,
                                   callback=None,
                                   timeout: int = 3600) -> Tuple[int, str]:
        """
        执行长时间运行的命令
        
        Args:
            command: 要执行的命令
            callback: 回调函数,用于处理实时输出
            timeout: 超时时间
            
        Returns:
            (退出码, 输出)
        """
        print(f"执行长时间命令: {command}")
        
        channel = self.transport.open_session()
        channel.settimeout(timeout)
        channel.exec_command(command)
        
        output = []
        
        try:
            while True:
                if channel.recv_ready():
                    data = channel.recv(1024).decode('utf-8', errors='ignore')
                    output.append(data)
                    
                    if callback:
                        callback(data)
                    else:
                        print(data, end='')
                
                if channel.exit_status_ready():
                    break
                
                # 检查通道是否关闭
                if channel.closed:
                    break
                
                time.sleep(0.1)
                
        except socket.timeout:
            print("✗ 命令执行超时")
            channel.close()
            return -1, ''.join(output)
        except Exception as e:
            print(f"✗ 命令执行错误: {e}")
            channel.close()
            return -1, ''.join(output)
        
        exit_status = channel.recv_exit_status()
        channel.close()
        
        return exit_status, ''.join(output)
    
    def get_system_info(self) -> dict:
        """获取系统信息"""
        info = {}
        
        # 系统信息
        exit_code, output, error = self.execute_command('uname -a')
        if exit_code == 0:
            info['uname'] = output.strip()
        
        # 内存信息
        exit_code, output, error = self.execute_command('free -h')
        if exit_code == 0:
            info['memory'] = output.strip()
        
        # 磁盘信息
        exit_code, output, error = self.execute_command('df -h')
        if exit_code == 0:
            info['disk'] = output.strip()
        
        # CPU信息
        exit_code, output, error = self.execute_command('lscpu')
        if exit_code == 0:
            info['cpu'] = output.strip()
        
        # 负载信息
        exit_code, output, error = self.execute_command('uptime')
        if exit_code == 0:
            info['uptime'] = output.strip()
        
        # 网络信息
        exit_code, output, error = self.execute_command('ip addr')
        if exit_code == 0:
            info['network'] = output.strip()
        
        return info
    
    def close(self):
        """关闭连接"""
        if self.client:
            self.client.close()
            print(f"✓ 关闭到 {self.hostname} 的连接")
            self.client = None
            self.transport = None
    
    def __enter__(self):
        """上下文管理器入口"""
        self.connect()
        return self
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        """上下文管理器出口"""
        self.close()

# 使用示例
def command_execution_examples():
    """命令执行示例"""
    
    # 创建执行器(需要真实服务器)
    executor = RemoteCommandExecutor(
        hostname='your_server.com',
        username='your_username',
        password='your_password'  # 或使用密钥
    )
    
    try:
        # 连接
        if not executor.connect():
            return
        
        print("\n1. 基本命令执行:")
        print("-" * 40)
        
        # 执行简单命令
        exit_code, output, error = executor.execute_command('ls -la')
        if exit_code == 0:
            print(f"输出:\n{output}")
        else:
            print(f"错误: {error}")
        
        print("\n2. 系统信息收集:")
        print("-" * 40)
        
        # 获取系统信息
        info = executor.get_system_info()
        for key, value in info.items():
            print(f"\n{key.upper()}:\n{value[:200]}...")
        
        print("\n3. 文件操作:")
        print("-" * 40)
        
        # 创建测试文件
        test_content = "Hello from Paramiko!\nThis is a test file."
        exit_code, output, error = executor.execute_command(
            f'echo "{test_content}" > /tmp/paramiko_test.txt'
        )
        
        # 读取文件
        exit_code, output, error = executor.execute_command(
            'cat /tmp/paramiko_test.txt'
        )
        if exit_code == 0:
            print(f"文件内容:\n{output}")
        
        # 检查文件信息
        exit_code, output, error = executor.execute_command(
            'ls -la /tmp/paramiko_test.txt'
        )
        if exit_code == 0:
            print(f"文件信息:\n{output}")
        
        print("\n4. 进程检查:")
        print("-" * 40)
        
        # 检查运行中的进程
        exit_code, output, error = executor.execute_command('ps aux | head -10')
        if exit_code == 0:
            print(f"前10个进程:\n{output}")
        
        print("\n5. 网络检查:")
        print("-" * 40)
        
        # 检查网络连接
        exit_code, output, error = executor.execute_command('netstat -tulpn | head -10')
        if exit_code == 0:
            print(f"网络连接:\n{output}")
        
        print("\n6. 服务状态:")
        print("-" * 40)
        
        # 检查服务状态(如果有systemd)
        exit_code, output, error = executor.execute_command(
            'systemctl list-units --type=service --state=running | head -5'
        )
        if exit_code == 0:
            print(f"运行中的服务:\n{output}")
        
        print("\n7. 使用sudo:")
        print("-" * 40)
        
        # 清理测试文件(需要sudo)
        sudo_password = input("输入sudo密码: ") if input("需要sudo密码? (y/n): ").lower() == 'y' else None
        
        if sudo_password:
            exit_code, output, error = executor.execute_command_with_sudo(
                'rm /tmp/paramiko_test.txt',
                sudo_password=sudo_password
            )
            if exit_code == 0:
                print("✓ 测试文件已删除")
            else:
                print(f"✗ 删除失败: {error}")
        
        print("\n8. 长时间运行命令监控:")
        print("-" * 40)
        
        # 定义回调函数处理实时输出
        def output_callback(data):
            print(f"[实时输出] {data}", end='')
        
        # 执行长时间命令
        exit_code, output = executor.execute_long_running_command(
            'for i in {1..5}; do echo "Iteration $i"; sleep 1; done',
            callback=output_callback
        )
        
        print(f"\n命令退出码: {exit_code}")
        
    finally:
        # 关闭连接
        executor.close()

# 使用上下文管理器
def context_manager_example():
    """上下文管理器使用示例"""
    
    with RemoteCommandExecutor(
        hostname='your_server.com',
        username='your_username',
        password='your_password'
    ) as executor:
        
        if executor.client:
            print("在上下文管理器中执行命令...")
            
            # 执行命令
            exit_code, output, error = executor.execute_command('hostname')
            if exit_code == 0:
                print(f"主机名: {output.strip()}")
            
            # 自动关闭连接
            print("连接将在退出上下文时自动关闭")

# 运行示例
if __name__ == "__main__":
    print("远程命令执行示例:")
    print("=" * 60)
    
    # 由于需要真实服务器,这里只展示代码结构
    print("注意: 需要真实SSH服务器来运行这些示例")
    print("取消注释以下代码并提供正确的连接信息:")
    print("\n# command_execution_examples()")
    print("# context_manager_example()")
交互式Shell
import paramiko
import select
import sys
import threading
import time
from typing import Optional, Callable

class InteractiveSSHShell:
    """交互式SSH Shell"""
    
    def __init__(self, hostname: str, username: str, 
                 password: str = None, pkey=None, port: int = 22):
        self.hostname = hostname
        self.port = port
        self.username = username
        self.password = password
        self.pkey = pkey
        
        self.client = None
        self.channel = None
        self.is_connected = False
        
    def connect(self) -> bool:
        """建立连接并创建交互式Shell"""
        try:
            self.client = paramiko.SSHClient()
            self.client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
            
            connect_kwargs = {
                'hostname': self.hostname,
                'port': self.port,
                'username': self.username,
                'timeout': 10
            }
            
            if self.password:
                connect_kwargs['password'] = self.password
            if self.pkey:
                connect_kwargs['pkey'] = self.pkey
            
            print(f"连接到 {self.hostname}:{self.port}...")
            self.client.connect(**connect_kwargs)
            
            # 创建交互式Shell通道
            self.channel = self.client.invoke_shell()
            
            # 设置终端类型和大小
            self.channel.get_pty(
                term='xterm',
                width=80,
                height=24
            )
            
            # 激活交互模式
            self.channel.invoke_shell()
            
            # 等待欢迎信息
            time.sleep(1)
            self._read_initial_output()
            
            self.is_connected = True
            print("✓ 交互式Shell已就绪")
            return True
            
        except Exception as e:
            print(f"✗ 连接失败: {e}")
            return False
    
    def _read_initial_output(self):
        """读取初始输出"""
        if self.channel.recv_ready():
            initial_output = self.channel.recv(4096).decode('utf-8', errors='ignore')
            print(initial_output, end='')
    
    def start_interactive_session(self, 
                                 local_echo: bool = True,
                                 timeout: float = 0.1):
        """
        启动交互式会话
        
        Args:
            local_echo: 是否本地回显输入
            timeout: select超时时间
        """
        if not self.is_connected:
            print("未连接,请先调用connect()")
            return
        
        print("\n" + "="*60)
        print("交互式SSH Shell")
        print("输入 'exit' 或 Ctrl+D 退出")
        print("="*60 + "\n")
        
        try:
            while True:
                # 检查是否有数据可读
                rlist, _, _ = select.select([self.channel, sys.stdin], [], [], timeout)
                
                for r in rlist:
                    if r is self.channel:
                        # 有来自远程的数据
                        if self.channel.recv_ready():
                            data = self.channel.recv(4096).decode('utf-8', errors='ignore')
                            if data:
                                sys.stdout.write(data)
                                sys.stdout.flush()
                    
                    elif r is sys.stdin:
                        # 本地输入
                        local_input = sys.stdin.readline()
                        
                        if not local_input:  # Ctrl+D
                            print("\n检测到EOF,退出...")
                            return
                        
                        command = local_input.strip()
                        
                        # 检查退出命令
                        if command.lower() in ['exit', 'quit', 'logout']:
                            print("退出交互式Shell...")
                            return
                        
                        # 发送命令到远程
                        if local_echo:
                            # 如果本地已经回显了输入,就不需要发送了
                            # 但有时需要确保命令被正确发送
                            pass
                        
                        # 发送命令(需要加上换行符)
                        self.channel.send(command + '\n')
                        
                        # 小延迟,让远程有时间处理
                        time.sleep(0.1)
                
                # 检查通道是否关闭
                if self.channel.closed:
                    print("\n远程连接已关闭")
                    break
                
        except KeyboardInterrupt:
            print("\n\n检测到Ctrl+C,退出...")
        except Exception as e:
            print(f"\n会话错误: {e}")
        finally:
            print("交互式会话结束")
    
    def execute_commands_interactively(self, commands: list):
        """以交互方式执行一系列命令"""
        if not self.is_connected:
            self.connect()
            if not self.is_connected:
                return
        
        print(f"执行 {len(commands)} 个命令...")
        
        for i, cmd in enumerate(commands, 1):
            print(f"\n[{i}/{len(commands)}] 执行: {cmd}")
            
            # 发送命令
            self.channel.send(cmd + '\n')
            
            # 等待并读取输出
            output = self._read_command_output(timeout=5)
            print(f"输出:\n{output}")
            
            # 检查是否有错误
            if "command not found" in output.lower():
                print(f"警告: 命令可能不存在: {cmd}")
            
            # 命令间的小延迟
            time.sleep(0.5)
    
    def _read_command_output(self, timeout: float = 2.0) -> str:
        """读取命令输出"""
        output = []
        start_time = time.time()
        
        while time.time() - start_time < timeout:
            if self.channel.recv_ready():
                data = self.channel.recv(4096).decode('utf-8', errors='ignore')
                output.append(data)
            
            # 检查是否还有更多数据
            time.sleep(0.1)
            
            # 如果一段时间没有新数据,认为输出结束
            if not self.channel.recv_ready():
                # 再等待一小会儿确认
                time.sleep(0.2)
                if not self.channel.recv_ready():
                    break
        
        return ''.join(output)
    
    def upload_and_execute_script(self, script_content: str, 
                                 interpreter: str = "bash"):
        """上传并执行脚本"""
        if not self.is_connected:
            self.connect()
            if not self.is_connected:
                return
        
        # 创建临时脚本文件
        temp_script = f"/tmp/script_{int(time.time())}.sh"
        
        # 发送创建脚本的命令
        self.channel.send(f"cat > {temp_script} << 'EOF'\n")
        time.sleep(0.1)
        
        # 发送脚本内容
        self.channel.send(script_content + "\nEOF\n")
        time.sleep(0.1)
        
        # 设置执行权限
        self.channel.send(f"chmod +x {temp_script}\n")
        time.sleep(0.1)
        
        # 执行脚本
        self.channel.send(f"{interpreter} {temp_script}\n")
        
        # 读取输出
        output = self._read_command_output(timeout=10)
        
        # 清理脚本文件
        self.channel.send(f"rm -f {temp_script}\n")
        
        return output
    
    def start_background_monitor(self, callback: Callable[[str], None],
                                interval: float = 1.0):
        """启动后台监控线程"""
        def monitor_thread():
            while self.is_connected and not self.channel.closed:
                if self.channel.recv_ready():
                    data = self.channel.recv(4096).decode('utf-8', errors='ignore')
                    if data and callback:
                        callback(data)
                time.sleep(interval)
        
        thread = threading.Thread(target=monitor_thread, daemon=True)
        thread.start()
        return thread
    
    def send_control_sequence(self, control_char: str):
        """发送控制序列"""
        control_sequences = {
            'ctrl_c': '\x03',
            'ctrl_d': '\x04',
            'ctrl_z': '\x1a',
            'tab': '\t',
            'enter': '\n',
            'backspace': '\x7f',
            'esc': '\x1b',
        }
        
        if control_char in control_sequences:
            self.channel.send(control_sequences[control_char])
            print(f"发送控制序列: {control_char}")
    
    def resize_terminal(self, width: int, height: int):
        """调整终端大小"""
        if self.channel:
            self.channel.resize_pty(width=width, height=height)
            print(f"终端大小调整为: {width}x{height}")
    
    def close(self):
        """关闭连接"""
        if self.channel:
            self.channel.close()
        if self.client:
            self.client.close()
        self.is_connected = False
        print("连接已关闭")
    
    def __enter__(self):
        self.connect()
        return self
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        self.close()

# 使用示例
def interactive_shell_example():
    """交互式Shell示例"""
    
    # 创建Shell实例
    shell = InteractiveSSHShell(
        hostname='your_server.com',
        username='your_username',
        password='your_password'
    )
    
    try:
        # 连接
        if not shell.connect():
            print("连接失败")
            return
        
        print("\n选择模式:")
        print("1. 完全交互模式")
        print("2. 执行预定义命令")
        print("3. 上传并执行脚本")
        
        choice = input("\n请选择 (1-3): ").strip()
        
        if choice == '1':
            # 完全交互模式
            shell.start_interactive_session()
            
        elif choice == '2':
            # 执行预定义命令
            commands = [
                'pwd',
                'whoami',
                'ls -la',
                'df -h',
                'free -h',
                'uptime',
                'echo "命令执行完成"'
            ]
            
            shell.execute_commands_interactively(commands)
            
        elif choice == '3':
            # 上传并执行脚本
            script_content = """#!/bin/bash
echo "=== 系统信息 ==="
echo "主机名: $(hostname)"
echo "用户: $(whoami)"
echo "日期: $(date)"
echo ""
echo "=== 磁盘使用 ==="
df -h | grep -v tmpfs
echo ""
echo "=== 内存使用 ==="
free -h
echo ""
echo "脚本执行完成!"
"""
            
            print("脚本内容:")
            print(script_content)
            print("\n执行脚本...")
            
            output = shell.upload_and_execute_script(script_content)
            print(f"脚本输出:\n{output}")
        
        else:
            print("无效选择")
            
    except KeyboardInterrupt:
        print("\n用户中断")
    except Exception as e:
        print(f"错误: {e}")
    finally:
        shell.close()

# 高级示例:带颜色和格式的交互式终端
class EnhancedInteractiveShell(InteractiveSSHShell):
    """增强的交互式Shell"""
    
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.command_history = []
        self.current_dir = None
        self.prompt_pattern = None
    
    def start_enhanced_session(self):
        """启动增强的交互会话"""
        if not self.is_connected:
            if not self.connect():
                return
        
        print("\n" + "="*60)
        print("增强交互式SSH Shell")
        print("功能:")
        print("  - 命令历史 (上下箭头)")
        print("  - 自动补全 (Tab)")
        print("  - 当前目录显示")
        print("  - 语法高亮")
        print("="*60 + "\n")
        
        # 启动后台线程监控输出
        import threading
        
        def output_monitor():
            while self.is_connected and self.channel and not self.channel.closed:
                if self.channel.recv_ready():
                    data = self.channel.recv(4096).decode('utf-8', errors='ignore')
                    # 在这里可以添加输出处理逻辑
                    sys.stdout.write(data)
                    sys.stdout.flush()
                    
                    # 尝试提取当前目录
                    self._extract_current_dir(data)
                
                time.sleep(0.01)
        
        monitor_thread = threading.Thread(target=output_monitor, daemon=True)
        monitor_thread.start()
        
        # 简单的前端循环
        try:
            while self.is_connected and self.channel and not self.channel.closed:
                # 显示自定义提示符
                prompt = self._get_prompt()
                command = input(prompt)
                
                if not command.strip():
                    continue
                
                # 添加到历史
                self.command_history.append(command)
                
                # 处理特殊命令
                if command.lower() in ['exit', 'quit']:
                    break
                elif command.lower() == 'history':
                    self._show_history()
                    continue
                elif command.lower() == 'clear':
                    print("\033[H\033[J", end='')  # 清屏
                    continue
                
                # 发送命令
                self.channel.send(command + '\n')
                
                # 等待命令执行
                time.sleep(0.1)
                
        except KeyboardInterrupt:
            print("\n\n退出...")
        except EOFError:
            print("\n\n检测到EOF,退出...")
        finally:
            print("会话结束")
    
    def _extract_current_dir(self, output: str):
        """从输出中提取当前目录"""
        # 查找常见的提示符模式
        import re
        
        patterns = [
            r'\[?([^\s@]+@[^\s]+)[:\s]([^\s>#]+)[>#]',  # user@host:dir>
            r'([^\s>#]+)[>#]\s*$'  # dir>
        ]
        
        for pattern in patterns:
            match = re.search(pattern, output)
            if match:
                if len(match.groups()) > 1:
                    self.current_dir = match.group(2)
                else:
                    self.current_dir = match.group(1)
                break
    
    def _get_prompt(self) -> str:
        """获取提示符"""
        if self.current_dir:
            # 显示当前目录
            dir_name = self.current_dir.split('/')[-1] if '/' in self.current_dir else self.current_dir
            return f"\033[32m{dir_name}\033[0m $ "
        else:
            return "\033[36mssh>\033[0m "
    
    def _show_history(self):
        """显示命令历史"""
        print("\n命令历史:")
        for i, cmd in enumerate(self.command_history[-10:], 1):
            print(f"  {i:2}. {cmd}")

# 运行示例
if __name__ == "__main__":
    print("交互式SSH Shell示例:")
    print("=" * 60)
    
    print("注意: 需要真实SSH服务器来运行这些示例")
    print("提供正确的连接信息后取消注释以下代码:")
    print("\n# interactive_shell_example()")
    
    # 模拟演示
    print("\n模拟演示代码结构:")
    print("-" * 40)
    
    # 创建模拟Shell类
    class MockShell:
        def __init__(self):
            print("初始化SSH Shell...")
        
        def connect(self):
            print("模拟连接服务器...")
            return True
        
        def start_interactive_session(self):
            print("模拟交互式会话:")
            print("  输入 'ls -la' 查看文件")
            print("  输入 'pwd' 查看当前目录")
            print("  输入 'exit' 退出")
        
        def close(self):
            print("关闭连接")
    
    # 演示用法
    shell = MockShell()
    if shell.connect():
        shell.start_interactive_session()
        shell.close()

3. SFTP 文件传输

基础文件传输
import paramiko
import os
from pathlib import Path
import stat
import time
from typing import Optional, List, Tuple
import hashlib

class SFTPClient:
    """SFTP客户端"""
    
    def __init__(self, hostname: str, username: str, 
                 password: str = None, pkey=None, port: int = 22):
        self.hostname = hostname
        self.port = port
        self.username = username
        self.password = password
        self.pkey = pkey
        
        self.ssh_client = None
        self.sftp_client = None
        self.is_connected = False
    
    def connect(self) -> bool:
        """建立SFTP连接"""
        try:
            self.ssh_client = paramiko.SSHClient()
            self.ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
            
            connect_kwargs = {
                'hostname': self.hostname,
                'port': self.port,
                'username': self.username,
                'timeout': 10
            }
            
            if self.password:
                connect_kwargs['password'] = self.password
            if self.pkey:
                connect_kwargs['pkey'] = self.pkey
            
            print(f"连接到 {self.hostname}:{self.port}...")
            self.ssh_client.connect(**connect_kwargs)
            
            # 创建SFTP客户端
            self.sftp_client = self.ssh_client.open_sftp()
            
            self.is_connected = True
            print("✓ SFTP连接成功")
            return True
            
        except Exception as e:
            print(f"✗ 连接失败: {e}")
            return False
    
    def upload_file(self, local_path: str, remote_path: str,
                   callback=None, confirm: bool = True) -> bool:
        """
        上传文件到远程服务器
        
        Args:
            local_path: 本地文件路径
            remote_path: 远程文件路径
            callback: 进度回调函数
            confirm: 是否确认覆盖
            
        Returns:
            是否成功
        """
        if not self.is_connected:
            print("未连接")
            return False
        
        local_path = Path(local_path)
        
        if not local_path.exists():
            print(f"✗ 本地文件不存在: {local_path}")
            return False
        
        if not local_path.is_file():
            print(f"✗ 不是文件: {local_path}")
            return False
        
        try:
            # 检查远程文件是否存在
            remote_exists = False
            try:
                self.sftp_client.stat(remote_path)
                remote_exists = True
            except IOError:
                pass
            
            if remote_exists and confirm:
                # 确认是否覆盖
                response = input(f"远程文件已存在: {remote_path}\n是否覆盖? (y/n): ")
                if response.lower() != 'y':
                    print("取消上传")
                    return False
            
            # 获取文件大小用于进度显示
            file_size = local_path.stat().st_size
            print(f"上传文件: {local_path} -> {remote_path}")
            print(f"文件大小: {file_size:,} 字节")
            
            # 上传文件
            if callback:
                # 使用带进度回调的上传
                self._upload_with_progress(str(local_path), remote_path, 
                                         callback, file_size)
            else:
                # 简单上传
                self.sftp_client.put(str(local_path), remote_path)
            
            print(f"✓ 上传成功: {remote_path}")
            return True
            
        except Exception as e:
            print(f"✗ 上传失败: {e}")
            return False
    
    def _upload_with_progress(self, local_path: str, remote_path: str,
                            callback, total_size: int):
        """带进度显示的上传"""
        # 创建自定义文件对象来跟踪进度
        class ProgressFile:
            def __init__(self, filepath, callback, total):
                self.file = open(filepath, 'rb')
                self.callback = callback
                self.total = total
                self.transferred = 0
                self.start_time = time.time()
            
            def read(self, size):
                data = self.file.read(size)
                if data:
                    self.transferred += len(data)
                    if self.callback:
                        self.callback(self.transferred, self.total)
                return data
            
            def close(self):
                self.file.close()
        
        progress_file = ProgressFile(local_path, callback, total_size)
        
        try:
            self.sftp_client.putfo(progress_file, remote_path)
        finally:
            progress_file.close()
    
    def download_file(self, remote_path: str, local_path: str,
                     callback=None, confirm: bool = True) -> bool:
        """
        从远程服务器下载文件
        
        Args:
            remote_path: 远程文件路径
            local_path: 本地文件路径
            callback: 进度回调函数
            confirm: 是否确认覆盖
            
        Returns:
            是否成功
        """
        if not self.is_connected:
            print("未连接")
            return False
        
        local_path = Path(local_path)
        
        try:
            # 检查远程文件是否存在
            remote_stat = self.sftp_client.stat(remote_path)
            if not stat.S_ISREG(remote_stat.st_mode):
                print(f"✗ 不是文件: {remote_path}")
                return False
            
            # 检查本地文件是否存在
            if local_path.exists() and confirm:
                response = input(f"本地文件已存在: {local_path}\n是否覆盖? (y/n): ")
                if response.lower() != 'y':
                    print("取消下载")
                    return False
            
            # 确保本地目录存在
            local_path.parent.mkdir(parents=True, exist_ok=True)
            
            print(f"下载文件: {remote_path} -> {local_path}")
            print(f"文件大小: {remote_stat.st_size:,} 字节")
            
            # 下载文件
            if callback:
                self._download_with_progress(remote_path, str(local_path),
                                           callback, remote_stat.st_size)
            else:
                self.sftp_client.get(remote_path, str(local_path))
            
            print(f"✓ 下载成功: {local_path}")
            return True
            
        except FileNotFoundError:
            print(f"✗ 远程文件不存在: {remote_path}")
            return False
        except Exception as e:
            print(f"✗ 下载失败: {e}")
            return False
    
    def _download_with_progress(self, remote_path: str, local_path: str,
                              callback, total_size: int):
        """带进度显示的下载"""
        # 创建自定义文件对象来跟踪进度
        class ProgressFile:
            def __init__(self, filepath, callback, total):
                self.file = open(filepath, 'wb')
                self.callback = callback
                self.total = total
                self.transferred = 0
                self.start_time = time.time()
            
            def write(self, data):
                self.file.write(data)
                self.transferred += len(data)
                if self.callback:
                    self.callback(self.transferred, self.total)
            
            def close(self):
                self.file.close()
        
        progress_file = ProgressFile(local_path, callback, total_size)
        
        try:
            self.sftp_client.getfo(remote_path, progress_file)
        finally:
            progress_file.close()
    
    def list_directory(self, remote_path: str = '.', 
                      detailed: bool = False) -> List[dict]:
        """
        列出远程目录内容
        
        Args:
            remote_path: 远程目录路径
            detailed: 是否显示详细信息
            
        Returns:
            目录内容列表
        """
        if not self.is_connected:
            print("未连接")
            return []
        
        try:
            items = []
            
            for item in self.sftp_client.listdir_attr(remote_path):
                item_info = {
                    'name': item.filename,
                    'size': item.st_size,
                    'uid': item.st_uid,
                    'gid': item.st_gid,
                    'mode': item.st_mode,
                    'atime': item.st_atime,
                    'mtime': item.st_mtime,
                    'is_dir': stat.S_ISDIR(item.st_mode),
                    'is_file': stat.S_ISREG(item.st_mode),
                    'is_link': stat.S_ISLNK(item.st_mode)
                }
                
                # 权限字符串
                mode = item.st_mode
                permissions = ''
                
                # 文件类型
                if stat.S_ISDIR(mode):
                    permissions += 'd'
                elif stat.S_ISLNK(mode):
                    permissions += 'l'
                else:
                    permissions += '-'
                
                # 用户权限
                permissions += 'r' if mode & stat.S_IRUSR else '-'
                permissions += 'w' if mode & stat.S_IWUSR else '-'
                permissions += 'x' if mode & stat.S_IXUSR else '-'
                
                # 组权限
                permissions += 'r' if mode & stat.S_IRGRP else '-'
                permissions += 'w' if mode & stat.S_IWGRP else '-'
                permissions += 'x' if mode & stat.S_IXGRP else '-'
                
                # 其他权限
                permissions += 'r' if mode & stat.S_IROTH else '-'
                permissions += 'w' if mode & stat.S_IWOTH else '-'
                permissions += 'x' if mode & stat.S_IXOTH else '-'
                
                item_info['permissions'] = permissions
                
                items.append(item_info)
            
            return items
            
        except Exception as e:
            print(f"✗ 列出目录失败: {e}")
            return []
    
    def create_directory(self, remote_path: str, 
                        parents: bool = True) -> bool:
        """
        创建远程目录
        
        Args:
            remote_path: 远程目录路径
            parents: 是否创建父目录
            
        Returns:
            是否成功
        """
        if not self.is_connected:
            print("未连接")
            return False
        
        try:
            if parents:
                # 递归创建目录
                parts = remote_path.strip('/').split('/')
                current_path = ''
                
                for part in parts:
                    current_path = f"{current_path}/{part}" if current_path else part
                    try:
                        self.sftp_client.stat(current_path)
                    except FileNotFoundError:
                        self.sftp_client.mkdir(current_path)
            else:
                # 直接创建目录
                self.sftp_client.mkdir(remote_path)
            
            print(f"✓ 创建目录: {remote_path}")
            return True
            
        except Exception as e:
            print(f"✗ 创建目录失败: {e}")
            return False
    
    def delete_file(self, remote_path: str) -> bool:
        """删除远程文件"""
        if not self.is_connected:
            print("未连接")
            return False
        
        try:
            self.sftp_client.remove(remote_path)
            print(f"✓ 删除文件: {remote_path}")
            return True
        except Exception as e:
            print(f"✗ 删除文件失败: {e}")
            return False
    
    def delete_directory(self, remote_path: str, 
                        recursive: bool = True) -> bool:
        """
        删除远程目录
        
        Args:
            remote_path: 远程目录路径
            recursive: 是否递归删除
            
        Returns:
            是否成功
        """
        if not self.is_connected:
            print("未连接")
            return False
        
        try:
            if recursive:
                # 递归删除目录内容
                self._delete_directory_recursive(remote_path)
            else:
                # 只删除空目录
                self.sftp_client.rmdir(remote_path)
            
            print(f"✓ 删除目录: {remote_path}")
            return True
            
        except Exception as e:
            print(f"✗ 删除目录失败: {e}")
            return False
    
    def _delete_directory_recursive(self, remote_path: str):
        """递归删除目录"""
        for item in self.sftp_client.listdir_attr(remote_path):
            item_path = f"{remote_path}/{item.filename}"
            
            if stat.S_ISDIR(item.st_mode):
                # 递归删除子目录
                self._delete_directory_recursive(item_path)
            else:
                # 删除文件
                self.sftp_client.remove(item_path)
        
        # 删除空目录
        self.sftp_client.rmdir(remote_path)
    
    def rename(self, old_path: str, new_path: str) -> bool:
        """重命名文件或目录"""
        if not self.is_connected:
            print("未连接")
            return False
        
        try:
            self.sftp_client.rename(old_path, new_path)
            print(f"✓ 重命名: {old_path} -> {new_path}")
            return True
        except Exception as e:
            print(f"✗ 重命名失败: {e}")
            return False
    
    def get_file_info(self, remote_path: str) -> Optional[dict]:
        """获取文件信息"""
        if not self.is_connected:
            print("未连接")
            return None
        
        try:
            stat_result = self.sftp_client.stat(remote_path)
            
            info = {
                'path': remote_path,
                'size': stat_result.st_size,
                'uid': stat_result.st_uid,
                'gid': stat_result.st_gid,
                'mode': stat_result.st_mode,
                'atime': stat_result.st_atime,
                'mtime': stat_result.st_mtime,
                'ctime': stat_result.st_ctime if hasattr(stat_result, 'st_ctime') else None,
                'is_dir': stat.S_ISDIR(stat_result.st_mode),
                'is_file': stat.S_ISREG(stat_result.st_mode),
                'is_link': stat.S_ISLNK(stat_result.st_mode)
            }
            
            return info
            
        except Exception as e:
            print(f"✗ 获取文件信息失败: {e}")
            return None
    
    def calculate_md5(self, remote_path: str) -> Optional[str]:
        """计算远程文件的MD5哈希"""
        if not self.is_connected:
            print("未连接")
            return None
        
        try:
            # 创建临时本地文件
            import tempfile
            import hashlib
            
            with tempfile.NamedTemporaryFile(delete=False) as temp_file:
                temp_path = temp_file.name
            
            try:
                # 下载文件
                self.sftp_client.get(remote_path, temp_path)
                
                # 计算MD5
                md5_hash = hashlib.md5()
                with open(temp_path, 'rb') as f:
                    for chunk in iter(lambda: f.read(4096), b''):
                        md5_hash.update(chunk)
                
                return md5_hash.hexdigest()
                
            finally:
                # 清理临时文件
                if os.path.exists(temp_path):
                    os.unlink(temp_path)
                    
        except Exception as e:
            print(f"✗ 计算MD5失败: {e}")
            return None
    
    def upload_directory(self, local_dir: str, remote_dir: str,
                        callback=None) -> Tuple[int, int]:
        """
        上传整个目录
        
        Args:
            local_dir: 本地目录
            remote_dir: 远程目录
            callback: 进度回调函数
            
        Returns:
            (成功数, 总数)
        """
        local_dir = Path(local_dir)
        
        if not local_dir.exists() or not local_dir.is_dir():
            print(f"✗ 本地目录不存在: {local_dir}")
            return 0, 0
        
        # 创建远程目录
        self.create_directory(remote_dir, parents=True)
        
        total_files = 0
        success_count = 0
        
        # 遍历本地目录
        for root, dirs, files in os.walk(local_dir):
            # 计算相对路径
            rel_path = Path(root).relative_to(local_dir)
            remote_path = f"{remote_dir}/{rel_path}" if str(rel_path) != '.' else remote_dir
            
            # 确保远程目录存在
            self.create_directory(remote_path, parents=True)
            
            # 上传文件
            for file in files:
                total_files += 1
                local_file = Path(root) / file
                remote_file = f"{remote_path}/{file}"
                
                print(f"上传 [{total_files}]: {local_file} -> {remote_file}")
                
                if self.upload_file(str(local_file), remote_file, callback=callback):
                    success_count += 1
        
        print(f"✓ 目录上传完成: {success_count}/{total_files} 个文件")
        return success_count, total_files
    
    def download_directory(self, remote_dir: str, local_dir: str,
                          callback=None) -> Tuple[int, int]:
        """
        下载整个目录
        
        Args:
            remote_dir: 远程目录
            local_dir: 本地目录
            callback: 进度回调函数
            
        Returns:
            (成功数, 总数)
        """
        local_dir = Path(local_dir)
        
        # 创建本地目录
        local_dir.mkdir(parents=True, exist_ok=True)
        
        total_files = 0
        success_count = 0
        
        def _download_recursive(remote_path, local_path):
            nonlocal total_files, success_count
            
            try:
                items = self.sftp_client.listdir_attr(remote_path)
                
                for item in items:
                    item_remote = f"{remote_path}/{item.filename}"
                    item_local = local_path / item.filename
                    
                    if stat.S_ISDIR(item.st_mode):
                        # 递归下载子目录
                        item_local.mkdir(exist_ok=True)
                        _download_recursive(item_remote, item_local)
                    elif stat.S_ISREG(item.st_mode):
                        # 下载文件
                        total_files += 1
                        print(f"下载 [{total_files}]: {item_remote} -> {item_local}")
                        
                        if self.download_file(item_remote, str(item_local), callback=callback):
                            success_count += 1
                            
            except Exception as e:
                print(f"下载目录时出错 {remote_path}: {e}")
        
        _download_recursive(remote_dir, local_dir)
        
        print(f"✓ 目录下载完成: {success_count}/{total_files} 个文件")
        return success_count, total_files
    
    def sync_directory(self, local_dir: str, remote_dir: str,
                      direction: str = 'both') -> dict:
        """
        同步目录
        
        Args:
            local_dir: 本地目录
            remote_dir: 远程目录
            direction: 同步方向 ('up', 'down', 'both')
            
        Returns:
            同步统计
        """
        stats = {
            'uploaded': 0,
            'downloaded': 0,
            'skipped': 0,
            'errors': 0
        }
        
        # 这里可以实现更复杂的同步逻辑
        # 包括比较文件修改时间、大小等
        
        print("目录同步功能需要更复杂的实现")
        print("可以考虑使用rsync或实现自定义同步逻辑")
        
        return stats
    
    def close(self):
        """关闭连接"""
        if self.sftp_client:
            self.sftp_client.close()
        if self.ssh_client:
            self.ssh_client.close()
        
        self.is_connected = False
        print("✓ SFTP连接已关闭")
    
    def __enter__(self):
        self.connect()
        return self
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        self.close()

# 使用示例
def sftp_examples():
    """SFTP使用示例"""
    
    # 创建SFTP客户端
    sftp = SFTPClient(
        hostname='your_server.com',
        username='your_username',
        password='your_password'
    )
    
    try:
        # 连接
        if not sftp.connect():
            return
        
        print("\n1. 列出目录内容:")
        print("-" * 40)
        
        # 列出当前目录
        items = sftp.list_directory('.', detailed=True)
        for item in items[:10]:  # 显示前10个
            size_str = f"{item['size']:,}" if item['is_file'] else "DIR"
            print(f"{item['permissions']} {size_str:>10} {item['name']}")
        
        print("\n2. 创建目录:")
        print("-" * 40)
        
        # 创建测试目录
        test_dir = '/tmp/paramiko_test'
        if sftp.create_directory(test_dir):
            print(f"创建目录: {test_dir}")
        
        print("\n3. 上传文件:")
        print("-" * 40)
        
        # 创建本地测试文件
        local_test_file = 'test_upload.txt'
        with open(local_test_file, 'w') as f:
            f.write("This is a test file for SFTP upload.\n")
            f.write("Created by Paramiko SFTP client.\n")
        
        # 定义进度回调函数
        def upload_progress(transferred, total):
            percent = (transferred / total) * 100
            print(f"\r进度: {transferred:,}/{total:,} 字节 ({percent:.1f}%)", end='')
        
        # 上传文件
        remote_test_file = f'{test_dir}/uploaded_file.txt'
        if sftp.upload_file(local_test_file, remote_test_file, 
                           callback=upload_progress):
            print("\n上传完成!")
        
        print("\n4. 下载文件:")
        print("-" * 40)
        
        # 定义下载进度回调
        def download_progress(transferred, total):
            percent = (transferred / total) * 100
            print(f"\r进度: {transferred:,}/{total:,} 字节 ({percent:.1f}%)", end='')
        
        # 下载文件
        local_downloaded = 'downloaded_file.txt'
        if sftp.download_file(remote_test_file, local_downloaded,
                            callback=download_progress):
            print("\n下载完成!")
            
            # 显示下载的文件内容
            with open(local_downloaded, 'r') as f:
                print(f"文件内容:\n{f.read()}")
        
        print("\n5. 获取文件信息:")
        print("-" * 40)
        
        # 获取文件信息
        file_info = sftp.get_file_info(remote_test_file)
        if file_info:
            print(f"文件: {file_info['path']}")
            print(f"大小: {file_info['size']:,} 字节")
            print(f"修改时间: {time.ctime(file_info['mtime'])}")
            print(f"权限: {oct(file_info['mode'])[-3:]}")
        
        print("\n6. 计算文件哈希:")
        print("-" * 40)
        
        # 计算MD5
        md5_hash = sftp.calculate_md5(remote_test_file)
        if md5_hash:
            print(f"MD5哈希: {md5_hash}")
            
            # 也计算本地文件的MD5进行对比
            import hashlib
            with open(local_downloaded, 'rb') as f:
                local_md5 = hashlib.md5(f.read()).hexdigest()
            print(f"本地MD5: {local_md5}")
            print(f"匹配: {md5_hash == local_md5}")
        
        print("\n7. 目录操作:")
        print("-" * 40)
        
        # 测试目录上传下载
        print("创建测试目录结构...")
        
        # 创建本地测试目录
        local_test_dir = 'test_directory'
        os.makedirs(local_test_dir, exist_ok=True)
        
        # 创建一些测试文件
        for i in range(3):
            file_path = os.path.join(local_test_dir, f'file_{i}.txt')
            with open(file_path, 'w') as f:
                f.write(f"Test file {i}\n" * 10)
        
        # 创建子目录
        sub_dir = os.path.join(local_test_dir, 'subdir')
        os.makedirs(sub_dir, exist_ok=True)
        
        with open(os.path.join(sub_dir, 'subfile.txt'), 'w') as f:
            f.write("Subdirectory file\n")
        
        # 上传整个目录
        print(f"上传目录: {local_test_dir}")
        remote_test_dir = f'{test_dir}/uploaded_dir'
        
        success, total = sftp.upload_directory(local_test_dir, remote_test_dir)
        print(f"上传结果: {success}/{total} 文件")
        
        # 下载整个目录
        print(f"\n下载目录: {remote_test_dir}")
        local_downloaded_dir = 'downloaded_directory'
        
        success, total = sftp.download_directory(remote_test_dir, local_downloaded_dir)
        print(f"下载结果: {success}/{total} 文件")
        
        print("\n8. 清理:")
        print("-" * 40)
        
        # 清理远程文件
        if sftp.delete_file(remote_test_file):
            print(f"删除远程文件: {remote_test_file}")
        
        # 递归删除远程目录
        if sftp.delete_directory(test_dir, recursive=True):
            print(f"删除远程目录: {test_dir}")
        
        # 清理本地文件
        for file in [local_test_file, local_downloaded]:
            if os.path.exists(file):
                os.remove(file)
                print(f"删除本地文件: {file}")
        
        # 清理本地目录
        import shutil
        for dir_path in [local_test_dir, local_downloaded_dir]:
            if os.path.exists(dir_path):
                shutil.rmtree(dir_path)
                print(f"删除本地目录: {dir_path}")
        
        print("\n✓ 所有测试完成!")
        
    except Exception as e:
        print(f"✗ 发生错误: {e}")
    finally:
        sftp.close()

# 高级示例:SFTP监控和实时同步
class SFTPMonitor:
    """SFTP文件监控器"""
    
    def __init__(self, sftp_client: SFTPClient):
        self.sftp = sftp_client
        self.monitored_files = {}
        self.is_monitoring = False
    
    def start_monitoring(self, remote_path: str, interval: float = 5.0):
        """开始监控文件变化"""
        print(f"开始监控: {remote_path}")
        self.is_monitoring = True
        
        import threading
        
        def monitor_thread():
            while self.is_monitoring:
                try:
                    self._check_for_changes(remote_path)
                    time.sleep(interval)
                except Exception as e:
                    print(f"监控错误: {e}")
                    time.sleep(interval)
        
        thread = threading.Thread(target=monitor_thread, daemon=True)
        thread.start()
        return thread
    
    def _check_for_changes(self, remote_path: str):
        """检查文件变化"""
        try:
            # 获取当前文件状态
            current_files = {}
            
            def _collect_files(path):
                try:
                    items = self.sftp.sftp_client.listdir_attr(path)
                    for item in items:
                        item_path = f"{path}/{item.filename}"
                        if stat.S_ISDIR(item.st_mode):
                            _collect_files(item_path)
                        else:
                            current_files[item_path] = {
                                'size': item.st_size,
                                'mtime': item.st_mtime
                            }
                except:
                    pass
            
            _collect_files(remote_path)
            
            # 检查变化
            for path, info in current_files.items():
                if path in self.monitored_files:
                    old_info = self.monitored_files[path]
                    if info['mtime'] != old_info['mtime']:
                        print(f"文件修改: {path}")
                        print(f"  大小: {old_info['size']:,} -> {info['size']:,}")
                        print(f"  时间: {time.ctime(old_info['mtime'])} -> {time.ctime(info['mtime'])}")
                else:
                    print(f"新文件: {path}")
            
            # 检查删除的文件
            for path in list(self.monitored_files.keys()):
                if path not in current_files:
                    print(f"文件删除: {path}")
            
            # 更新监控记录
            self.monitored_files = current_files
            
        except Exception as e:
            print(f"检查变化时出错: {e}")
    
    def stop_monitoring(self):
        """停止监控"""
        self.is_monitoring = False
        print("监控已停止")

# 运行示例
if __name__ == "__main__":
    print("SFTP文件传输示例:")
    print("=" * 60)
    
    print("注意: 需要真实SSH服务器来运行这些示例")
    print("提供正确的连接信息后取消注释以下代码:")
    print("\n# sftp_examples()")
    
    # 模拟演示
    print("\n模拟演示代码结构:")
    print("-" * 40)
    
    class MockSFTP:
        def connect(self):
            print("模拟SFTP连接...")
            return True
        
        def list_directory(self, path='.'):
            print(f"列出目录: {path}")
            return [
                {'name': 'file1.txt', 'size': 1024, 'is_dir': False},
                {'name': 'folder1', 'size': 0, 'is_dir': True},
                {'name': 'file2.log', 'size': 2048, 'is_dir': False}
            ]
        
        def upload_file(self, local, remote):
            print(f"上传: {local} -> {remote}")
            return True
        
        def download_file(self, remote, local):
            print(f"下载: {remote} -> {local}")
            return True
        
        def close(self):
            print("关闭连接")
    
    # 演示用法
    sftp = MockSFTP()
    if sftp.connect():
        items = sftp.list_directory()
        for item in items:
            print(f"  {item['name']} ({'DIR' if item['is_dir'] else 'FILE'})")
        
        sftp.upload_file('local.txt', 'remote.txt')
        sftp.download_file('remote.txt', 'local_copy.txt')
        sftp.close()

4. SSH服务器

基础SSH服务器
import paramiko
import socket
import threading
import time
import logging
from typing import Optional, Dict, Any
from pathlib import Path

class SimpleSSHServer(paramiko.ServerInterface):
    """简单SSH服务器接口"""
    
    def __init__(self):
        self.event = threading.Event()
        self.username = None
        self.password = None
        
    def check_channel_request(self, kind: str, chanid: int) -> int:
        """检查通道请求"""
        if kind == 'session':
            return paramiko.OPEN_SUCCEEDED
        return paramiko.OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED
    
    def check_auth_password(self, username: str, password: str) -> int:
        """检查密码认证"""
        print(f"认证尝试: {username}")
        
        # 简单的认证逻辑
        if username == 'admin' and password == 'password':
            self.username = username
            self.password = password
            print(f"✓ 用户 {username} 认证成功")
            return paramiko.AUTH_SUCCESSFUL
        else:
            print(f"✗ 用户 {username} 认证失败")
            return paramiko.AUTH_FAILED
    
    def check_auth_publickey(self, username: str, key: paramiko.PKey) -> int:
        """检查公钥认证"""
        print(f"公钥认证尝试: {username}")
        print(f"密钥类型: {key.get_name()}")
        print(f"密钥指纹: {key.get_fingerprint().hex()}")
        
        # 这里可以添加公钥验证逻辑
        # 例如,检查公钥是否在授权列表中
        
        return paramiko.AUTH_FAILED  # 暂时禁用公钥认证
    
    def check_channel_shell_request(self, channel) -> bool:
        """检查Shell请求"""
        return True
    
    def check_channel_pty_request(self, channel, term, width, height,
                                 pixelwidth, pixelheight, modes) -> bool:
        """检查PTY请求"""
        return True
    
    def get_allowed_auths(self, username: str) -> str:
        """返回允许的认证方法"""
        return "password,publickey"
    
    def check_channel_exec_request(self, channel, command: bytes) -> bool:
        """检查执行命令请求"""
        print(f"执行命令: {command.decode('utf-8')}")
        
        # 处理命令
        response = self._handle_command(command.decode('utf-8'))
        
        # 发送响应
        channel.send(response)
        channel.send_exit_status(0)
        
        return True
    
    def _handle_command(self, command: str) -> str:
        """处理命令"""
        cmd = command.strip().lower()
        
        if cmd == 'help':
            return """可用命令:
help      - 显示此帮助
time      - 显示当前时间
whoami    - 显示当前用户
exit      - 退出
"""
        elif cmd == 'time':
            return f"当前时间: {time.strftime('%Y-%m-%d %H:%M:%S')}\n"
        elif cmd == 'whoami':
            return f"当前用户: {self.username}\n"
        elif cmd == 'exit':
            return "再见!\n"
        else:
            return f"未知命令: {command}\n输入 'help' 查看可用命令\n"

class SSHServer:
    """SSH服务器"""
    
    def __init__(self, host: str = '0.0.0.0', port: int = 2222,
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐