Каналы часто используют для выполнения консольных команд — в частности, для считывания их вывода или передачи им какого-нибудь ввода. Для упрощения этой задачи предусмотрены функции popen() и pclose():
#include
FILE *popen(const char *
Возвращает файловый поток или NULL, если произошла ошибка
int pclose(FILE *
Возвращает код завершения дочернего процесса или -1, если произошла ошибка
Функция popen() открывает канал и создает дочерний процесс, запускающий командную оболочку, которая, в свою очередь, создает еще один дочерний процесс для выполнения строки, переданной в аргументе command. Аргумент mode представляет собой строку, определяющую, будет ли процесс читать из канала (mode равен r) или записывать в него (mode равен w). Это взаимоисключающие режимы, поскольку каналы являются однонаправленными. В зависимости от значения аргумента mode происходит одно из двух: либо стандартный вывод выполняемой команды соединяется с записывающим концом канала, либо ее стандартный ввод соединяется со считывающим концом канала (рис. 44.4).
Рис. 44.4.
При успешном выполнении popen() возвращает указатель на файловый поток, который можно применять в библиотечных функциях стандартного ввода/вывода. При возникновении ошибки (например, когда значение mode не равно r или w, не удалось открыть канал или вызов fork(), создающий потомков, завершился неудачей) popen() возвращает NULL и устанавливает значение errno, чтобы сигнализировать о причине ошибки.
После завершения popen() вызывающий процесс использует канал для чтения вывода команды command или для передачи ей какого-нибудь ввода. Когда команда закрывает записывающий конец канала, вызывающий процесс получает символ конца файла (аналогично ведут себя каналы, созданные с помощью вызова pipe()). Если команда закрывает считывающий конец канала, то процесс получает сигнал SIGPIPE и ошибку EPIPE.
По окончании ввода/вывода вызывается функция pclose(), закрывающая канал и ждущая завершения дочерней командной оболочки (функция fclose() в этом случае не подходит, так как не ждет завершения потомка). В случае успеха pclose() возвращает код завершения (см. раздел 26.1.3) дочерней командной оболочки (который равен коду завершения последней выполненной в нем команды — если только оболочка не была завершена по сигналу). Как и в случае с вызовом system() (см. раздел 27.6), если командная оболочка не может быть запущена, то функция pclose() возвращает такое же значение, как если бы оболочка была закрыта с помощью вызова _exit(127). В случае возникновения какой-то другой ошибки pclose() возвращает -1. Возможна также ситуация, в которой не удается получить код завершения. Чуть ниже мы объясним, как это может произойти.
Стандарт SUSv3 требует, чтобы во время ожидания кода завершения дочерней командной оболочки функция pclose(), как и system(), автоматически перезапускала вызов waitpid(), который она выполняет внутри, если тот был прерван обработчиком сигнала.
В целом все написанное нами в разделе 27.6 относительно вызова system() применимо и к функции popen(). Она всего лишь предоставляет больше удобств: открывает канал, дублирует дескрипторы, закрывает те из них, что не используются, а также берет на себя все нюансы запуска вызовов fork() и exec(). Кроме того, команда управляется оболочкой. Но за такое удобство приходится платить производительностью, ведь для этого создается два дополнительных процесса: один для оболочки, а другой — для одной или нескольких команд, которые она выполняет. По аналогии с system() функция popen() никогда не должна вызываться из привилегированных программ.
Вызов system() и функции popen() и pclose() имеют определенное сходство, однако между ними существуют и различия. Оно вытекает из того факта, что system() инкапсулирует выполнение консольных команд внутри единого вызова, тогда как в случае с popen() вызывающий процесс работает параллельно консольной команде, после чего вызывает pclose(). Разница заключается в аспектах, представленных ниже.
• Поскольку вызывающий процесс и выполняемая команда работают параллельно, стандарт SUSv3 требует, чтобы функция popen()