process.communicate 和 getche() 失败
我正在尝试自动化一个用C++编写的交互式命令行工具的执行。
这个程序启动后,会等待用户输入字母 S、Q 或 P(分别代表状态、退出或暂停)。它使用了一个非标准的 msvcrt 函数 "getche",这个函数可以直接获取按键,而不需要用户按回车。
我尝试用标准的方法与这个程序进行交互(比如向标准输入写数据,使用 process.communicate[]),但是程序没有接收到输入。经过几个小时的尝试不同的方法,我在 Visual Studio 中创建了两个小项目来复现这个问题,以确保我没有疯。
这是用来调用这个程序的 Python 脚本:
import subprocess
import time
cmd = ["test-getch.exe"]
process = subprocess.Popen(cmd, stderr = subprocess.PIPE, stdin = subprocess.PIPE, stdout = subprocess.PIPE)
i = process.stdin
#msvcrt.ungetch('s')
i.write("S\n")
print process.communicate()[0]
i.close()
time.sleep(3)
print "DONE"
这两个程序是我测试的。第一个程序我可以与之进行交互:
#include "stdafx.h"
#include <conio.h>
int _tmain(int argc, _TCHAR* argv[])
{
char response [2];
printf("Enter \"s\":\n");
gets(response);
printf("You entered %s", response);
return 0;
}
而这个程序我无法与之进行交互:
#include "stdafx.h"
#include <conio.h>
int _tmain(int argc, _TCHAR* argv[])
{
int response;
printf("Enter \"a\":\n");
response = getche();
printf("You entered %c", response);
return 0;
}
看起来 getche() 并不监听标准输入,可能是监听某种键盘事件。有没有人知道该怎么处理这个问题?
补充:我还要提到,我发现了使用 IDA Pro 捕获输入的方法。我并不是写这个我想要自动化的程序的人。它是一个闭源工具,所以我无法重新编写它接受输入的方式,只能对程序进行修改。
其实我选择了一个相当疯狂的解决方案,但它有效……我对 pydbg 非常熟悉,似乎通过附加到这个进程并调用我需要的函数来进行进程仪器化是可行的。这确实有点过于复杂,但我可以在之后从进程中分离出来,让它正常运行。
[1] Pydbg: http://pedram.redhive.com/PyDbg/docs/
2 个回答
getche
是从 控制台 读取输入,而不是从标准输入读取。如果你的 Python 程序在一个控制台窗口中运行,那么你的子进程仍然会尝试从那个控制台读取输入,而不是从作为标准输入传递过来的管道。
虽然可以创建一个看不见的控制台窗口,把它连接到子进程,并向它输入数据,但这非常复杂,而且容易出错。
我建议你重新编写程序,只从标准输入读取,而不是使用 getche()
。如果你 真的 想让程序在用户不按回车的情况下对按键做出反应,那么可以根据标准输入是否来自终端来改变程序的行为。如果是,就使用 getche
;如果不是,就直接从 stdin
读取。你可以使用 _isatty
(或者 POSIX 的等价函数 isatty
,出于某种原因,微软决定在他们的运行时中不再使用 POSIX 名称)。例如:
int ReadChar()
{
if(_isatty(0))
{
// stdin is a terminal
return _getche();
}
else
{
// stdin is not a terminal
return getchar();
}
}
亚当·罗森菲尔德的回答是个不错的办法,如果你可以改变被调用程序的行为。不过,如果你真的需要写入控制台的输入缓冲区,可以试试PyWin32的win32console模块。不过,我不太确定当标准输出被重定向时,如何让字符回显部分正常工作。最后的结果是,它会在行的开头打印。
C:
#include <stdio.h>
int main(int argc, char *argv[]) {
int response;
printf("Enter \"a\": ");
response = getche();
printf("\nYou entered \"%c\" ", response);
return 0;
}
/* gcc test_getch.c -o test_getch.exe */
Python:
import subprocess
import win32console
def make_buf(c):
buf = win32console.PyINPUT_RECORDType(win32console.KEY_EVENT)
buf.KeyDown = 1
buf.RepeatCount = 1
buf.Char = c
return buf
con_in = win32console.GetStdHandle(win32console.STD_INPUT_HANDLE)
cmd = ["test_getch.exe"]
p = subprocess.Popen(cmd, stdin=subprocess.PIPE)
buf = make_buf('a')
con_in.WriteConsoleInput([buf])