Running an external command with user input in C

I want to show the output of a Linux command dialog --menu

from my C program so that the user can select an option from the menu. Also, the last line of output from the program is the option the user selected, so I need to grab it.

I tried using popen()

and system()

to accomplish this and looked on the internet but couldn't find anything that was successful.

If I can't find a way to use it dialog

, I'll have to take a much simpler approach (simple "Type your selection and hit enter") and it won't be that cool.

Thanks in advance, Brian

+2


source to share


4 answers


The command dialog

prints the result of the user's selection to stderr. This means that you have to grab stderr, not stdout. It's a little tricky. I'm going to test this myself, but I guess the easiest to use is popen

this:

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

      

Then you can read from the file dialog

(unless it's NULL, of course).

This works because the popen argument is actually passed to the call sh

. This means popen actually works sh -c <argument of popen>

, which is why all the standard redirects work. So you use parentheses to get exactly what you want, which is for the dialog program itself to send its output to the controlling terminal and its stderr to be redirected to where you can read it with popen.



There is another method that has the same disadvantages as the solution popen

, and has the additional disadvantage that you have to open and read another file after the dialog ends. But this has the advantage of being simple. Unfortunately, this also requires you to be able to write to the filesystem, and the most natural place to do this (/ tmp) is fraught with security issues related to someone else not hijacking your file in some way. This method should follow system("dialog --menu plus other arguments 2>temp_file");

. Then you read from temp_file when it's done.

They're both a bit ugly, especially since the dialog contains a lot of arguments that shell metacharacters can have in them. Therefore, even if the above work, I highly recommend using a combination of pipe

, fork

, execvp

, fdopen

and waitpid

to get the result you're after.

The skeleton for this looks something like this:

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

      

+3


source


Ok, I did it pretty well. see example code below:

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

      



a bit boring plus a slight loss of performance, but this is possible for all dialog components like checklist and menus, etc.

You can control all the details of the scripts that will be stored in SCRIPT_FILE, and overall action and flow control is simple.

+1


source


I'm sure I'm asking for a firestorm of criticism, but here's the main idea nonetheless. I have not tested this for compiler errors, nor have I supplied any header files. This is just a piece of code that I whipped up while drinking.

Basically, you fork()

, in the child process, redirect stderr and call exec()

, call waitpid()

into the parent process and get the return value "dialog", and if there was no error, read the file you are redirecting stderr to .

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


source


You can use pipes to receive standard output and input from a child application (managed by your main application). However, you will need to fork

use regular exec

and not popen or system.

0


source







All Articles