2009-10-25 4 views
2

Je souhaite afficher la sortie de la commande Linux dialog --menu à partir de mon programme C afin que l'utilisateur puisse sélectionner une option dans le menu. En outre, la dernière ligne de sortie du programme est l'option sélectionnée par l'utilisateur, donc j'ai besoin de capturer cela.Exécution d'une commande externe avec entrée utilisateur en C

J'ai essayé d'utiliser popen() et system() pour ce faire, et j'ai regardé sur le web, mais je n'ai rien trouvé qui ait abouti à un résultat satisfaisant.

Si je ne peux pas trouver un moyen d'utiliser dialog, je vais devoir utiliser une approche beaucoup plus simple (un simple "Tapez votre choix et appuyez sur Entrée") et ce ne sera pas aussi cool.

Nous vous remercions à l'avance, Brian

Répondre

3

La commande dialog imprime le résultat des sélections de l'utilisateur sur stderr. Cela signifie que vous devrez capturer stderr, pas stdout. C'est un peu difficile. Je vais aller tester moi-même, mais je pense que la meilleure chose à faire est d'utiliser popen comme ceci:

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

Ensuite, vous pouvez lire à partir du fichier dialog (à condition qu'il n'est pas NULL bien sûr).

Cela fonctionne parce que l'argument de popen est effectivement passé à un appel de sh. Cela signifie que popen fonctionne vraiment sh -c <argument of popen> donc toutes les redirections de shell standard fonctionnent. Donc, vous utilisez des parenthèses pour obtenir exactement ce que vous voulez, qui est pour le programme de dialogue lui-même d'envoyer sa sortie à son terminal de contrôle, et pour son stderr d'être redirigé vers l'endroit où vous pouvez le lire avec popen.

Une autre méthode présente les mêmes inconvénients que la solution popen et présente l'inconvénient supplémentaire de vous demander d'ouvrir et de lire un autre fichier une fois la boîte de dialogue terminée. Mais il a l'avantage de la simplicité. Malheureusement, il vous faut aussi être capable d'écrire sur le système de fichiers, et l'endroit le plus naturel pour le faire (/ tmp) est chargé de problèmes de sécurité pour s'assurer que quelqu'un d'autre ne détourne pas votre fichier. Cette méthode consiste à faire system("dialog --menu plus other arguments 2>temp_file");. Ensuite, vous lisez depuis temp_file quand c'est fait.

Ce sont tous deux un peu moche, d'autant plus que la boîte de dialogue prend beaucoup d'arguments susceptibles d'avoir des métacaractères de shell.Donc, même si le travail ci-dessus, je recommande fortement d'utiliser une combinaison de pipe, fork, execvp, fdopen, et waitpid pour obtenir le résultat que vous recherchez.

Un squelette pour cela ressemblerait à quelque chose comme ceci:

#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

J'ai utilisé la première option, cela fonctionnait parfaitement. Merci beaucoup. – HalfBrian

+0

utilise 'fgets (inbuf, sizeof (inbuf), boîte de dialogue)' pas 'sizeof (inbuf) - 1'. lisez le manuel pour les fgets – user102008

+0

@ user102008 - Même si je savais que ça pourrait être 'sizeof (inbuf)' j'utiliserais toujours 'sizeof (inbuf) - 1' pour me protéger contre les erreurs d'implémentation dans' fgets'. – Omnifarious

0

Je suis sûr que je demande une tempête de feu de la critique, mais est néanmoins ici l'idée de base. Je n'ai pas vérifié ceci pour les erreurs de compilateur et n'ai pas fourni les dossiers d'en-tête. C'est juste un extrait de code que j'ai fouetté en prenant un verre.

Fondamentalement, vous fork(), dans le processus de l'enfant redirigez stderr et appelez exec(), appelez waitpid() dans le processus parent et obtenir la valeur de retour de « dialogue », et s'il n'y avait pas d'erreur de lire le fichier dans lequel vous redirigez 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

une solution relativement simple. une amélioration possible serait 'unlink()' fd du système de fichiers afin que seulement l'enfant et le parent puissent y accéder, et en utilisant 'mkstemp() pour randomiser le nom de fichier utilisé –

0

Vous pouvez utiliser des tuyaux pour obtenir la sortie standard et donner à l'entrée de l'application de l'enfant (contrôlé par l'application principale). Vous auriez besoin de fork et d'utiliser un exec conventionnel plutôt que le système ou le système, cependant.

1
bien

, je l'avais fait assez bien. voir l'exemple de code ci-dessous:

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

un peu ennuyeux et un peu la perte de perfromance mais faisable pour tous les composants de dialogue tels que liste de contrôle des menus, etc.

Vous pouvez contrôler tous les détails des scripts qui seraient stocké dans le SCRIPT_FILE, et le contrôle global de l'action et du flux est simple.

Questions connexes