问题描述
我有一个 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)
另一个,stdin
和 stdout
)。代码已注释。
/* 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