伪终端运行的subprocess未适配真实终端全尺寸问题求解
解决方案
伪终端的窗口尺寸可以通过ioctl系统调用配合TIOCSWINSZ指令修改,Python的标准库fcntl模块可以直接调用该系统接口,同时可以监听终端尺寸变化信号实现动态适配。
完整修改后的代码
import os import pty import subprocess import sys import time import select import fcntl import struct import signal # 终端尺寸操作相关ioctl指令,以下为Linux平台标准取值 TIOCGWINSZ = 0x5413 TIOCSWINSZ = 0x5414 def get_real_terminal_size(): # 获取当前真实终端的行数、列数 buf = struct.pack('HHHH', 0, 0, 0, 0) res = fcntl.ioctl(sys.stdout.fileno(), TIOCGWINSZ, buf) rows, cols, _, _ = struct.unpack('HHHH', res) return rows, cols def set_pty_size(pty_fd, rows, cols): # 为指定伪终端文件描述符设置窗口尺寸 buf = struct.pack('HHHH', rows, cols, 0, 0) fcntl.ioctl(pty_fd, TIOCSWINSZ, buf) # 终端尺寸变化信号处理回调 def handle_sigwinch(signum, frame): rows, cols = get_real_terminal_size() # 同步更新两个伪终端的尺寸 set_pty_size(stdout_master_fd, rows, cols) set_pty_size(stderr_master_fd, rows, cols) # 给子进程发送SIGWINCH信号,通知其适配新尺寸 if p.poll() is None: os.kill(p.pid, signal.SIGWINCH) # 注册SIGWINCH信号监听 signal.signal(signal.SIGWINCH, handle_sigwinch) stdout_master_fd, stdout_slave_fd = pty.openpty() stderr_master_fd, stderr_slave_fd = pty.openpty() # 程序启动时先同步一次真实终端的初始尺寸 init_rows, init_cols = get_real_terminal_size() set_pty_size(stdout_slave_fd, init_rows, init_cols) set_pty_size(stderr_slave_fd, init_rows, init_cols) p = subprocess.Popen( "top", shell=True, stdout=stdout_slave_fd, stderr=stderr_slave_fd, close_fds=True ) stdout_parts = [] while p.poll() is None: rlist, _, _ = select.select([stdout_master_fd, stderr_master_fd], [], []) for f in rlist: output = os.read(f, 1000) # 非阻塞读取 sys.stdout.write(output.decode("utf-8")) sys.stdout.flush() time.sleep(0.01)
关键逻辑说明
- 类Unix系统中终端尺寸通过
winsize结构体存储,包含行数、列数、像素宽度、像素高度四个字段,常规场景下仅需要设置行数和列数即可适配字符输出 - 程序启动时会先读取当前真实终端的尺寸,赋值给新建的两个伪终端,解决初始默认80x24尺寸不对的问题
- 监听
SIGWINCH信号,当用户拖拽终端改变大小时,会同步更新伪终端的尺寸,同时给子进程发送SIGWINCH信号,通知子进程重新渲染适配新尺寸
注意:上述
TIOCGWINSZ和TIOCSWINSZ的取值为Linux平台的标准值,如果需要适配macOS等其他类Unix系统,可以通过对应系统的头文件查询指令的对应数值。
内容的提问来源于stack exchange,提问作者Mugen
相关产品推荐
相关产品推荐

