C stdio 重定向到和从子进程

问题描述

我有一个 CLI 程序,它从标准输入读取命令行并将输出写入标准输出。据我了解,当我从终端启动这个程序时,它成为该终端的子进程,从而继承了标准输入和标准输出

不是从终端启动 CLI,我想要另一个程序来启动它。我希望 CLI 从这个程序的 stdout 获取它的输入,并将它的输出重定向到这个程序的 stdin。

Terminal Program CLI
stdin -> stdin <-stdout
stdout<- stdout->stdin

我不确定,但我认为我可以这样做:

system("</dev/stdout ./CLI >/dev/stdin &");

有没有更好的方法可以做到这一点,我觉得有。我可以创建命名管道而不是使用标准输入和标准输出

解决方法

通常,在终端中,您运行某种 shell,如 bash、ksh 或 tcsh。这个 shell 通常是像 xterm 或 Konsole 这样的终端程序的子进程。每当您在终端中启动一个程序时,该程序将成为 shell 的子进程,如下所示:

xterm -> bash -> my_cli_program

有一个名为 pstree 的命令,它显示带有子进程的进程树。

有一个调用 popen() 的函数,它允许您运行一个类似于 system() 的程序,但它允许您写入该程序的标准输入 从其标准输出读取。不幸的是,由于使用了单向管道,因此您只能使用 popen 进行读取或写入。

要从子进程的 stdin 和 stdout 读取和写入,您需要使用 pipe() 创建两个管道。创建管道后,您可以 fork() 然后在子进程和父进程中使用 dup2() 将进程的 stdout 和 stdin 与您创建的管道连接起来。一切都连接好后,您可以在子进程中调用 system() 或更好的 execve() ,因为 system() 将创建另一个进程。

,

您需要pipe(2)

pipe() 创建一个管道,一个单向的数据通道,可以 用于进程间通信。数组 pipefd 用于 返回两个指向管道末端的文件描述符。 pipefd[0] 指的是管道的读取端。 pipefd[1] 指的是 到管道的写端。写入到写端的数据 管道由内核缓冲,直到从 read 中读取 管道的末端。如需更多详细信息,请参阅pipe(7)

你需要dup2(2)

int dup2(int oldfd,int newfd);

dup2() 系统调用执行与 dup() 相同的任务,但 它不使用编号最低的未使用文件描述符,而是 使用 newfd 中指定的文件描述符编号。其他 话,文件描述符 newfd 被调整,以便它现在 引用与 oldfd 相同的打开文件描述。

如果文件描述符 newfd 先前已打开,则将其关闭 在被重新使用之前;关闭是静默执行的(即,任何 dup2() 不会报告关闭期间的错误。

所以,你需要的是两个管道。由于一个管道只能做一个方向,而你想要全双工,你需要两个。下面是两个非常简单的例子。 cap.c 是一种读取一个字符并将其回显大写的程序。它无限期地这样做。

pipe.c 是产生 cap.c 的程序,创建两个管道,写入它,读回孩子的响应,并通过 stderr 给我们响应(因为我们将有dup2(2)另一个,stdinstdout)。代码已注释。

/* cap.c */
#include <stdio.h>
#include <unistd.h>
#include <ctype.h>

int main(void)
{
    char buf;
    while(1) {
        read(0,&buf,1);
        buf = toupper(buf);
        write(1,1);
    }
}

另一个...

/* pipe.c */
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>

int main(void)
{
    int parent_write[2],child_write[2],ws;
    char *cap[2] = {"./cap",NULL},buf;
    pid_t cpid;

    pipe(parent_write);
    pipe(child_write);

    /* pipe[0] -> read (you read from it)
     * pipe[1] -> write (you write to it)
     *
     * parent_write[0] -> the child will read this
     * parent_write[1] -> the parent writes here
     *
     * child_write[0] -> the parent reads here
     * child_write[1] -> the child writes here
     */

    if(!(cpid = fork())) {
        /* child process,the "add" */

        /* close the read end of child_write
         * close the write end of parent_write
         */
        close(child_write[0]);
        close(parent_write[1]);

        /* dup2 will swap our stdin(0) and stdout(1)
         * for the pipe 
         *
         * parent_write[0] -> our new stdin(0)
         * child_write[1] -> our new stdout(1)
         */
        dup2(parent_write[0],0);
        dup2(child_write[1],1);

        execv(*cap,cap);

    } else {
        /* close the read end parent_write
         * close the write end of child_write
         */
        close(parent_write[0]);
        close(child_write[1]);

        /* dup2 will swap...
         *
         * parent_write[1] -> new stdout
         * child_write[0] -> new stdin
         */
        dup2(parent_write[1],1);
        dup2(child_write[0],0);

        for(int i = 0; i < 10; i++) {
            buf = 'a' + i;
            write(1,1);
            fprintf(stderr,"sent: %c\n",buf);
            read(0,"got : %c\n",buf);
        }
            
        kill(cpid,1);
        wait(&ws);
    }
    return 0;
}

然后……

$ gcc cap.c -o cap
$ gcc pipe.c -o pipe
$ ./pipe
sent: a
got : A
sent: b
got : B
sent: c
got : C
sent: d
got : D
sent: e
got : E
sent: f
got : F
sent: g
got : G
sent: h
got : H
sent: i
got : I
sent: j
got : J