Linux 3.0: выполнение дочернего процесса с помощью piped stdin / stdout

Под Linux 3.0 / C ++:

Я хотел бы функцию, которая делает следующее:

string f(string s)
{
    string r = system("foo < s");
    return r;
}

Очевидно, что вышесказанное не работает, но вы поняли идею. У меня есть строка s, которую я хотел бы передать в качестве стандартного ввода дочернего процесса выполнения приложения "foo", а затем я хотел бы записать его стандартный вывод в строку r и затем вернуть его.

Какую комбинацию системных вызовов Linux или функций posix мне следует использовать?

 Some programmer dude23 февр. 2012 г., 03:16
Тогда я предлагаю вам посмотреть системные вызовыpipe, fork, dup2 а такжеexec, И, возможно, проверить учебник, такой какэтот.
 Andrew Tomazos23 февр. 2012 г., 03:11
@Joe: Linux является операционной системой. 3.0 - это номер версии. Я указываю ответ конкретной платформы, который работает только в Linux 3.0.0, и более новые версии приемлемы.
 Andrew Tomazos23 февр. 2012 г., 03:10
@R ..: Избавьте нас от ваших личных аннотаций - если вы собираетесь что-то сказать, это плохая идея, добавьте суффикс «потому что <причина>».
 R..23 февр. 2012 г., 04:46
Если ничего другого, вы должны сделать эквивалентsystem но с несколько более разумной обработкой сигнала ... И, вероятно, также пропустите оболочку.
 Joe23 февр. 2012 г., 03:06
Что такое "Linux 3.0"?

Ответы на вопрос(3)

возвращается после ошибки dup вместо выхода. Возможно, ребенка дупса можно заменить на:

    if (dup2(aStdinPipe[PIPE_READ], STDIN_FILENO) == -1 ||
        dup2(aStdoutPipe[PIPE_WRITE], STDOUT_FILENO) == -1 ||
        dup2(aStdoutPipe[PIPE_WRITE], STDERR_FILENO) == -1
        ) 
    {
        exit(errno); 
    }

    // all these are for use by parent only
    close(aStdinPipe[PIPE_READ]);
    close(aStdinPipe[PIPE_WRITE]);
    close(aStdoutPipe[PIPE_READ]);
    close(aStdoutPipe[PIPE_WRITE]);
 Ammo Goettsch31 авг. 2017 г., 01:47
Обновил код выше
 Ammo Goettsch31 авг. 2017 г., 01:08
Ты прав. Дочерний процесс не должен возвращаться из этого стекового фрейма, а также не должен пытаться печатать ошибки. Вероятно, никто не заметил, потому что это никогда не происходит.

вам придется делать то, что popen делает явно за кулисами, с помощью каналов. Я не уверен, что что-то изменится в C ++, но вот чистый пример C:

void piped(char *str){
    int wpipefd[2];
    int rpipefd[2];
    int defout, defin;
    defout = dup(stdout);
    defin = dup (stdin);
    if(pipe(wpipefd) < 0){
            perror("Pipe");
            exit(EXIT_FAILURE);
    }
    if(pipe(rpipefd) < 0){
            perror("Pipe");
            exit(EXIT_FAILURE);
    }
    if(dup2(wpipefd[0], 0) == -1){
            perror("dup2");
            exit(EXIT_FAILURE);
    }
    if(dup2(rpipefd[1], 1) == -1){
            perror("dup2");
            exit(EXIT_FAILURE);
    }
    if(fork() == 0){
            close(defout);
            close(defin);
            close(wpipefd[0]);
            close(wpipefd[1]);
            close(rpipefd[0]);
            close(rpipefd[1]);
            //Call exec here. Use the exec* family of functions according to your need
    }
    else{
            if(dup2(defin, 0) == -1){
                    perror("dup2");
                    exit(EXIT_FAILURE);
            }
            if(dup2(defout, 1) == -1){
                    perror("dup2");
                    exit(EXIT_FAILURE);
            }
            close(defout);
            close(defin);
            close(wpipefd[1]);
            close(rpipefd[0]);
            //Include error check here
            write(wpipefd[1], str, strlen(str));
            //Just a char by char read here, you can change it accordingly
            while(read(rpipefd[0], &ch, 1) != -1){
                    write(stdout, &ch, 1);
            }
    }

}

Эффективно вы делаете это:

Создайте каналы и перенаправьте stdout и stdin на концы двух каналов (обратите внимание, что в linux pipe () создает однонаправленные каналы, поэтому вам нужно использовать две трубы для своих целей).Exec теперь запустит новый процесс, который имеет концы каналов для stdin и stdout.Закройте неиспользуемые дескрипторы, запишите строку в канал и затем начните читать все, что процесс может вывести в другой канал.

dup () используется для создания дублирующейся записи в таблице дескрипторов файлов. В то время как dup2 () изменяет то, на что указывает дескриптор.

Примечание. Как упомянул Ammo @ в своем решении, то, что я предоставил выше, является более или менее шаблоном, он не будет работать, если вы просто попытаетесь выполнить код, поскольку явно отсутствует exec * (семейство функций), поэтому ребенок прекратит работу почти сразу после разветвления ().

 Don Scott25 сент. 2015 г., 23:22
