【发布时间】:2014-02-14 19:10:22
【问题描述】:
因此我尝试以编程方式替换 shell 用户。一个比喻的例子:想象一下,出于某种原因,我想在 C 中使用 VIM。然后我还需要编写命令(stdout)并从编辑器(stdin)读取内容。
最初我认为这可能是一项微不足道的任务,但似乎没有标准方法。 int system(const char *command); 只是执行一个命令并将命令 stdin/stdout 设置为调用进程之一。
因为这无济于事,我查看了FILE *popen(const char *command, const char *type);,但手册页指出:
因为根据定义,管道是单向的,类型参数可以只指定读或写,不能同时指定;结果流是相应的只读或只写。
及其含义:
popen() 的返回值在所有方面都是一个普通的标准 I/O 流,除了它必须用 pclose() 而不是 fclose(3) 关闭。 写入这样的流会写入标准输入 命令的;命令的标准输出与调用popen()的进程的相同,除非这被命令本身改变。相反,从“弹出”流中读取 命令的标准输出,命令的标准输入与调用popen()的进程相同。
因此使用popen() 并非完全不可能,但在我看来它非常不雅,因为我必须解析调用进程的标准输出(调用popen() 的代码)才能解析从 popen 命令发送的数据(使用 popen 类型 'w' 时)。
相反,当使用类型 'r' 调用 popen 时,我需要写入调用的进程标准输入,以便将数据写入 popened 命令。在这种情况下,我什至不清楚这两个进程是否在标准输入中接收到相同的数据......
我只需要控制一个程序的标准输入和标准输出。我的意思是不能有这样的功能:
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)
所以我的第一个问题:实现上层功能的最佳方式是什么?
目前我正在尝试以下方法与另一个进程进行通信:
- 使用
int pipe(int fildes[2]);设置两个管道。一个管道读取进程的标准输出,另一个管道写入进程的标准输入。 - 叉子。
- 使用
int execvp(const char *file, char *const argv[]);在分叉的子进程中执行我想与之通信的进程。 - 在原流程中使用两个管道与孩子通信。
这很容易说机器人实现起来并不那么简单(至少对我来说)。在一种情况下,我奇怪地设法做到了,但是当我试图用一个更简单的例子来理解我在做什么时,我失败了。这是我目前的问题:
我有两个程序。第一个只是每 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");
}
}
现在第二个程序应该读取第一个程序的标准输出(请记住,我最终想用一个进程来读写,所以在上层用例中使用 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");
}
}
}
}
【问题讨论】: