当前位置: 首页 > 知识库问答 >
问题:

与子进程stdout/stdin通信

松智勇
2023-03-14

我试图与一个进程通信(该进程本身会写入stdin和stdout,以便在终端中与用户交互),并在C中读取它的stdin和写入它的stdout。

因此,我尝试以编程方式替换shell用户。一个methapohrical示例:假设出于某种原因我想在C中使用VIM。然后我还需要编写命令(stdout)并从编辑器(stdin)中读取内容。

起初我认为这可能是一项微不足道的任务,但似乎没有标准的方法<代码>int系统(const char*命令) 只执行一个命令,并将命令stdin/stdout设置为调用进程之一。

因为这毫无意义,我查看了FILE*popen(constchar*命令,constchar*类型) 但手册页面说明:

由于管道根据定义是单向的,所以类型参数可以只指定读取或写入,而不是两者都指定;相应地,结果流是只读或只写的。

及其含义:

popen()的返回值在所有方面都是正常的标准I/O流,但它必须使用pclose()而不是fclose(3)关闭。写入这样的流写入命令的标准输入;该命令的标准输出与调用popen()的进程的标准输出相同,除非命令本身对此进行了更改。相反,从“popend”流读取命令的标准输出,命令的标准输入与调用popen()的进程的标准输入相同。

因此,使用popen()并非完全不可能,但在我看来它非常不雅观,因为我必须解析调用进程的stdout(调用popen()的代码),以便解析从popened命令发送的数据(当使用popen类型“w”时)。

相反,当使用“r”类型调用popen时,我需要写入调用的进程stdin,以便将数据写入popened命令。在本例中,我甚至不清楚这两个进程是否在stdin中接收相同的数据。。。

我只需要控制程序的stdin和stdout。我的意思是,难道不能有这样一个函数:

stdin_of_process, stdout_of_process = real_popen("/path/to/bin", "rw")
// write some data to the process stdin
write("hello", stdin_of_process)
// read the response of the process
read(stdout_of_process)

所以我的第一个问题是:实现上层功能的最佳方式是什么?

目前,我正在尝试以下方法与另一个进程进行通信:

  1. int-pipe(int-fildes[2])设置两条管道 。一个管道读取进程的标准输出,另一个管道写入进程的标准输入
  2. 叉子
  3. 使用intexecvp(constchar*file,char*constargv[]),在分叉子进程中执行我想要与之通信的进程
  4. 使用原始流程中的两个管道与子级进行通信

这很容易,机器人不是那么简单地实现的(至少对我来说)。在一个案例中,我奇怪地设法做到了这一点,但是当我试图用一个更简单的例子来理解我在做什么时,我失败了。这是我目前的问题:

我有两个节目。第一个只需每隔100毫秒将递增的数字写入其标准输出:

#include <unistd.h>
#include <time.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>

void sleepMs(uint32_t ms) {
    struct timespec ts;
    ts.tv_sec = 0 + (ms / 1000);
    ts.tv_nsec = 1000 * 1000 * (ms % 1000);
    nanosleep(&ts, NULL);
}

int main(int argc, char *argv[]) {
    long int cnt = 0;
    char buf[0x10] = {0};

    while (1) {
        sleepMs(100);
        sprintf(buf, "%ld\n", ++cnt);
        if (write(STDOUT_FILENO, buf, strlen(buf)) == -1)
            perror("write");
    }
}

现在,第二个程序应该读取第一个程序的stdout(请记住,我最终想要读取和写入一个进程,所以在上面的用例中使用popen()的技术正确解决方案可能是正确的具体情况下,因为我简化了我的实验,只是捕获底层程序的stdout)。我希望从底层程序读取上层程序写入stdout的任何数据。但它什么也不读。原因在哪里?(第二个问题)。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdint.h>
#include <time.h>

void sleepMs(uint32_t ms) {
    struct timespec ts;
    ts.tv_sec = 0 + (ms / 1000);
    ts.tv_nsec = 1000 * 1000 * (ms % 1000);
    nanosleep(&ts, NULL);
}

int main() {
    int pipe_fds[2];
    int n;
    char buf[0x100] = {0};
    pid_t pid;

    pipe(pipe_fds);

    char *cmd[] = {"/path/to/program/above", NULL};

    if ((pid = fork()) == 0) { /* child */
        dup2(pipe_fds[1], 1); // set stdout of the process to the write end of the pipe
        execvp(cmd[0], cmd); // execute the program.
        fflush(stdout);
        perror(cmd[0]); // only reached in case of error
        exit(0);
    } else if (pid == -1) { /* failed */
        perror("fork");
        exit(1);
    } else { /* parent */

        while (1) {
            sleepMs(500); // Wait a bit to let the child program run a little
            printf("Trying to read\n");
            if ((n = read(pipe_fds[0], buf, 0x100)) >= 0) { // Try to read stdout of the child process from the read end of the pipe
                buf[n] = 0; /* terminate the string */
                fprintf(stderr, "Got: %s", buf); // this should print "1 2 3 4 5 6 7 8 9 10 ..."
            } else {
                fprintf(stderr, "read failed\n");
                perror("read");
            }
        }
    }
}

