Python:编译后用subprocess.Popen.communicate向sudo传密码遇Unicode错误
解决编译后运行sudo权限请求的错误问题
咱们先把问题根源捋清楚:当你用python3 -m py_compile编译脚本后,实际运行的是__pycache__目录下的.pyc文件,这时候程序里的__file__变量指向的是这个.pyc文件的路径,而非原始的.py脚本。当你的代码尝试以sudo权限重新运行自身时,这种路径差异会导致子进程的输入交互环节出错,也就是你遇到的communicate调用报错。
下面给你几个实用的解决方案:
方案1:强制指向原始.py文件重新运行
修改你获取当前脚本路径的逻辑,自动把.pyc路径转换成对应的.py文件路径,避免路径混乱。示例代码如下:
import os def get_real_script_path(): script_path = os.path.abspath(__file__) # 处理pyc文件的情况 if script_path.endswith('.pyc'): # 针对__pycache__目录下的编译文件,比如__pycache__/myscript.cpython-39.pyc if '__pycache__' in script_path: # 剥离__pycache__目录,还原原始py文件名 base_name = os.path.basename(script_path).split('.cpython-')[0] + '.py' script_path = os.path.join(os.path.dirname(os.path.dirname(script_path)), base_name) else: # 直接去掉后缀的c,比如myscript.pyc -> myscript.py script_path = script_path[:-1] return script_path
之后在启动sudo子进程时,用这个函数返回的路径替代__file__:
import subprocess script_path = get_real_script_path() # 启动sudo子进程,用universal_newlines处理文本输入输出 reload = subprocess.Popen( ['sudo', 'python3', script_path], stdin=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True ) prompt = reload.communicate(input=f"{password}\n")[1]
这样不管是直接运行.py还是编译后的.pyc,都会调用原始脚本,从根源上避免路径问题。
方案2:用sudo原生参数处理密码输入(更安全可靠)
其实直接通过communicate手动输入密码不是最佳实践,sudo自带-S参数可以从标准输入读取密码,让sudo自己处理密码提示流程,既安全又能避开编译后的路径问题:
import os import subprocess def is_root(): return os.geteuid() == 0 if not is_root(): # 用-S参数让sudo从stdin读取密码,直接重启脚本 subprocess.call(['sudo', '-S', 'python3', get_real_script_path()]) exit()
修改后,程序会触发sudo原生的密码提示,不需要你手动构造输入逻辑,不管是.py还是.pyc运行都能正常工作,还能避免密码在代码中处理的安全风险。
方案3:打包成可执行文件(替代py_compile)
如果你的目标是生成可分发的执行文件,不如用pyinstaller打包成单个可执行文件,彻底避开.pyc路径的问题:
pip install pyinstaller pyinstaller --onefile myscript.py
生成的可执行文件在dist目录下,运行它时,sudo权限请求的逻辑会和直接运行.py脚本完全一致,没有额外问题。
内容的提问来源于stack exchange,提问作者FrailOtis
相关产品推荐
相关产品推荐

