与subprocess标准输出/标准input进行通信

我正在尝试与一个进程进行通信(它本身写入到标准input和标准输出与用户在一个terminal进行交互),并读取它的标准input,并写入它的标准输出在C.

因此,我尝试以编程方式replaceshell用户。 一个美好的例子:想象一下,我想用C中的VIM出于某种原因。 然后我还需要编写命令(stdout)并从编辑器(stdin)读取内容。

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

因为这无处可去,所以我查看了FILE *popen(const char *command, const char *type); 但手册页指出:

由于pipe道的定义是单向的,所以types参数可以只指定读或写,而不是两者; 所得到的stream是相应的只读或只写。

其含义是:

popen()的返回值在所有方面都是标准的I / Ostream,除了它必须用pclose()而不是fclose(3)来closures。 写入这样的stream写入命令的标准input;命令的标准输出 与调用popen()的进程 的标准输出 相同 ,除非这个命令本身被改变了。 相反, 从“popened”stream中读取命令的标准输出命令的标准input与调用popen()的过程相同

因此,使用popen()并不是完全不可能的,但是在我看来,这是非常不雅观的,因为我将不得不parsing调用进程(称为popen()的代码)的stdout,以便parsing从被指挥的命令(当使用popen型'w'时)。

相反,当用'r'types调用popen时,我需要写入调用的进程stdin,以便将数据写入到被调用的命令中。 在这种情况下,我不清楚这两个进程是否在标准input接收相同的数据…

我只需要控制程序的stdin和stdout。 我的意思是不能有这样的function:

 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) 

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

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

  1. int pipe(int fildes[2]);设置两个pipe道int pipe(int fildes[2]); 。 一个pipe道读取进程的标准输出,另一个pipe道写入进程的标准input。
  2. 叉子。
  3. 使用int execvp(const char *file, char *const argv[]);执行我想要在分叉的subprocess中进行通信的进程int execvp(const char *file, char *const argv[]);
  4. 在原始stream程中使用两个pipe道与小孩沟通。

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

我有两个程序。 第一个每100毫秒写一个递增的数字给它的stdout:

 #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"); } } 

现在第二个程序应该读取第一个程序的标准输出(请记住,我最终希望读取和写入一个进程,所以一个技术正确的解决scheme使用popen()作为上层用例可能是正确的在这个特定的情况下,因为我简化了我的实验,只是捕获底层程序的标准输出)。 我期望从底层程序读取上层程序写入标准输出的任何数据。 但它什么都不读。 原因何在? (第二个问题)。

 #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"); } } } } 

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

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

你有正确的想法,我没有时间分析所有的代码来指出具体的问题,但是我想指出一些你可能忽略的有关程序和终端工作的东西。

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

像这样控制vi这样的程序是非常不平凡的。

在您的简化实验…

你的缓冲区是一个字节太小。 另外,请注意IO有时是行缓冲的。 所以,你可以尝试确保新行被转移(使用printf而不是sprintf / strlen / write …你已经将stdout挂钩到你的管道上了),否则在命中新行之前你可能看不到数据。 我不记得管线缓冲,但它是值得一试。