共有2个答案

沈博达
2023-03-14

您的想法是正确的,我没有时间分析您的所有代码来指出具体的问题,但我确实想指出一些您在程序和终端如何工作方面可能忽略的事情。

终端作为“文件”的想法是幼稚的。像vi这样的程序使用一个库(ncurses)来发送特殊的控制字符(并更改终端设备驱动程序设置)。例如,vi将终端设备驱动程序本身置于一种模式,在这种模式下,它可以一次读取一个字符,等等。

以这种方式“控制”像vi这样的程序是非常重要的。

在你的简化实验中...

缓冲区太小一个字节。另外,请注意IO有时是行缓冲的。因此,您可以尝试确保换行符正在被传输(使用printf而不是斯普林特/斯特伦/写...您已经将stdout挂接到管道上了),否则您可能在换行符被命中之前看不到数据。我不记得管道有线路缓冲,但值得一试。

黄宏旷
2023-03-14

这是一个(C 11风味)完整的例子。

但是,出于许多实际目的,Expect库可能是一个不错的选择(请查看其源代码发行版的example子目录中的代码)。

 类似资料:
  • 问题内容: 这是此问题的后续措施,但是如果我想将参数传递给,如何实时获取输出?这就是我目前拥有的;我也尝试从模块中替换为,这只会导致脚本挂起。 在我不必通过的前一个问题中,建议我使用,没有空间输送任何东西到。 附录:这适用于传输,但是我只能在最后看到输出,并且我想查看传输发生时的详细信息。 问题答案: 为了实时从子流程中获取标准输出,您需要准确确定所需的行为;具体来说,您需要确定是要逐行处理还是逐

  • 问题内容: (我是Java新手)我需要启动一个进程并接收2或3个句柄:对于STDIN,STDOUT(和STDERR),因此我可以将输入写入进程并接收其输出,就像命令行管道一样表现(例如“ grep”) 在Python中,可以通过以下代码实现: Java等效项是什么? 到目前为止我已经尝试过 顺便说一句,第一次尝试仅适用于\ n \ n,但不适用于单个\ n(为什么?) 下面的代码有效,但是所有输入

  • 问题内容: 我试图从通话中抢夺,尽管我可以通过以下方式轻松实现此目标: 我想“实时”抓取。使用上述方法,PIPE等待获取所有内容,然后返回。 因此,出于日志记录目的,这不符合我的要求(例如,“查看”发生的情况)。 在运行时,有没有办法逐行获取?或者这是(必须等到关闭)的限制。 编辑 如果我切换为我只得到(不理想)的最后一行: 问题答案: 您的解释器正在缓冲。在打印语句后,添加对sys.stdout

  • 问题内容: 我试图在Python中使用子进程,以使外部脚本以类似于服务器的方式打开。外部脚本首先加载模型。完成此操作后,它将通过STDIN接受请求,并将处理后的字符串返回给STDOUT。 到目前为止,我已经尝试过 但是,我不能使用 为了通过子过程重复处理input_strings,无论我使用还是,out都将是空的。但是,当我在读取STDOUT之前关闭stdin时,它起作用了,但是这关闭了子进程,这

  • 我有一个程序,通过Pipe. SourceChannel将stdout、stderr和stdin传输到/从Bytes Buffers。 程序使用选择器注册每个通道。 循环中的程序定期遍历 selectedKeys 集中的每个键,这些键对应于 stdout、stderr 和 stdin。对于每个密钥,它确定密钥是否有效、可读或可写,如果是,则与 ByteBuffer 执行相应的 IO 操作 我的问题

  • 问题内容: 我想捕获并显示通过Python的子流程调用的流程的输出。 我以为我可以将文件状对象作为参数stdout和stderr传递 我可以看到它访问了属性-因此它正在处理对象。但是,永远不会调用该方法。我的方法是否完全无效或我只是缺少什么? 更新: 我还想工作的是ANSI控制字符,用于移动光标并覆盖以前的输出内容。我不知道这是否是正确的术语,但这是我的意思的一个示例:我正在尝试使一些GIT自动化