如何捕获子进程的输入和输出? [英] How to capture inputs and outputs of a child process?
问题描述
我正在尝试制作一个程序,该程序以可执行文件名作为参数,运行可执行文件并报告该运行的输入和输出.例如,考虑一个名为"circle"的子程序.我的程序需要运行以下内容:
I'm trying to make a program which takes an executable name as an argument, runs the executable and reports the inputs and outputs for that run. For example consider a child program named "circle". The following would be desired run for my program:
$ python3 capture_io.py ./circle
Enter radius of circle: 10
Area: 314.158997
[('output', 'Enter radius of circle: '), ('input', '10\n'), ('output', 'Area: 314.158997\n')]
我决定为此任务使用 pexpect
模块.它具有一种称为 interact
的方法如上所示,它使用户可以与子程序进行交互.它还具有2个可选参数:output_filter
和input_filter
.从文档中:
I decided to use pexpect
module for this job. It has a method called interact
which lets the user interact with the child program as seen above. It also takes 2 optional parameters: output_filter
and input_filter
. From the documentation:
output_filter
将传递子进程的所有输出.input_filter
将通过用户的所有键盘输入.
The
output_filter
will be passed all the output from the child process. Theinput_filter
will be passed all the keyboard input from the user.
这是我写的代码:
import sys
import pexpect
_stdios = []
def read(data):
_stdios.append(("output", data.decode("utf8")))
return data
def write(data):
_stdios.append(("input", data.decode("utf8")))
return data
def capture_io(argv):
_stdios.clear()
child = pexpect.spawn(argv)
child.interact(input_filter=write, output_filter=read)
child.wait()
return _stdios
if __name__ == '__main__':
stdios_of_child = capture_io(sys.argv[1:])
print(stdios_of_child)
circle.c
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char* argv[]) {
float radius, area;
printf("Enter radius of circle: ");
scanf("%f", &radius);
if (radius < 0) {
fprintf(stderr, "Negative radius values are not allowed.\n");
exit(1);
}
area = 3.14159 * radius * radius;
printf("Area: %f\n", area);
return 0;
}
哪个会产生以下输出:
$ python3 capture_io.py ./circle
Enter radius of circle: 10
Area: 314.158997
[('output', 'Enter radius of circle: '), ('input', '1'), ('output', '1'), ('input', '0'), ('output', '0'), ('input', '\r'), ('output', '\r\n'), ('output', 'Area: 314.158997\r\n')]
从输出中可以看到,输入被逐个字符地处理,并且回显为输出,从而造成混乱.是否可以更改此行为,以使我的input_filter
仅在按下Enter
时运行?
As you can observe from the output, input is processed character by character and also echoed back as output which creates such a mess. Is it possible to change this behaviour so that my input_filter
will run only when Enter
is pressed?
或更笼统地说,实现我的目标的最佳方法是什么(无论是否使用pexpect
)?
Or more generally, what would be the best way to achieve my goal (with or without pexpect
)?
推荐答案
是否可以更改此行为,使我的
input_filter
仅在按下Enter
时运行?
是,您可以通过从pexpect.spawn
继承并覆盖interact
方法来实现.我很快就会来.
Yes, you can do it by inheriting from pexpect.spawn
and overwriting the interact
method. I will come to that soon.
正如VPfB在他们的答案中指出的那样,您不能使用管道,我认为值得一提的是 pexpect
的文档.
As VPfB pointed out in their answer, you can't use a pipe and I think it's worth to mentioning that this issue is also addressed in the pexpect
's documentation.
您说的是
...输入被逐个字符处理,并作为输出回显...
... input is processed character by character and also echoed back as output ...
如果您检查interact
您可以看到以下行:
If you examine the source code of the interact
you can see this line:
tty.setraw(self.STDIN_FILENO)
这会将您的终端设置为原始模式:
This will set your terminal to raw mode:
输入可以逐个字符输入,...,并且禁止对终端输入和输出字符进行所有特殊处理.
input is available character by character, ..., and all special processing of terminal input and output characters is disabled.
这就是为什么input_filter
函数在每次按键时都会运行,并且会看到退格键或其他特殊字符的原因.如果您可以注释掉这一行,则在运行程序时会看到以下内容:
That is why your input_filter
function is running for every key press and it sees backspace or other special characters. If you could comment out this line, you would see something like this when you run your program:
$ python3 test.py ./circle
Enter radius of circle: 10
10
Area: 314.158997
[('output', 'Enter radius of circle: '), ('input', '10\n'), ('output', '10\r\n'), ('output', 'Area: 314.158997\r\n')]
这也可以让您编辑输入(即12[Backspace]0
会给您相同的结果).但是正如您所看到的,它仍然会回显输入.可以通过为儿童终端设置一个简单标志来禁用此功能:
And this would also let you edit the input (i. e. 12[Backspace]0
would give you same result). But as you can see, it still echoes the input. This can be disabled by setting a simple flag for child's terminal:
mode = tty.tcgetattr(self.child_fd)
mode[3] &= ~termios.ECHO
tty.tcsetattr(self.child_fd, termios.TCSANOW, mode)
运行最新更改:
$ python3 test.py ./circle
Enter radius of circle: 10
Area: 314.158997
[('output', 'Enter radius of circle: '), ('input', '10\n'), ('output', 'Area: 314.158997\r\n')]
宾果!现在,您可以从pexpect.spawn
继承并使用这些更改覆盖interact
方法,或者使用Python的内置pty
模块实现相同的操作:
Bingo! Now you can inherit from pexpect.spawn
and override interact
method with these changes or implement the same thing using the builtin pty
module of Python:
import os
import pty
import sys
import termios
import tty
_stdios = []
def _read(fd):
data = os.read(fd, 1024)
_stdios.append(("output", data.decode("utf8")))
return data
def _stdin_read(fd):
data = os.read(fd, 1024)
_stdios.append(("input", data.decode("utf8")))
return data
def _spawn(argv):
pid, master_fd = pty.fork()
if pid == pty.CHILD:
os.execlp(argv[0], *argv)
mode = tty.tcgetattr(master_fd)
mode[3] &= ~termios.ECHO
tty.tcsetattr(master_fd, termios.TCSANOW, mode)
try:
pty._copy(master_fd, _read, _stdin_read)
except OSError:
pass
os.close(master_fd)
return os.waitpid(pid, 0)[1]
def capture_io_and_return_code(argv):
_stdios.clear()
return_code = _spawn(argv)
return _stdios, return_code >> 8
if __name__ == '__main__':
stdios, ret = capture_io_and_return_code(sys.argv[1:])
print(stdios)
与pexpect
:
import sys
import termios
import tty
import pexpect
_stdios = []
def read(data):
_stdios.append(("output", data.decode("utf8")))
return data
def write(data):
_stdios.append(("input", data.decode("utf8")))
return data
class CustomSpawn(pexpect.spawn):
def interact(self, escape_character=chr(29),
input_filter=None, output_filter=None):
self.write_to_stdout(self.buffer)
self.stdout.flush()
self._buffer = self.buffer_type()
mode = tty.tcgetattr(self.child_fd)
mode[3] &= ~termios.ECHO
tty.tcsetattr(self.child_fd, termios.TCSANOW, mode)
if escape_character is not None and pexpect.PY3:
escape_character = escape_character.encode('latin-1')
self._spawn__interact_copy(escape_character, input_filter, output_filter)
def capture_io_and_return_code(argv):
_stdios.clear()
child = CustomSpawn(argv)
child.interact(input_filter=write, output_filter=read)
child.wait()
return _stdios, child.status >> 8
if __name__ == '__main__':
stdios, ret = capture_io_and_return_code(sys.argv[1:])
print(stdios)
这篇关于如何捕获子进程的输入和输出?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!