2

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

我想像这样的伪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中只有单向管道

编辑:

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

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

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

输入/输出最大大小估计为数百千字节

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

输入:

5
1 3 4 6 2

预期输出:

16
1 2 3 4 6
4

3 回答 3

4

阅读高级 Linux 编程——其中至少有一整章可以回答你的问题——并了解更多关于execve(2)fork(2)waitpid(2)pipe(2)dup2(2)poll(2) ...

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

顺便说一句,如果您的程序有一些事件循环,它可能会有所帮助(实际上poll是为简单的事件循环提供基础)。Glib(来自 GTK)提供了生成进程的功能,Qt 有QProcesslibevent知道它们等等。

于 2013-10-25T20:25:03.177 回答
2

鉴于处理只是从父母到孩子的一条消息(必须在孩子响应之前完成)和从孩子到父母的一条消息的问题,那么处理起来很容易:

  • 创建两个管道,一个用于与子通信,一个用于与父通信。
  • 叉子。
  • 子进程将管道的相关端(“to-child”管道的读取端,“to-parent”管道的写入端)复制到标准输入、输出。
  • Child 关闭所有管道文件描述符。
  • 子执行测试程序(或打印标准错误报告失败并退出的消息)。
  • 父级关闭管道的不相关端。
  • 父级将消息写入子级并关闭管道。
  • 父级读取子级的响应并关闭管道。
  • 父母继续其快乐的方式。

这使得子进程像僵尸一样四处游荡。如果父级要多次执行此操作,或者只需要知道子级的退出状态,则在关闭读取管道后,它将等待子级死亡,收集其状态。

所有这些都是直截了当的常规编码。我相信您可以在 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);
}

我的程序.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
于 2013-10-26T13:53:30.630 回答
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 更容易为这些类型的目的编程。

于 2013-10-25T20:21:16.353 回答