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
source to share
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;
}
source to share
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.
source to share
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
*/
}
source to share