PyQt窗口执行终端命令需关闭窗口才显示输出的原因
问题
我正在编写一个基于PyQt的小型GUI,其中包含几个按钮,用于执行我常用的命令。我希望这些命令能在启动该GUI的终端中执行。
在Linux系统中,从另一个终端可以通过the_command > /dev/pts/x实现此需求。
以下是我编写的代码:
from PyQt5.Qt import QApplication, QMainWindow, QPushButton import fcntl from functools import partial import subprocess import sys import termios # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # run script from a Linux terminal to get a tty! # run a command in the terminal def run_term_cmd(cmd, tty): with open(tty, 'w') as fd: for c in cmd: fcntl.ioctl(fd, termios.TIOCSTI, c) fcntl.ioctl(fd, termios.TIOCSTI, "\n") # return the current tty (eg. /dev/pts/4) def get_tty(): cp = subprocess.run("tty", capture_output=True, check=True) return cp.stdout.decode().rstrip() class MainWindow(QMainWindow): def __init__(self, cmds, parent=None): super(MainWindow, self).__init__(parent) btn = QPushButton("run ls") self.setCentralWidget(btn) btn.clicked.connect(partial(self.btn_clicked, "ls -al /")) def btn_clicked(self, cmd): run_term_cmd(cmd, get_tty()) # switch this to run with/without Qt use_gui = True if not use_gui: run_term_cmd("ls -al", get_tty()) else: app = QApplication(sys.argv) w = MainWindow(sys.argv) w.show() sys.exit(app.exec_())
当use_gui = False(不使用GUI)时,这段代码可以正常工作;但开启GUI后,命令的输出要等到窗口关闭后才会显示。请问这是什么原因?
原因与解决方法
原因
Qt的事件循环(app.exec_())启动后,会接管当前进程的标准输出、标准错误流,导致终端的输出缓冲区无法即时刷新。你通过TIOCSTI模拟输入到终端的命令确实会执行,但输出被系统缓冲,直到Qt事件循环终止(窗口关闭)时,才会一次性将缓冲内容输出到终端。
另外,GUI运行时,Python进程的标准流已与Qt事件循环绑定,终端的输出流处于阻塞缓冲状态,无法实时回显命令执行结果。
解决方法
要让终端即时显示输出,需要让命令输出绕过Qt的流控制,直接写入终端TTY,或者强制刷新终端缓冲区,以下是两种可行方案:
方案1:启动GUI前重定向标准流到终端TTY
在初始化QApplication之前,将进程的stdout和stderr重新绑定到当前终端的TTY,这样命令输出会直接写入终端,不受Qt事件循环影响:
from PyQt5.Qt import QApplication, QMainWindow, QPushButton import fcntl from functools import partial import subprocess import sys import termios # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # run script from a Linux terminal to get a tty! # run a command in the terminal def run_term_cmd(cmd, tty): with open(tty, 'w') as fd: for c in cmd: fcntl.ioctl(fd, termios.TIOCSTI, c) fcntl.ioctl(fd, termios.TIOCSTI, "\n") # return the current tty (eg. /dev/pts/4) def get_tty(): cp = subprocess.run("tty", capture_output=True, check=True) return cp.stdout.decode().rstrip() # 新增:重定向标准输出/错误到终端TTY tty_path = get_tty() sys.stdout = open(tty_path, 'w') sys.stderr = open(tty_path, 'w') class MainWindow(QMainWindow): def __init__(self, cmds, parent=None): super(MainWindow, self).__init__(parent) btn = QPushButton("run ls") self.setCentralWidget(btn) btn.clicked.connect(partial(self.btn_clicked, "ls -al /")) def btn_clicked(self, cmd): run_term_cmd(cmd, get_tty()) # switch this to run with/without Qt use_gui = True if not use_gui: run_term_cmd("ls -al", get_tty()) else: app = QApplication(sys.argv) w = MainWindow(sys.argv) w.show() sys.exit(app.exec_())
方案2:模拟输入后强制刷新终端缓冲区
在run_term_cmd函数中,模拟输入命令后,调用TIOCFLUSH强制刷新终端的输入输出缓冲区:
def run_term_cmd(cmd, tty): with open(tty, 'w') as fd: for c in cmd: fcntl.ioctl(fd, termios.TIOCSTI, c) fcntl.ioctl(fd, termios.TIOCSTI, "\n") # 强制刷新终端缓冲区 fcntl.ioctl(fd, termios.TIOCFLUSH, termios.TCIOFLUSH)
相比之下,方案1的稳定性和效果更好,推荐优先使用。
内容的提问来源于stack exchange,提问作者Fanfoue
相关产品推荐
相关产品推荐

