最简单的方法来执行linux程序并通过C / C ++中的stdin / stdout与它通信

时间:2013-10-25 19:50:13

标签: c linux exec ipc

我有程序我无法修改,,我需要执行它,将一些数据写入其标准输入并以编程方式从其标准输出中获得答案,自动化。 什么是最简单的方法?

我想像这样的伪C代码

char input_data_buffer[] = "calculate 2 + 2\nsay 'hello world!'";
char output_data_buffer[MAX_BUF];
IPCStream ipcs = executeIPC("./myprogram", "rw");
ipcs.write(input_data_buffer);
ipcs.read(output_data_buffer);
...
PS:我想过popen,但是AFAIK在linux中只有单向管道

修改

假设它将是来自每一方的一个消息通信。首先,父端向子进程'stdin发送输入,然后child向其stdout提供输出并退出,同时parent读取其stdout。现在关于通信终止:我认为当子进程退出时它会将EOF终结符发送到stdout,因此父进程将确切知道子进程是否完成,另一方面保证父进程知道子进程需要什么样的输入。

通常这个程序(父母) - 学生的解决方案测试员。它从CLI获取另外两个可执行文件的路径,第一个是学生的测试程序,第二个是标准正常工作的程序,它解决了同样的问题。

学生程序的输入/输出是严格规定的,因此测试人员运行两个程序并比较其输出的大量随机输入,所有不匹配将被报告。

输入/输出最大尺寸估计为几百千字节

示例:..实现插入排序算法...第一行有序列长度...第二行有数字序列a_i其中| a_i | < 2 ^ 31 - 1 ...... 输出第一行必须是所有元素的总和,第二行必须是排序顺序。

输入:

5
1 3 4 6 2

预期产出:

16
1 2 3 4 6

3 个答案:

答案 0 :(得分:4)

阅读Advanced Linux Programming - 至少有一整章来回答您的问题 - 并详细了解execve(2)fork(2)waitpid(2)pipe(2)dup2(2)poll(2) ...

请注意,您需要(至少在单线程程序中)对程序的输入和输出进行多路复用(使用poll)。否则您可能会遇到死锁:子进程可能被阻止写入您的程序(因为输出管道已满),并且您的程序可能被阻止从中读取(因为输入管道为空)。

顺便说一句,如果你的程序有一些event loop它可能会有所帮助(实际上poll正在为简单的事件循环提供基础)。 Glib(来自GTK)为spawn processes提供功能,Qt有QProcesslibevent知道它们等等。

答案 1 :(得分:2)

鉴于处理只是一个父母与孩子之间的消息问题(必须在孩子回应之前完成),以及从孩子到父母之间的一条消息,那么很容易处理:

  • 创建两个管道,一个用于与孩子沟通,一个用于与父母沟通。
  • 叉。
  • 子进程复制管道的相关末尾(读取'to-child'管道的末尾,将'to-parent'管道的末尾写入)到标准输入,输出。
  • Child关闭所有管道文件描述符。
  • Child execs测试程序(或打印消息以标准错误报告失败并退出)。
  • 父关闭管道的无关端。
  • Parent将消息写入子节点并关闭管道。
  • Parent从子项中读取响应并关闭管道。
  • 家长继续快乐。

这使得儿童进程像僵尸一样躺在身边。如果父母要多次这样做,或者只是需要知道孩子的退出状态,那么在关闭阅读管道之后,它将等待孩子死亡,收集其状态。

所有这些都是直截了当的常规编码。我相信你可以在SO上找到例子。


由于Stack Overflow显然没有合适的例子,所以这里是上面概述的代码的简单实现。有两个源文件,basic_pipe.c用于基本管道工作,myprogram.c应该响应问题中显示的提示。第一个几乎是一般目的;它可能应该循环读取操作(但在我测试它的机器上没有关系,这是运行Ubuntu 14.04衍生物)。第二个非常专业。

系统调用

basic_pipe.c

#include <assert.h>
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>

static char msg_for_child[] = "calculate 2 + 2\nsay 'hello world!'\n";
static char cmd_for_child[] = "./myprogram";

static void err_syserr(const char *fmt, ...);
static void be_childish(int to_child[2], int fr_child[2]);
static void be_parental(int to_child[2], int fr_child[2], int pid);

