使用Python Paramiko exec_command执行命令未完成的问题求助
看起来你遇到的问题很典型——手动终端执行正常,但自动化脚本调用时卡住,大概率和非交互式环境的输出缓冲/TTY依赖或者Paramiko的流读取方式有关。结合你的描述,我整理了几个可能的原因和对应的解决方案:
1. 进度条Callback依赖TTY导致阻塞
你代码中的Callback=pp(BACKUP)应该是一个进度条类(比如tqdm或者自定义的进度打印),这类工具通常依赖终端TTY(交互式终端)来刷新输出。而Paramiko的exec_command默认不会分配TTY,非TTY环境下进度条可能因为无法输出而卡住程序。
解决方案:
方法A:给exec_command分配TTY
在调用exec_command时添加get_pty=True参数,模拟终端环境:
ssh_stdin, ssh_stdout, ssh_stderr = self.ssh.exec_command( 'sudo python3 /path/to/backup.py', timeout=1800, get_pty=True )
注意:如果你的sudo需要输入密码,还需要通过
ssh_stdin.write('your_sudo_password\n')提前输入密码,否则会卡在密码输入步骤。
方法B:修改Callback适配非TTY环境
在backup.py中判断当前输出是否为TTY,非TTY时禁用进度条输出:
import os import sys def get_progress_callback(file_path): # 检查是否为交互式终端 if sys.stdout.isatty(): # 原有的进度条逻辑,比如用tqdm from tqdm import tqdm file_size = os.path.getsize(file_path) return tqdm(total=file_size, unit='B', unit_scale=True) else: # 非TTY环境下返回空回调,不输出进度 class NullCallback: def __call__(self, bytes_transferred): pass return NullCallback() # 上传时使用适配后的回调 s3_client.upload_file( Filename=BACKUP, Bucket=BUCKET_NAME, Key=SPLIT_FILE_NAME, Callback=get_progress_callback(BACKUP), Config=config )
2. Paramiko未及时读取流导致缓冲区阻塞
当backup.py有大量stdout/stderr输出(比如进度条的实时打印)时,Paramiko的输出缓冲区会被填满,如果没有及时读取,程序会因为无法继续输出而挂起。你当前只调用了一次readline(),这不足以处理持续的输出。
解决方案:用线程实时读取stdout和stderr
启动两个线程分别读取stdout和stderr,避免缓冲区阻塞:
import threading import logging def read_stream(stream, stream_name): """持续读取流并打印日志""" for line in iter(stream.readline, ''): logging.debug(f'{stream_name}: {line.strip()}') # 先启动读取线程 stdin, stdout, stderr = self.ssh.exec_command('sudo python3 /path/to/backup.py', timeout=1800) stdout_thread = threading.Thread(target=read_stream, args=(stdout, 'ssh_stdout')) stderr_thread = threading.Thread(target=read_stream, args=(stderr, 'ssh_stderr')) stdout_thread.start() stderr_thread.start() # 等待命令执行完成 stdout_thread.join() stderr_thread.join()
3. 环境变量差异导致上传异常
手动登录终端时的环境变量和Paramiko执行命令时的环境变量可能不同,比如AWS的凭证配置(~/.aws/credentials)、PATH或者其他自定义变量,可能导致s3_client在自动化执行时出现隐性错误。
解决方案:
- 对比手动执行和Paramiko执行的环境变量:分别执行
sudo env,查看输出差异,确认是否有缺失的关键变量(比如AWS_ACCESS_KEY_ID、AWS_SECRET_ACCESS_KEY)。 - 如果是凭证问题,在
backup.py中显式指定凭证路径,或者用环境变量传入:# 在backup.py中指定凭证文件路径 import boto3 s3_client = boto3.client( 's3', aws_access_key_id='YOUR_KEY', aws_secret_access_key='YOUR_SECRET', endpoint_url='your_cos_endpoint' )
4. 检查sudo的免密配置
如果你的sudo需要密码,手动执行时你输入了密码,但Paramiko执行时没有处理密码输入,会导致程序卡在sudo的密码请求步骤。可以检查/etc/sudoers配置,确保执行该脚本的用户不需要输入密码:
# 编辑sudoers文件(用visudo避免语法错误) visudo # 添加一行: your_username ALL=(ALL) NOPASSWD: /usr/bin/python3 /path/to/backup.py
先试试前两个方案(TTY分配或修改Callback),这是最可能解决你当前卡住问题的方向。
内容的提问来源于stack exchange,提问作者runnerpaul

