Запуск внешней команды с пользовательским вводом в C - PullRequest
2 голосов
/ 25 октября 2009

Я хочу показать вывод команды Linux dialog --menu из моей программы на C, чтобы пользователь мог выбрать опцию из меню. Кроме того, последняя строка вывода из программы - это опция, выбранная пользователем, поэтому мне нужно зафиксировать это.

Я пытался использовать popen() и system() для достижения этой цели и искал в Интернете, но не смог найти ничего, что привело бы к успешному результату.

Если я не могу найти способ использовать dialog, мне придется использовать гораздо более простой подход (простой «Введите свой выбор и нажмите ввод»), и он не будет таким крутым.

Заранее спасибо, Brian

Ответы [ 4 ]

3 голосов
/ 25 октября 2009

Команда dialog печатает результат выбора пользователя на stderr. Это означает, что вам придется захватывать stderr, а не stdout. Это немного сложно. Я собираюсь проверить это сам, но думаю, что проще всего использовать popen, например:

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

Затем вы можете читать из файла dialog (если, конечно, он не равен NULL).

Это работает, потому что аргумент popen фактически передается для вызова sh. Это означает, что popen действительно запускает sh -c <argument of popen>, поэтому все стандартные перенаправления оболочки работают. Таким образом, вы используете круглые скобки, чтобы получить именно то, что вам нужно, - чтобы сама диалоговая программа отправляла свой вывод на управляющий терминал, а ее stderr перенаправлялся туда, где вы можете прочитать его с помощью popen.

Существует еще один метод, который имеет те же недостатки, что и решение popen, и имеет дополнительный недостаток, заключающийся в необходимости открывать и читать другой файл после завершения диалога. Но у этого есть преимущество простоты. К сожалению, это также требует от вас возможности записи в файловую систему, и наиболее естественное место для этого (/ tmp) чревато проблемами безопасности, связанными с тем, чтобы кто-то еще не каким-то образом не взломал ваш файл. Этот метод должен сделать system("dialog --menu plus other arguments 2>temp_file");. Затем вы читаете из временного_файла, когда это будет сделано.

Они оба немного уродливы, тем более что в диалоге много аргументов, которые могут содержать метасимволы оболочки. Поэтому, даже если вышеуказанное сработает, я настоятельно рекомендую использовать комбинацию pipe, fork, execvp, fdopen и waitpid, чтобы получить желаемый результат.

Скелет для этого будет выглядеть примерно так:

#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;
}
1 голос
/ 25 октября 2009

ну, я сделал это довольно хорошо. см. пример кода ниже:

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

немного скучно плюс небольшая потеря производительности, но выполнимо для всех компонентов диалога, таких как контрольный список, меню и т. Д.

Вы можете контролировать все детали сценариев, которые будут храниться в SCRIPT_FILE, и все действия и управление потоками просты.

0 голосов
/ 25 октября 2009

Вы можете использовать каналы для получения стандартного вывода и ввода данных дочернего приложения (управляется вашим основным приложением). Однако вам нужно будет fork и использовать обычный exec вместо popen или system.

0 голосов
/ 25 октября 2009

Я уверен, что прошу огненную бурю критики, но, тем не менее, вот основная идея. Я не проверял это на ошибки компилятора и не предоставлял файлы заголовков. Это просто фрагмент кода, который я взбил, выпивая.

По сути, вы fork(), в перенаправлении дочернего процесса stderr и вызове exec(), вызове waitpid() в родительском процессе и получение возвращаемого значения «dialog», и если без ошибок прочитайте файл, на который вы перенаправляете 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
   */  
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...