Запустить процесс со строковым вводом и выводом - PullRequest
4 голосов
/ 28 августа 2011

Здесь есть множество вопросов, связанных с fork () и exec ().Я не нашел такого, который действительно облегчает процесс их использования, и цель - сделать жизнь программиста простой.

Мне нужна C ++, дружественная к Linux функция, которая выполняет следующие действия:

string RunCommand(string command, string input){}

Эта функция должна иметь возможность запускать команды оболочки, такие как grep, и "pipe" для содержимоговойдите в него, прочитайте выход и верните его.Так что, если бы я сделал следующее в командной строке:

ps -elf | grep somequerytext

Я бы в коде сделал:

string psOutput = RunCommand("ps -elf","");
string grepOutput = RunCommand("grep somequerytext", psOutput);

* edit: Вопрос в том, какова лучшая реализация RunCommandfunction.

* edit: popen рассматривался как решение для простоты, но popen ограничивает вас передачей или выводом данных, но не обоими.

Ответы [ 3 ]

4 голосов
/ 28 августа 2011

Прежде чем обсуждать реализацию RunCommand, давайте рассмотрим этот фрагмент кода:

string psOutput = RunCommand("ps -elf","");
string grepOutput = RunCommand("grep somequerytext", psOutput);

В приведенном выше фрагменте кода проблема заключается в том, что команды выполняются последовательно, а не параллельно / параллельно. (См. Программирование с потоками POSIX стр.9 ). Например, ps -elf генерирует огромное количество данных, которые будут сохранены в psOutput и затем переданы следующей команде. , Но в реальной реализации каждый процесс в конвейере запускается одновременно, и данные передаются с pipe (с некоторой буферизацией, конечно), и нет необходимости ждать выполнения одного процесса, прежде чем начинать выполнение другого процесса.

Я предлагаю вам взглянуть на Расширенное программирование Ричарда Стивена в среде Unix глава 8 "Управление процессами" стр.223 для реализации system. На основе кода Ричарда Стивена пример реализации RunCommand будет выглядеть следующим образом (только скелетный код, без проверки ошибок):

int RunCommand(string command)
{
    pit_t pid;
    if ( ( pid = fork() ) < 0 ) return -1;
    else if (pid == 0)
    {
        execl("/bin/sh", "sh", "-c", command.c_str(), (char*) 0);
    }
    else
    {
       /* The parent waits for the child */
       wait(pid, ...);
    }
}

и затем можно вызвать вышеуказанные функции как:

string s("ps -elf | grep somequerytext");
int status = RunCommand(s);

Оболочка заботится о синтаксическом анализе своего ввода и выполнении команд, устанавливая pipe s между ними. Если вы заинтересованы в понимании того, как реализована оболочка, см. «Пример Minishell» в Terrence Chan Системное программирование Unix с использованием C ++ глава 8 «Процессы Unix» ( ответ Джонатана Леффлера довольно много описывает реализацию оболочки!)

4 голосов
/ 28 августа 2011

Похоже, вам нужна функция для:

  • Создать две трубы и вилку.
  • Затем дочерний процесс выполняет:
    • Дублируйте соответствующие дескрипторы каналов, чтобы один файловый дескриптор был стандартным вводом, а один стандартный вывод
    • Закрыть дескрипторы канала
    • Разделить командную строку на аргументы
    • Запустить команду с аргументами
  • Затем родительский (основной) процесс выполняет:
    • Закрыть соответствующие дескрипторы файла канала
    • Записывает входную строку в дочерний элемент и закрывает канал для стандартного входного элемента дочернего элемента
    • Считывает выходную строку из дочернего элемента
    • Закрывает трубу со стандартного вывода ребенка
    • ждет смерти ребенка
  • Когда дочерний элемент мертв, основной процесс может продолжаться, возвращая прочитанную строку.

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

Одной из причин того, что для этого нет стандартной функции, является именно трудность определения подходящей семантики.

Я проигнорировал проблемы с обработкой ошибок и обработкой сигналов; они добавляют к сложности всего этого.

0 голосов
/ 28 августа 2011

Почему бы не использовать popen()?Он находится в стандартной библиотеке и очень прост в использовании:

FILE* f = popen("ps -elf | grep somequerytext", "r");
char buf[2048];
buf[fread(buf, 1, 2048, f)] = '\0';
cout << buf;
...