2009-10-25 36 views
2

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

我试过使用popen()system()来实现这一点,并在网上查找,但找不到任何能够导致成功结果的东西。

如果我找不到使用dialog的方法,我将不得不使用一种更简单的方法(一种简单的“输入您的选择并按回车”),它不会很酷。

谢谢你在前进, 布赖恩

回答

3

dialog命令在stderr上打印用户选择的结果。这意味着你将不得不捕获标准错误,而不是标准输出。这有点棘手。我要去这个测试自己,但我的猜测是最容易做的事情是使用popen这样的:

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

然后你可以从dialog文件读取(只要它不为空,当然)。

这是可行的,因为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元字符。因此,即使上述工作,我强烈建议使用pipe,fork,execvp,fdopenwaitpid的组合以获得您所追求的结果。

,一种在骨骼会是这个样子:

#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; 
} 
+0

我使用了第一个选项,它工作得很好。 非常感谢。 – HalfBrian 2009-10-25 17:25:22

+0

使用'fgets(inbuf,sizeof(inbuf),对话框)'not'sizeof(inbuf) - 1'。阅读fgets的手册 – user102008 2010-12-22 00:24:42

+0

@ user102008 - 即使我知道它可能是'sizeof(inbuf)',我仍然会使用'sizeof(inbuf) - 1'来防止'fgets'中的实现错误。 – Omnifarious 2010-12-22 15:42:36

0

我敢肯定,我要求批评了轩然大波但尽管如此这里的基本思想。我没有检查编译器错误,也没有提供头文件。这只是我在喝酒的时候掀起的一段代码片段。

基本上,你fork(),子进程中的重定向标准错误,并呼吁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 
    */ 
} 
+0

一个合理简单的解决方案。一个可能的增强将是'从文件系统中'unlink()'fd,以便只有孩子和父母可以访问它,并且使用'mkstemp()来随机化文件名 – 2009-10-29 10:31:44

0

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

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. 
... 

有点沉闷再加上一点点perfromance损失,但是对于可行,如清单和菜单等对话的所有组件

你可以控制这将是脚本的所有细节存储在SCRIPT_FILE中,整个操作和流程控制很简单。