如何在Python中执行带sudo的命令并捕获标准输出?
问题
需要在Python脚本中执行sudo命令(仅部分操作如apt install需要高权限),同时要获取命令返回值用于分析。现有代码尝试传递sudo密码失败,用管道方式(["echo", f"{password}\n", "|", "sudo", "-S"])也无法正常执行命令,仅打印命令内容。
现有代码:
from subprocess import Popen, PIPE, CalledProcessError from typing import List def _logable_sudo(self, cmds: List[str], password: str): sudo_cmds = ["sudo"] sudo_cmds.extend(cmds) process = Popen(sudo_cmds, stdin=PIPE, stdout=PIPE, stderr=STDOUT) process.communicate(f"{password}\n".encode()) with process.stdout: try: for line in iter(process.stdout.readline, b""): # TODO replace print with actuall logging print(line.decode("utf-8").strip()) except CalledProcessError as e: print(f"{str(e)}")
解决方案
1. 修复密码传递逻辑(核心修复)
你的代码有两个关键问题:
sudo默认不会从标准输入读取密码,必须加上-S参数明确告知communicate()执行后会自动关闭stdin/stdout管道,后续再读取stdout会报错,应该直接从communicate()的返回值获取输出
修改后的可用代码:
from subprocess import Popen, PIPE from typing import List def _logable_sudo(self, cmds: List[str], password: str) -> int: sudo_cmds = ["sudo", "-S"] # 新增-S参数,让sudo从stdin读密码 sudo_cmds.extend(cmds) # 启用text模式,避免手动编解码字节流 process = Popen( sudo_cmds, stdin=PIPE, stdout=PIPE, stderr=STDOUT, text=True ) # 一次性传递密码并获取所有输出 output, _ = process.communicate(f"{password}\n") # 逐行处理输出(替换成日志逻辑即可) for line in output.strip().split("\n"): print(line) # 返回命令退出码,0表示成功,非0表示失败,用于后续分析 return process.returncode
2. 管道方式失败的原因
用["echo", f"{password}\n", "|", "sudo", "-S"]这种写法是错误的——Popen默认不会解析shell的管道符号,除非设置shell=True。但这种方式存在安全风险(如果密码或命令包含特殊字符,可能触发shell注入),所以不推荐。如果非要用管道,写法如下(仅作参考):
process = Popen( f"echo '{password}' | sudo -S {' '.join(cmds)}", shell=True, stdout=PIPE, stderr=STDOUT, text=True ) output, _ = process.communicate()
3. 额外注意事项
- 确保执行脚本的用户有sudo权限,若在非交互式环境运行,可能需要修改sudoers配置添加
Defaults !requiretty - 密码不要硬编码,建议从环境变量或加密配置文件读取
- 可通过返回的退出码判断命令执行结果,结合输出内容做更细致的错误分析
内容的提问来源于stack exchange,提问作者MaKaNu
相关产品推荐
相关产品推荐

