PyInstaller打包Linux可执行文件subprocess/pexpect失效问题求助
问题描述
我基于Python和customtkinter开发了一个GUI程序,在VSCode中运行完全正常,但使用PyInstaller打包成Linux可执行文件后,界面能正常启动,不过按钮绑定的flashAllButtonPressed()、buttonPressed()函数(内部调用了subprocess和pexpect)无法执行。我怀疑是缺少subprocess和pexpect所需的控制台输入输出环境。
注:当前密码实现并非最佳实践,这只是运行在Ubuntu 20.04系统上的硬盘托架验证原型。我是Python和tkinter/customtkinter新手,了解过STDIN和STDOUT但不知如何在此场景实现。
使用的PyInstaller打包命令:
pyinstaller -F -w -c testGUI.py --collect-all customtkinter
相关代码
import customtkinter as ctk from subprocess import check_output import pexpect import subprocess class Application(ctk.CTkFrame): def __init__(self, master=None): super().__init__(master) self.master = master self.pack() self.returnDriveList() self.createWidgets() currentRow = 0 currentCol = 0 max_cols = 5 drivesConnected = [] out = check_output(["lsblk", "--scsi"]) driveArray = out.splitlines() def createWidgets(self): frame1 = ctk.CTkFrame(master=None) frame1.pack(side="top") #self.button1 = ctk.CTkButton(frame1, text="refresh drives", command=lambda: self.refreshDrives()) #self.button1.pack(side="left") self.button2 = ctk.CTkButton(frame1, text="flash all drives", command=lambda: self.flashAllButtonPressed(self.drivesConnected)) self.button2.pack(side="right") frame2 = ctk.CTkFrame(self) frame2.pack(side="bottom") self.drivesConnected.pop(0) self.drivesConnected.pop(0) for drive in self.drivesConnected: button = ctk.CTkButton(frame2,text=drive,command=lambda s=drive:self.buttonPressed(s)) button.grid( row=self.currentRow, column=self.currentCol, padx=10, pady=10 ) self.currentCol += 1 if self.currentCol == self.max_cols: self.currentRow += 1 self.currentCol = 0 def flashAllButtonPressed(self, drives): for drive in drives: for i in range(5): print("led flashed") child = pexpect.spawn(f'sudo sg_ses --sas-addr=1:0:0:0 --set=ident /dev/{drive}') child.expect("password") child.sendline("111111") print(child.stdin + child.stdout) def driveStringToProcess(self, string): processedString = string.split() return processedString def buttonPressed(self, text): print(f"for loop button pressed: {text}") for i in range(10): print("led flashed") child = pexpect.spawn(f'sudo sg_ses --sas-addr=1:0:0:0 --set=ident /dev/{text}') child.expect("password") child.sendline("111111") #subprocess.run(["sudo", "sg_ses", "--sas-addr=1:0:0:0", "--set=ident", f"/dev/{text}"]) #subprocess.run(["sleep", ".8"]) #subprocess.run(["sudo", "sg_ses", "--sas-addr=1:0:0:0", "--set=ident", f"/dev/{text}"]) def returnDriveList(self): print("returnedDriveList function called") for drive in self.driveArray: processedDriveString = self.driveStringToProcess(drive) formattedDriveName = str(processedDriveString[0]) driveName = formattedDriveName.replace("'", "").replace("b", "", 1) print(f"Drive name: {driveName}") self.drivesConnected.append(driveName) print(f"drive appended: '{driveName}'") def refreshDrives(self): print("refresh button pressed") ctk.set_appearance_mode("dark") root = ctk.CTk() root.title("Drive Locator") root.minsize(640, 480) root.maxsize(1280, 720) app = Application(master=root) app.mainloop()
(注:补充了代码中缺失的import语句,确保代码可运行)
解决方案建议
- 修正PyInstaller打包参数:你同时使用了
-w(无控制台窗口)和-c(控制台窗口)参数,二者冲突。Linux环境下需去掉-w,保留-c以确保程序拥有控制台IO环境供subprocess和pexpect使用。同时添加--hidden-import确保pexpect依赖被正确打包,修改后的命令:
pyinstaller -F -c testGUI.py --collect-all customtkinter --hidden-import pexpect --hidden-import pexpect._async
- 修复pexpect输出读取错误:代码中
print(child.stdin + child.stdout)会触发类型错误,因为child.stdin和child.stdout是文件对象,无法直接相加。若需打印命令输出,可替换为:
child = pexpect.spawn(f'sudo sg_ses --sas-addr=1:0:0:0 --set=ident /dev/{drive}') child.expect("password") child.sendline("111111") # 读取并解码命令输出 print(child.read().decode('utf-8'))
- 配置sudo免密执行sg_ses(推荐):原型阶段可给当前用户配置sudo免密权限执行
sg_ses,避免pexpect处理密码的复杂逻辑。执行sudo visudo并添加一行:
你的用户名 ALL=(ALL) NOPASSWD: /usr/bin/sg_ses
之后代码可直接用subprocess调用,无需处理密码:
subprocess.run(["sudo", "sg_ses", "--sas-addr=1:0:0:0", "--set=ident", f"/dev/{drive}"], check=True)
- 确保系统命令路径正确:Linux下打包后需确保
sg_ses在系统PATH中,或直接使用绝对路径(如/usr/bin/sg_ses)调用命令,避免路径找不到的问题。
内容的提问来源于stack exchange,提问作者jackatalta
相关产品推荐
相关产品推荐

