gpt4 book ai didi

python - 如何捕获子进程的输入和输出?

转载 作者:行者123 更新时间:2023-12-03 16:27:43 26 4
gpt4 key购买 nike

我正在尝试制作一个将可执行文件名称作为参数的程序,运行可执行文件并报告该运行的输入和输出。例如,考虑一个名为“circle”的子程序。我的程序需要运行以下内容:

$ python3 capture_io.py ./circleEnter radius of circle: 10Area: 314.158997[('output', 'Enter radius of circle: '), ('input',  '10\n'), ('output', 'Area: 314.158997\n')]

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:

The output_filter will be passed all the output from the child process. The input_filter will be passed all the keyboard input from the user.

So this is the code I wrote:

capture_io.py

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)
圈子.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
输入圆半径:10
区域:314.158997
[('output', '输入圆的半径:'), ('input', '1'), ('output', '1'), ('input', '0'), ('output', '0'), ('input', '\r'), ('output', '\r\n'), ('output', '区域: 314.158997\r\n')]

从输出中可以看出,输入是逐个字符处理的,并且还作为输出回显,这会造成困惑。是否可以更改此行为,以便我的 input_filter仅在 Enter 时运行被按下?
或者更一般地说,实现我的目标的最佳方式是什么(有或没有 pexpect )?

最佳答案

当我开始写一个助手时,我意识到主要问题是输入应该被记录行缓冲,所以退格和其他编辑在输入到达程序之前完成,但输出应该是无缓冲的,以便记录不被新行终止的提示。

为了记录的目的捕获输出,需要一个管道,但它会自动打开行缓冲。众所周知,伪终端解决了这个问题(expect 模块是围绕伪终端构建的),但是终端同时具有输入和输出,我们只想取消缓冲输出。

幸好有 stdbuf 效用。在 Linux 上,它改变了动态链接可执行文件的 C 库函数。不能普遍使用。

我修改了一个 Python 双向复制程序来记录它复制的数据。结合 stdbuf它产生所需的输出。

import select
import os

STDIN = 0
STDOUT = 1

BUFSIZE = 4096

def main(cmd):
ipipe_r, ipipe_w = os.pipe()
opipe_r, opipe_w = os.pipe()
if os.fork():
# parent
os.close(ipipe_r)
os.close(opipe_w)
fdlist_r = [STDIN, opipe_r]
while True:
ready_r, _, _ = select.select(fdlist_r, [], [])
if STDIN in ready_r:
# STDIN -> program
data = os.read(STDIN, BUFSIZE)
if data:
yield('in', data) # optional: convert to str
os.write(ipipe_w, data)
else:
# send EOF
fdlist_r.remove(STDIN)
os.close(ipipe_w)
if opipe_r in ready_r:
# program -> STDOUT
data = os.read(opipe_r, BUFSIZE)
if not data:
# got EOF
break
yield('out', data)
os.write(STDOUT, data)
os.wait()
else:
# child
os.close(ipipe_w)
os.close(opipe_r)
os.dup2(ipipe_r, STDIN)
os.dup2(opipe_w, STDOUT)
os.execlp(*cmd)
# not reached
os._exit(127)

if __name__ == '__main__':
log = list(main(['stdbuf', 'stdbuf', '-o0', './circle']))
print(log)

它打印:
[('out', b'Enter radius of circle: '), ('in', b'12\n'), ('out', b'Area: 452.388947\n')]

关于python - 如何捕获子进程的输入和输出?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62288531/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com