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

int pfd[2];

pipe(pfd); /* Выделим для канала (к примеру) файловые дескрипторы 3 и 4 */

/* Здесь выполняем другие шаги, например fork() */

close(STDOUT_FILENO); /* Освобождаем файловый дескриптор 1 */

dup(pfd[1]); /* При дублировании используется наименьший

свободный номер дескриптора, то есть fd 1 */

Конечным результатом вышеприведенных шагов является то, что стандартный вывод процесса привязывается к записывающему концу канала. Аналогичная цепочка вызовов применяется для привязки считывающего конца канала к стандартному вводу процесса.

Стоит отметить: данные шаги основаны на предположении о том, что файловые дескрипторы процесса с номерами 0, 1 и 2 уже открыты (командная оболочка обычно делает это автоматически для каждой программы, которая в ней выполняется). Если бы дескриптор 0 был закрыт до вышеприведенных вызовов, то мы бы ошибочно привязали к записывающему концу канала стандартной ввод процесса. Чтобы исключить такую возможность, close() и dup() можно заменить вызовом dup2(), показанным ниже; это позволит нам явно задать дескриптор, который будет привязан к концу канала:

dup2(pfd[1], STDOUT_FILENO); /* Закрываем дескриптор 1 и повторно устанавливаем

связь с записывающим концом канала */

Продублировав pfd[1], мы получили два дескриптора, ссылающихся на записывающий конец канала: дескриптор 1 и pfd[1]. Поскольку неиспользуемые файловые дескрипторы канала следует закрывать, мы сделаем это после вызова dup2():

close(pfd[1]);

Код, показанный выше, требует предварительного открытия стандартного вывода. Предположим, что стандартный ввод/вывод был закрыт перед вызовом pipe(). В этом случае вызов pipe() выделил бы для канала два дескриптора — например, pfd[0] со значением 0 и pfd[1] со значением 1. Следовательно, эквивалентом предыдущих вызовов dup2() и close() был бы такой код:

dup2(1, 1); /* Ничего не делает */

close(1); /* Закрывает единственный дескриптор для записывающего конца канала */

В целях безопасности эти вызовы рекомендуется заключить внутрь инструкции if следующего вида:

if (pfd[1]!= STDOUT_FILENO) {

dup2(pfd[1], STDOUT_FILENO);

close(pfd[1]);

}

Пример программы

Программа, представленная в листинге 44.4, использует методики, описанные в данном разделе для получения того же результата, что и у кода из листинга 44.1. Открыв канал, мы создаем два дочерних процесса. Первый привязывает свой стандартный вывод к записывающему концу канала, после чего выполняет команду ls. Второй привязывает свой стандартный ввод к считывающему концу канала и выполняет команду wc.

Листинг 44.4. Использование канала для соединения команд ls и wc

pipes/pipe_ls_wc.c

#include

#include "tlpi_hdr.h"

int

main(int argc, char *argv[])

{

int pfd[2]; /* Файловые дескрипторы канала */

if (pipe(pfd) == -1) /* Создаем канал */

errExit("pipe");

switch (fork()) {

case -1:

errExit("fork");

case 0: /* Первый потомок выполняет 'ls', записывая результат в канал */

if (close(pfd[0]) == -1) /* Считывающий конец не используется */

errExit("close 1 ");

/* Дублируем стандартный вывод на записывающем конце канала;

закрываем лишний дескриптор */

if (pfd[1]!= STDOUT_FILENO) { /* Проверка на всякий случай */

if (dup2(pfd[1], STDOUT_FILENO) == -1)

errExit("dup2 1");

if (close(pfd[1]) == -1)

errExit("close 2");

}

execlp("ls", "ls", (char *) NULL); /* Записывает в канал */

errExit("execlp ls");

default: /* Родитель выходит из этого блока, чтобы создать следующего потомка */

break;

}

switch (fork()) {

case -1:

errExit("fork");

case 0: /* Второй потомок выполняет 'wc', считывая ввод из канала */

if (close(pfd[1]) == -1) /* Записывающий конец не используется */

errExit("close 3");

/* Дублируем стандартный ввод на считывающем конце канала;

закрываем лишний дескриптор */

if (pfd[0]!= STDIN_FILENO) { /* Проверка на всякий случай */

if (dup2(pfd[0], STDIN_FILENO) == -1)

errExit("dup2 2");

if (close(pfd[0]) == -1)

errExit("close 4");

}

execlp("wc", "wc", "-l", (char *) NULL); /* Читает из канала */

errExit("execlp wc ");

default: /* Родитель выходит из этого блока */

break;

}

/* Родитель закрывает лишние дескрипторы канала и ждет завершения дочерних процессов */

if (close(pfd[0]) == -1)

errExit("close 5");

if (close(pfd[1]) == -1)

errExit("close 6");

if (wait(NULL) == -1)

errExit("wait 1");

if (wait(NULL) == -1)

errExit("wait 2");

exit(EXIT_SUCCESS);

}

pipes/pipe_ls_wc.c

Запустив программу из листинга 44.4, мы увидим следующее:

$ ./pipe_ls_wc

24

$ ls | wc — l Проверяем результаты с помощью консольных команд

24

44.5. Взаимодействие с консольными командами с помощью канала: popen()
Перейти на страницу:

Похожие книги