Получаем вывод дочернего процесса в Unix

Буквально вчера один человек задал мне вопрос, на который я не смог сходу ответить: как получить вывод дочернего процесса и записать его в строку? Другими словами, необходимо запустить какую-то внешнюю программу, и прочитать её вывод. Всё это должно быть реализовано программно, на Си. Судя по всему, решения могут варьироваться от одной операционной системы к другой, так, я немного повозился с данной задачей. Здесь я покажу, как это можно сделать в 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) и т.д.

Реклама

3 ответа на “Получаем вывод дочернего процесса в Unix

  1. А как закрыть форкнутый процесс? Просто если взять и зацыклить вышеуказанный код — то можно будет заметить, что будет открываться всё новый и новый дочерний процесс

  2. Дык если известен PID дочернего процесса, можнож ему и сигналы посылать. Например kill()…

    А вообще просто не стОит форкатся, если оно не надо и если уж форкнулись, хорошей мыслью будет предусмотреть в коде копии завершение по какому-нить условию.

Добавить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход / Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход / Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход / Изменить )

Google+ photo

Для комментария используется ваша учётная запись Google+. Выход / Изменить )

Connecting to %s