Буквально вчера один человек задал мне вопрос, на который я не смог сходу ответить: как получить вывод дочернего процесса и записать его в строку? Другими словами, необходимо запустить какую-то внешнюю программу, и прочитать её вывод. Всё это должно быть реализовано программно, на Си. Судя по всему, решения могут варьироваться от одной операционной системы к другой, так, я немного повозился с данной задачей. Здесь я покажу, как это можно сделать в Unix при помощи каналов.
Анонимные каналы (anonymous pipes) – один из способов взаимодействия процессов. Мы можем записывать данные в канал на одном конце (в одном процессе), а считывать на другом конце (в другом процессе). Каналы – важная часть Unix-подобных операционных систем, они позволяют организовывать цепочки действий, когда вывод одной программы поступает на вход другой, и так далее.
В Unix shell анонимный канал создаётся при помощи символа прямого слеша |. Например:
$ ps -A | grep init 1 ? 00:00:01 init
Здесь стандартный вывод ps перенаправляется на стандартный ввод grep.
Вернёмся к нашей задаче. Нам нужно запустить дочерний процесс, который запустит внешнюю программу. Это мы проделаем при помощи fork(). По-умолчанию stdout дочернего процесса направлен на экран. Но нам нужно записать эти данные в строковой буфер, чтобы потом как-то с ними работать. Тут нам на помощь и приходят анонимные каналы. Мы создаём такой канал при помощи pipe(), а затем перенаправляем стандартный вывод дочернего процесса в ввод этого канала при помощи dup2(). После этого мы запускаем внешнюю программу при помощи execlp(), и весь её вывод пойдёт не на экран, а в наш канал.
В родительском же процессе мы читаем данные из канала в строковый буфер при помощи read().
Привожу исходный код демонстрационной программы:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#define SIZE 256
int main (int argc, char *argv[]) {
int mypipe[2];
pipe(mypipe);
switch(fork()) {
case -1: /* error */
perror("fork");
exit(EXIT_FAILURE);
case 0: /* child process */
close(mypipe[0]); /* close unused in */
dup2(mypipe[1], 1); /* stdout to pipe out */
execlp("ls", NULL);
close(mypipe[1]);
_exit(EXIT_SUCCESS);
}
/* parent process */
close(mypipe[1]); /* close unused out */
char buf[SIZE] = "";
read(mypipe[0], buf, SIZE); /* read from pipe in */
close(mypipe[0]);
printf("Child output:\n%s\n", buf);
return EXIT_SUCCESS;
}
Функция pipe(int filedes[2]) создаёт канал и записывает дескрипторы ввода и вывода соответственно в filedes[0] и filedes[1]. 0 – это ввод, 1 – это вывод. Мы используем знание этого при перенаправлении стандартного вывода дочернего процесса в вывод канала при помощи dup2(mypipe[1], 1). В каждом процессе мы сперва закрываем неиспользуемый «конец» канала, а затем работаем с оставшимся.
Стоит отметить, что в этом примере мы сможем получить только данные из стандартного вывода дочернего процесса. Стандартный вывод ошибок по-прежнему направлен на экран.
Вывод программы:
$ ./pipestest Child output: pipestest pipestest.c
Исходный текст вы можете загрузить отсюда.
Подробную информацию о используемых функциях вы можете найти в manpages: fork(2), pipe(2), dup(2), exec(3) и т.д.
Ага, читал именно о похожих решениях в книге Арнольда Роббинса “Linux. Программирование в примерах”
А как закрыть форкнутый процесс? Просто если взять и зацыклить вышеуказанный код – то можно будет заметить, что будет открываться всё новый и новый дочерний процесс
Дык если известен PID дочернего процесса, можнож ему и сигналы посылать. Например kill()…
А вообще просто не стОит форкатся, если оно не надо и если уж форкнулись, хорошей мыслью будет предусмотреть в коде копии завершение по какому-нить условию.