int main(void)
{
  int to_child[2];
  int fr_child[2];
  if (pipe(to_child) != 0 || pipe(fr_child) != 0)
    err_syserr("Failed to open pipes\n");
  assert(to_child[0] > STDERR_FILENO && to_child[1] > STDERR_FILENO &&
         fr_child[0] > STDERR_FILENO && fr_child[1] > STDERR_FILENO);
  int pid;
  if ((pid = fork()) < 0)
    err_syserr("Failed to fork\n");
  if (pid == 0)
    be_childish(to_child, fr_child);
  else
    be_parental(to_child, fr_child, pid);
  printf("Process %d continues and exits\n", (int)getpid());
  return 0;
}

static void be_childish(int to_child[2], int fr_child[2])
{
  printf("Child PID: %d\n", (int)getpid());
  fflush(0);
  if (dup2(to_child[0], STDIN_FILENO) < 0 ||
      dup2(fr_child[1], STDOUT_FILENO) < 0)
    err_syserr("Failed to set standard I/O in child\n");
  close(to_child[0]);
  close(to_child[1]);
  close(fr_child[0]);
  close(fr_child[1]);
  char *args[] = { cmd_for_child, 0 };
  execv(args[0], args);
  err_syserr("Failed to execute %s", args[0]);
  /* NOTREACHED */
}

static void be_parental(int to_child[2], int fr_child[2], int pid)
{
  printf("Parent PID: %d\n", (int)getpid());
  close(to_child[0]);
  close(fr_child[1]);
  int o_len = sizeof(msg_for_child) - 1; // Don't send null byte
  if (write(to_child[1], msg_for_child, o_len) != o_len)
    err_syserr("Failed to write complete message to child\n");
  close(to_child[1]);
  char buffer[4096];
  int nbytes;
  if ((nbytes = read(fr_child[0], buffer, sizeof(buffer))) <= 0)
    err_syserr("Failed to read message from child\n");
  close(fr_child[0]);
  printf("Read: [[%.*s]]\n", nbytes, buffer);
  int corpse;
  int status;
  while ((corpse = waitpid(pid, &status, 0)) != pid && corpse != -1)
    err_syserr("Got pid %d (status 0x%.4X) instead of pid %d\n",
               corpse, status, pid);
  printf("PID %d exited with status 0x%.4X\n", pid, status);
}

static void err_syserr(const char *fmt, ...)
{
  int errnum = errno;
  va_list args;
  va_start(args, fmt);
  vfprintf(stderr, fmt, args);
  va_end(args);
  if (errnum != 0)
    fprintf(stderr, "(%d: %s)\n", errnum, strerror(errnum));
  exit(EXIT_FAILURE);
}

myprogram.c

#include <stdio.h>

int main(void)
{
  char buffer[4096];
  char *response[] =
  {
    "4",
    "hello world!",
  };
  enum { N_RESPONSES = sizeof(response)/sizeof(response[0]) };

  for (int line = 0; fgets(buffer, sizeof(buffer), stdin) != 0; line++)
  {
    fprintf(stderr, "Read line %d: %s", line + 1, buffer);
    if (line < N_RESPONSES)
    {
      printf("%s\n", response[line]);
      fprintf(stderr, "Sent line %d: %s\n", line + 1, response[line]);
    }
  }
  fprintf(stderr, "All done\n");

  return 0;
}

示例输出

请注意,无法保证孩子在父母开始执行be_parental()功能之前完成。

Child PID: 19538
Read line 1: calculate 2 + 2
Sent line 1: 4
Read line 2: say 'hello world!'
Sent line 2: hello world!
All done
Parent PID: 19536
Read: [[4
hello world!
]]
PID 19538 exited with status 0x0000
Process 19536 continues and exits

答案 2 :(得分:0)

您可以使用expect来实现此目的:   http://en.wikipedia.org/wiki/Expect

这是通常期望的程序会做的事情:

# Start the program
spawn <your_program>
# Send data to the program
send "calculate 2 + 2"
# Capture the output
set results $expect_out(buffer)

Expect可以在使用expect开发库的C程序中使用,因此您可以将以前的命令直接转换为C函数调用。这里有一个例子:

http://kahimyang.info/kauswagan/code-blogs/1358/using-expect-script-cc-library-to-manage-linux-hosts

你也可以在perl和python中使用它,它通常比C更容易为这些目的编程。