Python subprocess调用festival.exe时模拟按键输入无效程序阻塞
问题根因
你之前尝试的两种stdin写入方案全部失效,核心原因是festival.exe的按键等待逻辑根本没有读取标准输入流(stdin):它是直接调用Windows控制台原生API(比如_getch、ReadConsoleInput)从自身绑定的控制台窗口输入缓冲区读取事件,和stdin管道完全是两个独立的输入通道,你往PIPE里写入的内容永远不会被程序读到,自然无法触发程序继续执行。
可行解决方案
你可以根据自己是否需要保留程序的控制台窗口,选择对应的方案:
方案1:无窗口启动(最简单,无需额外依赖)
如果你不需要看到festival.exe的控制台界面,启动进程时加上CREATE_NO_WINDOW创建标志即可。这种模式下程序没有关联的控制台,调用读取按键的API会直接返回失败,不会进入阻塞等待状态,会自动走完后续流程(通常是直接退出)。
示例代码:
from subprocess import Popen, CREATE_NO_WINDOW proc = Popen( "festival.exe level.sok", creationflags=CREATE_NO_WINDOW ) # 等待进程执行结束即可,不会卡在按键等待步骤 proc.wait()
方案2:保留窗口,直接向控制台缓冲区写入按键事件
如果你需要查看程序的运行输出、不能隐藏控制台,可以通过Windows API直接向程序关联的控制台输入缓冲区写入模拟按键事件,和手动按键的效果完全一致。
注意:启动进程时不要把stdin重定向到PIPE,否则程序会失去和控制台的绑定,后续发送的事件无法被接收。如果需要收集输出,可以把stdout/stderr重定向到文件。
示例代码:
import ctypes from ctypes import wintypes from subprocess import Popen import time # Windows API常量定义 STD_INPUT_HANDLE = -10 KEY_EVENT_TYPE = 0x0001 VK_RETURN = 0x0D # 回车键虚拟键码 # 定义控制台输入事件结构体 class KEY_EVENT_RECORD(ctypes.Structure): _fields_ = [ ("bKeyDown", wintypes.BOOL), ("wRepeatCount", wintypes.WORD), ("wVirtualKeyCode", wintypes.WORD), ("wVirtualScanCode", wintypes.WORD), ("UnicodeChar", wintypes.WCHAR), ("dwControlKeyState", wintypes.DWORD) ] class INPUT_RECORD(ctypes.Structure): _fields_ = [ ("EventType", wintypes.WORD), ("KeyEvent", KEY_EVENT_RECORD) ] # 启动进程,不要重定向stdin proc = Popen("festival.exe level.sok") # 等待程序运行到等待按键的阶段,等待时间根据程序实际运行速度调整 time.sleep(2) # 加载kernel32.dll获取控制台操作接口 kernel32 = ctypes.WinDLL("kernel32.dll", use_last_error=True) console_input_handle = kernel32.GetStdHandle(STD_INPUT_HANDLE) # 构造回车按下事件 key_down = INPUT_RECORD() key_down.EventType = KEY_EVENT_TYPE key_down.KeyEvent.bKeyDown = True key_down.KeyEvent.wRepeatCount = 1 key_down.KeyEvent.wVirtualKeyCode = VK_RETURN key_down.KeyEvent.UnicodeChar = "\r" # 构造回车抬起事件,避免按键状态异常 key_up = INPUT_RECORD() key_up.EventType = KEY_EVENT_TYPE key_up.KeyEvent.bKeyDown = False key_up.KeyEvent.wRepeatCount = 1 key_up.KeyEvent.wVirtualKeyCode = VK_RETURN key_up.KeyEvent.UnicodeChar = "\r" written_count = wintypes.DWORD() # 写入按键事件到控制台缓冲区 kernel32.WriteConsoleInputW( console_input_handle, ctypes.byref(key_down), 1, ctypes.byref(written_count) ) kernel32.WriteConsoleInputW( console_input_handle, ctypes.byref(key_up), 1, ctypes.byref(written_count) ) proc.wait()
方案3:使用第三方封装库简化模拟逻辑
如果不想手写ctypes调用逻辑,也可以使用pywin32库封装好的控制台操作接口,或者pynput等全局按键模拟工具发送按键。注意用全局按键模拟时需要确保festival.exe的控制台窗口处于前台激活状态,否则按键会被发送到当前激活的其他程序。
内容的提问来源于stack exchange,提问作者Tibcik
相关产品推荐
相关产品推荐

