使用C中的用户输入运行外部命令

时间:2009-10-25 04:26:11

标签: c linux

我想从我的C程序中显示Linux命令dialog --menu的输出,以便用户可以从菜单中选择一个选项。此外,程序的最后一行输出是用户选择的选项,因此我需要捕获它。

我尝试使用popen()system()来完成此操作,并在网上查看,但找不到任何可以带来成功结果的内容。

如果我找不到使用dialog的方法,我将不得不使用更简单的方法(简单的“输入你的选择并按回车”),它就不会那么酷。< / p>

提前谢谢你, 布赖恩

4 个答案:

答案 0 :(得分:3)

dialog命令打印用户在stderr上的选择结果。这意味着你必须捕获stderr,而不是stdout。这有点棘手。我将自己去测试一下,但我的猜测是最简单的方法是使用popen这样:

FILE *dialog = popen("(dialog --menu plus other arguments >/dev/tty) 2>&1");

然后你可以从dialog文件中读取(如果它当然不是NULL)。

这是有效的,因为popen的参数实际上是传递给sh的调用。这意味着popen确实运行sh -c <argument of popen>所以所有标准shell重定向都有效。因此,您可以使用括号来获得您想要的内容,这是对话框程序本身将其输出发送到其控制终端,并将其stderr重定向到您可以使用popen读取它的位置。

还有另一种方法与popen解决方案具有相同的缺点,并且还有一个缺点是需要在对话框完成后打开并读取另一个文件。但它具有简单的优点。不幸的是,它还要求您能够写入文件系统,而最自然的地方(/ tmp)充满了与确保其他人不以某种方式劫持您的文件相关的安全问题。该方法是system("dialog --menu plus other arguments 2>temp_file");。然后在完成后从temp_file中读取。

这些都有点难看,特别是因为对话框需要很多可能在其中包含shell元字符的参数。因此,即使进行了上述工作,我强烈建议您使用pipeforkexecvpfdopenwaitpid的组合来获得结果'之后。

这样的骨架看起来像这样:

#include <stdio.h>
#include <stddef.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>

int main(int argc, const char *argv[])
{
   const char *dia_args[] = {
      "dialog",
      "--output-fd",
      NULL,
      "--menu",
      "Hi there",
      "60", "15", "15",
      "t1", "i1",
      "t2", "i2",
      "t3", "i3",
      "t4", "i4",
      "t5", "i5",
      NULL
   };
   int pipefds[2];

   if (pipe(pipefds) < 0) {
      perror("pipe failed");
      return 1;
   } else {
      const pid_t child = fork();
      if (child < 0) {
         perror("fork failed");
         return 1;
      } else if (child == 0) {
         char pipefdstr[60];
         close(pipefds[0]);
         if (snprintf(pipefdstr, sizeof(pipefdstr) - 1, "%u", pipefds[1]) < 0) {
            perror("snprintf failed");
            return 1;
         } else {
            pipefdstr[sizeof(pipefdstr) - 1] = '\0'; /* Protect against bugs in snprintf */
            dia_args[2] = pipefdstr;
            execvp(dia_args[0], dia_args);
            perror("Unable to exec dialog command");
            return 1;
         }
      } else { /* child > 0 */
         FILE *dialog = fdopen(pipefds[0], "r");
         char inbuf[200];
         int waitresult = 0;
         if (dialog == NULL) {
            perror("Unable to fdopen");
            kill(child, SIGKILL);
            return 1;
         }
         close(pipefds[1]);
         while (fgets(inbuf, sizeof(inbuf) - 1, dialog)) {
            inbuf[sizeof(inbuf) - 1] = '\0';
            printf("Got [%s] from dialog.\n", inbuf);
         }
         fclose(dialog);
         if (waitpid(child, &waitresult, 0) < 0) {
            perror("waitpid failed");
            return 1;
         }
         if (!WIFEXITED(waitresult) || (WEXITSTATUS(waitresult) != 0)) {
            fprintf(stderr, "dialog exited abnormally.");
            return 1;
         }
      }
   }
   return 0;
}

答案 1 :(得分:1)

好吧,我做得很好。请参阅下面的代码示例:

fp = fopen(SCRIPT_FILE, "w+");
fprintf(fp,
    "#!/bin/sh\\n\\n"
    "dialog --clear --title \""BRAND_INFO"\""
    " --inputbox \""TITLE"\\n\\n"
    "Please input the name[/tmp/input]:\" "
    BOX_HEIGHT" "BOX_WIDTH" 2> "RESULT_FILE"\n");
fclose(fp);
system("bash "SCRIPT_FILE); 
fp = fopen(RESULT_FILE, "rt");
// here read the results from the RESULT_FILE 
//    into which dialog had written the results.
...

有点无聊加上一点性能损失,但对于清单和菜单等对话框的所有组成部分都是可行的。

您可以控制将存储在SCRIPT_FILE中的脚本的所有详细信息,并且整个操作和流控制很简单。

答案 2 :(得分:0)

我确定我要求批评的风暴,但这是基本的想法。我没有检查这个编译器错误,也没有提供头文件。这只是我在喝酒时掀起的代码片段。

基本上,您fork()在子进程中重定向 stderr 并调用exec(),在父进程中调用waitpid()并获取返回值“对话框,“如果没有错误,请阅读您重定向的文件 stderr

pid_t pid;
char cmd[] = "dialog";
char *args[] = {"dialog", "--menu", NULL};
int status;
int fd;

if((pid = fork()) == 0)
{
  /* child */

  /* I believe dialog prints to stderr the answer chosen 
   * also you should check the return value of open()
   */
  fd = open("some_file_name", O_WRONLY | O_CREAT | O_TRUNC, 0);

  close(STDERR_FILENO);

  dup(fd);

  execvp(cmd, args);
  perror("execvp()");
  exit(1);
}
else if(pid < 0)
{
  perror("fork()");
  exit(1);
}
else
{
  /* parent */

  /* you should also check the return of waitpid() 
   * this is just for example
   */
  waitpid(pid, &status, 0);

  /* if everything was ok then status has the return value 
   * also the file "some_file_name" should have the output
   */  
}

答案 3 :(得分:0)

您可以使用管道获取标准输出并提供子应用程序输入(由主应用程序控制)。您需要fork并使用传统的exec而不是popen或系统。