Почему вы сохраняете ссылку на stdin / stdout с dup, когда они впоследствии перенаправляются с dup2? dup2 неявно закрывает для вас второй параметр.
 eerpini03 окт. 2015 г., 02:40
Вы правы, это не нужно. Ответ боеприпасов выше - правильный способ сделать это, пожалуйста, обратитесь к этому.
Решение Вопроса

предоставленный eerpini, не работает как написано. Обратите внимание, например, что концы трубы, которые закрыты в родительском элементе, используются позже. смотреть на

close(wpipefd[1]); 

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

Единственное структурное изменение заключается в том, что я перенаправляю ввод-вывод только в дочернем (обратите внимание, что вызовы dup2 находятся только в дочернем пути). Это очень важно, потому что в противном случае ввод-вывод родительского устройства испортится. Спасибо eerpini за первоначальный ответ, который я использовал при разработке этого.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>

#define PIPE_READ 0
#define PIPE_WRITE 1

int createChild(const char* szCommand, char* const aArguments[], char* const aEnvironment[], const char* szMessage) {
  int aStdinPipe[2];
  int aStdoutPipe[2];
  int nChild;
  char nChar;
  int nResult;

  if (pipe(aStdinPipe) < 0) {
    perror("allocating pipe for child input redirect");
    return -1;
  }
  if (pipe(aStdoutPipe) < 0) {
    close(aStdinPipe[PIPE_READ]);
    close(aStdinPipe[PIPE_WRITE]);
    perror("allocating pipe for child output redirect");
    return -1;
  }

  nChild = fork();
  if (0 == nChild) {
    // child continues here

    // redirect stdin
    if (dup2(aStdinPipe[PIPE_READ], STDIN_FILENO) == -1) {
      exit(errno);
    }

    // redirect stdout
    if (dup2(aStdoutPipe[PIPE_WRITE], STDOUT_FILENO) == -1) {
      exit(errno);
    }

    // redirect stderr
    if (dup2(aStdoutPipe[PIPE_WRITE], STDERR_FILENO) == -1) {
      exit(errno);
    }

    // all these are for use by parent only
    close(aStdinPipe[PIPE_READ]);
    close(aStdinPipe[PIPE_WRITE]);
    close(aStdoutPipe[PIPE_READ]);
    close(aStdoutPipe[PIPE_WRITE]); 

    // run child process image
    // replace this with any exec* function find easier to use ("man exec")
    nResult = execve(szCommand, aArguments, aEnvironment);

    // if we get here at all, an error occurred, but we are in the child
    // process, so just exit
    exit(nResult);
  } else if (nChild > 0) {
    // parent continues here

    // close unused file descriptors, these are for child only
    close(aStdinPipe[PIPE_READ]);
    close(aStdoutPipe[PIPE_WRITE]); 

    // Include error check here
    if (NULL != szMessage) {
      write(aStdinPipe[PIPE_WRITE], szMessage, strlen(szMessage));
    }

    // Just a char by char read here, you can change it accordingly
    while (read(aStdoutPipe[PIPE_READ], &nChar, 1) == 1) {
      write(STDOUT_FILENO, &nChar, 1);
    }

    // done with these in this example program, you would normally keep these
    // open of course as long as you want to talk to the child
    close(aStdinPipe[PIPE_WRITE]);
    close(aStdoutPipe[PIPE_READ]);
  } else {
    // failed to create child
    close(aStdinPipe[PIPE_READ]);
    close(aStdinPipe[PIPE_WRITE]);
    close(aStdoutPipe[PIPE_READ]);
    close(aStdoutPipe[PIPE_WRITE]);
  }
  return nChild;
}
 Ammo Goettsch24 июн. 2018 г., 22:44
Вот суть совершенно ужасного протокола приложения (работает на MacOS, должен работать на Linux), который просто отправляет сообщение hello world и предполагает, что ничего не происходит неправильно. Конечно, вы бы написали настоящий протокол приложения для производственного кода, но, по крайней мере, вы можете увидеть его здесь.gist.github.com/derammo/e2802f9e4a713633901c7c5390388b78
 Brishna Batool23 июн. 2018 г., 21:22
Я пытаюсь сделать что-то подобное, но для меня этот код вызывает, я полагаю, тупик для следующего потока: parent: write, затем read; ребенок: стандартный, затем стандартный. Проблема исчезнет, если я удалю одно из прочтений. Код - именно то, что написал Ammo. (Это не застревает, если родитель читает первым, а ребенок пишет)
 minastaros12 мая 2017 г., 14:53
спасибо, наконец, полный и рабочий кусок кода.
 Ammo Goettsch24 июн. 2018 г., 22:38
О, я понимаю ваш комментарий сейчас. Программа-пример выполняет блокировку чтения (...) на родительской стороне, поэтому фактически блокируется навсегда, если дочерний процесс прекращает отправку чего-либо и не закрывает стандартный вывод. Это выходит за рамки этого примера программы, которая как раз делает получение разветвления и перенаправления правильными. Вам нужно будет написать собственную логику приложения для связи между родителем и ребенком.

Ваш ответ на вопрос