You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.27 12:37:09