Подтвердить что ты не робот

Одновременное чтение и запись

Можно ли читать и писать в дескриптор файла, возвращенный popen. У меня есть интерактивный процесс, который я бы хотел контролировать с помощью C. Если это невозможно с помощью popen, есть ли какой-нибудь способ?

4b9b3361

Ответ 1

Как уже было сказано, popen работает в одном направлении. Если вам нужно читать и писать, вы можете создать канал с помощью pipe(), развернуть новый процесс с помощью функций fork() и exec, а затем перенаправить свои входы и выходы с помощью dup2(). Во всяком случае, я предпочитаю exec поверх popen, так как он дает вам лучший контроль над процессом (например, вы знаете его pid)

Редакция:

Как следует из комментариев, труба может использоваться только в одном направлении. Поэтому вам нужно создавать отдельные каналы для чтения и записи. Поскольку предыдущий пример был неправильным, я удалил его и создал новый, правильный:

#include<unistd.h>
#include<sys/wait.h>
#include<sys/prctl.h>
#include<signal.h>
#include<stdlib.h>
#include<string.h>
#include<stdio.h>

int main(int argc, char** argv)
{
  pid_t pid = 0;
  int inpipefd[2];
  int outpipefd[2];
  char buf[256];
  char msg[256];
  int status;

  pipe(inpipefd);
  pipe(outpipefd);
  pid = fork();
  if (pid == 0)
  {
    // Child
    dup2(outpipefd[0], STDIN_FILENO);
    dup2(inpipefd[1], STDOUT_FILENO);
    dup2(inpipefd[1], STDERR_FILENO);

    //ask kernel to deliver SIGTERM in case the parent dies
    prctl(PR_SET_PDEATHSIG, SIGTERM);

    //replace tee with your process
    execl("/usr/bin/tee", "tee", (char*) NULL);
    // Nothing below this line should be executed by child process. If so, 
    // it means that the execl function wasn't successfull, so lets exit:
    exit(1);
  }
  // The code below will be executed only by parent. You can write and read
  // from the child using pipefd descriptors, and you can send signals to 
  // the process using its pid by kill() function. If the child process will
  // exit unexpectedly, the parent process will obtain SIGCHLD signal that
  // can be handled (e.g. you can respawn the child process).

  //close unused pipe ends
  close(outpipefd[0]);
  close(inpipefd[1]);

  // Now, you can write to outpipefd[1] and read from inpipefd[0] :  
  while(1)
  {
    printf("Enter message to send\n");
    scanf("%s", msg);
    if(strcmp(msg, "exit") == 0) break;

    write(outpipefd[1], msg, strlen(msg));
    read(inpipefd[0], buf, 256);

    printf("Received answer: %s\n", buf);
  }

  kill(pid, SIGKILL); //send SIGKILL signal to the child process
  waitpid(pid, &status, 0);
}

Ответ 2

Причина popen() и друзья не предлагают двунаправленную связь, так это то, что она будет тупиковой, из-за буферизации в подпроцессе. Все временные трубопроводы и решения socketpair(), обсуждаемые в ответах, страдают от одной и той же проблемы.

В UNIX большинству команд нельзя доверять, чтобы читать одну строку и немедленно обрабатывать ее и печатать, за исключением случаев, когда их стандартный вывод является tty. Причина в том, что stdio-буферы выводятся в пользовательском пространстве по умолчанию и отменяют системный вызов write() до тех пор, пока не будет заполнен буфер или поток stdio не будет закрыт (как правило, из-за того, что программа или script собирается выйти после просмотра EOF вход). Если вы пишете в такую ​​программу stdin через трубу и теперь ждите ответа от этой программы stdout (без закрытия входящего канала), ответ застрял в буферах stdio и никогда не выйдет - это тупик.

Вы можете обмануть некоторые линейно ориентированные программы (например, grep), чтобы не буферизировать, используя псевдо-tty, чтобы разговаривать с ними; посмотрите libexpect(3). Но в общем случае вам придется повторно запустить другой подпроцесс для каждого сообщения, позволяя использовать EOF для сигнализации конца каждого сообщения и вызывать сброс всех буферов в команде (или конвейере команд). Очевидно, что это не очень хорошая работа.

Подробнее об этой проблеме читайте на странице perlipc (для двунаправленных каналов в Perl, но соображения буферизации применяются независимо от языка, используемого для основная программа).

Ответ 4

popen() может открыть канал только в режиме чтения или записи, а не в обоих. Посмотрите этот поток для обхода.

Ответ 5

Используйте forkpty (он нестандартен, но API очень приятный, и вы всегда можете отказаться от своей собственной реализации, если у вас его нет) и exec программу, с которой вы хотите общаться в дочерний процесс.

В качестве альтернативы, если семантика tty вам не по душе, вы можете написать что-то вроде forkpty, но используя два канала: по одному для каждого направления связи или используя socketpair для связи с внешней программой через unix-сокет.

Ответ 6

Вы не можете использовать popen для использования двухсторонних каналов.

Фактически, некоторые ОС не поддерживают двусторонние каналы, и в этом случае единственная возможность - это сокет-пара (socketpair).

Ответ 7

В одном из netresolve backends Я разговариваю с script, поэтому мне нужно написать его stdin и читать из stdout. Следующая функция выполняет команду с stdin и stdout, перенаправленными на канал. Вы можете использовать его и адаптировать по своему вкусу.

static bool
start_subprocess(char *const command[], int *pid, int *infd, int *outfd)
{
    int p1[2], p2[2];

    if (!pid || !infd || !outfd)
        return false;

    if (pipe(p1) == -1)
        goto err_pipe1;
    if (pipe(p2) == -1)
        goto err_pipe2;
    if ((*pid = fork()) == -1)
        goto err_fork;

    if (*pid) {
        /* Parent process. */
        *infd = p1[1];
        *outfd = p2[0];
        close(p1[0]);
        close(p2[1]);
        return true;
    } else {
        /* Child process. */
        dup2(p1[0], 0);
        dup2(p2[1], 1);
        close(p1[0]);
        close(p1[1]);
        close(p2[0]);
        close(p2[1]);
        execvp(*command, command);
        /* Error occured. */
        fprintf(stderr, "error running %s: %s", *command, strerror(errno));
        abort();
    }

err_fork:
    close(p2[1]);
    close(p2[0]);
err_pipe2:
    close(p1[1]);
    close(p1[0]);
err_pipe1:
    return false;
}

https://github.com/crossdistro/netresolve/blob/master/backends/exec.c#L46

(Я использовал тот же код в Может ли popen() создавать двунаправленные каналы, например pipe() + fork()?)

Ответ 8

popen работает для меня в обоих направлениях (чтение и запись) Я использовал трубку popen() в обоих направлениях.

Чтение и запись дочернего процесса stdin и stdout с файловым дескриптором, возвращаемым popen (команда, "w" )

Кажется, что все нормально.

Я предположил, что это сработает, прежде чем я узнаю, и это так. По сообщениям выше это не должно работать. Меня это немного беспокоит.

gcc на raspbian (raspbery pi debian)