2009-10-25 35 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中,整個操作和流程控制很簡單。