Приглашаем посетить
Пушкин (pushkin-lit.ru)

Управление процессами и межпроцессные взаимодействия

Назад
Глава 16 Управление процессами и межпроцессные взаимодействия
Вперед

Управление процессами и межпроцессные взаимодействия

Введение

Многие из нас относятся к Perl по-своему, но большинство считает его чем-то вроде "клея", объединяющего разнородные компоненты. Эта глава посвящена командам и отдельным процессам - их созданию, взаимодействию и завершению. Итак, речь пойдет о системном программировании. В области системного программирования на Perl, как обычно, все простое упрощается, а все сложное становится доступным. Если вы хотите работать на высоком уровне, Perl с радостью вам поможет. Если вы собираетесь закатать рукава и заняться низкоуровневым программированием, уподобившись хакерам С, -что ж, возможно и это.
Perl позволяет очень близко подобраться к системе, но при этом могут возникнуть некоторые проблемы переносимости. Из всей книги эта глава в наибольшей степени ориентирована на UNIX. Изложенный материал чрезвычайно полезен для тех, кто работает в UNIX-системах, и в меньшей степени - для всех остальных. Рассматриваемые в ней возможности не являются универсальными, как, например, строки, числа или базовая арифметика. Большинство базовых операций более или менее одинаково работает повсюду. Но если вы не работаете в системе семейства UNIX или другой POSIX-совместимой системе, многие интересные возможности у вас будут работать иначе (или не будут работать вообще). В сомнительных ситуациях обращайтесь к документации, прилагаемой к вашей версии Perl.
Создание процессов
В этой главе рассматриваются порожденные процессы. Иногда вы просто выполняете автономную команду (с помощью system) и оставляете созданный процесс на произвол судьбы. В других случаях приходится сохранять тесную связь с созданным процессом, скармливать ему тщательно отфильтрованные данные или управлять его потоком вывода ('...' и конвейерные вызовы open). Наконец, даже без запуска нового процесса вызов ехес позволяет заменить текущую программу чем-то совершенно новым.
Сначала мы рассмотрим самые переносимые и распространенные операции управления процессами: '...', system, open и операции с хэшем %SIG. Здесь нет ничего сложного, но мы не остановимся па этом и покажем, что делать, когда простые решения не подходят. Допустим, вы хотите прервать свою программу в тот момент, когда она запустила другую программу. Или вам захотелось отделить стандартный поток ошибок порожденного процесса от его стандартного вывода. Или вы собираетесь одновременно управлять и как вводом, так и выводом программы. Или вы решили воспользоваться преимуществами многозадачности и разбить свою программу на несколько одновременно работающих процессов, взаимодействующих друг с другом. В подобных ситуациях приходится обращаться к системным функциям: pipe, fork и ехес. Функция pipe создает два взаимосвязанных манипулятора, записывающий и читающий; при этом все данные, записываемые в первый, могут быть прочитаны из первого. Функция fork является основой многозадачности, по, к сожалению, она не поддерживается некоторыми системами, не входящими в семейство UNIX. Функция создает процесс-дубликат, который практически во всех отношениях идентичен своему родителю, включая значения переменных и открытые файлы. Самые заметные изменения - идентификатор процесса и идентификатор родительского процесса. Новые программы запускаются функцией fork, после чего функция ехес заменяет программу порожденного процесса чем-то другим. Функции fork и ехес не всегда используются вместе, поэтому наличие отдельных примитивов оказывается более выразительным и мощным по сравнению с ситуацией, когда ваши возможности ограничиваются выполнением system. На практике fork по отдельности используется чаще, чем с ехес.
При уничтожении порожденного процесса его память возвращается операционной системе, но соответствующий элемент таблицы процессов не освобождается. Благодаря этому родитель может проверить статус завершения всех порожденных процессов. Процессы, которые умерли, но не были удалены из таблицы процессов, называются зомби; их следует своевременно удалять, чтобы они не заполнили всю таблицу процессов. Оператор '.,,', а также функции system и open автоматически следят за этим и работают в большинстве систем, не входящих в семейство UNIX. При выходе за рамки этих простых переносимых функций и запуске программ с помощью низкоуровневых примитивов возникают дополнительные хлопоты. Кроме того, не стоит забывать и о сигналах.

Сигналы

Ваш процесс узнает о смерти созданного им порожденного процесса с помощью сигнала. Сигналы представляют собой нечто вроде оповещений, доставляемых операционной системой. Они сообщают о произошедших ошибках (когда ядро говорит: "Не трогай эту область памяти!") и событиях (смерть порожденного процесса, тайм-аут процесса, прерывание по Ctrl+C). При ручном запуске процесса обычно указывается подпрограмма, которая должна вызываться при завершении потомка.
Каждый процесс имеет стандартные обработчики для всех возможных сигналов. Вы можете установить свой собственный обработчик или изменить отношение программы к большинству сигналов. Не изменяются только SIGKILL и SIGTOP - все остальные сигналы можно игнорировать, перехватывать и блокировать.
Приведем краткую сводку важнейших сигналов.
SIGINT
Обычно возникает при нажатии Ctrl+C. Требует, чтобы процесс завершил свою работу. Простые программы (например, фильтры) обычно просто умирают, но более сложные программы - командные интерпретаторы, редакторы и программы FTP - обычно используют SIGINT для прерывания затянувшихся операций.
SIGQUIT vОбычно генерируется терминалом, как правило, при нажатии Ctrl+\. По умолчанию выводит в файл содержимое памяти.
SIGTERM
Посылается командой kill при отсутствии явно заданного имени сигнала. Может рассматриваться как вежливая просьба умереть, адресованная процессу.
SIGUSR1 и SIGUSR2
Никогда не вызываются системными событиями, поэтому пользовательские приложения могут смело использовать их для собственных целей.
SIGPIPE Посылается ядром, когда ваш процесс пытается записать в канал (pipe) или со-кет, а процесс на другом конце канала/сокета отсоединился (обычно потому, что он перестал существовать). SIGALRM
Посылается при истечении промежутка времени, установленного функцией alarm (см. рецепт 16.21).
SIGHUP
Посылается процессу при разрыве связи (hang-up) на управляющем терминале (например, при потере несущей модемом), но также часто означает, что программа должна перезапуститься или заново прочитать свою конфигурацию.
SIGCHLD
Вероятно, самый важный сигнал во всем низкоуровневом системном програм мировании. Система посылает процессу сигнал SIGSHLD в том случае, если один из его порожденных процессов перестает выполняться - или, что более вероятно, при его завершении. Дополнительные сведения о SIGCHLD приведены в рецепте 16.19.
Имена сигналов существуют лишь для удобства программистов. С каждым сигналом связано определенное число, используемое операционной системой вместо имени. Хотя мы говорим о сигнале SIGCHLD, операционная система опознает его по номеру - например, 20 (в зависимости от операционной системы). Perl преобразует номера сигналов в имена, поэтому вы можете работать с именами сигналов.
Обработка сигналов рассматривается в рецептах 16.7, 16.15, 16.18, 16.20 и 16.21.

16.1. Получение вывода от программы

Проблема

Требуется запустить программу и сохранить ее вывод в переменной.

Решение

Воспользуйтесь либо оператором '...':
$output = 'ПРОГРАММА АРГУМЕНТЫ'; # Сохранение данных в одной

# многострочной переменной.
@output = 'ПРОГРАММА АРГУМЕНТЫ'; # Сохранение данных в массиве,
# по одной строке на элемент.
#либо решением из рецепта 16.4:

open(README, "ПРОГРАММА АРГУМЕНТЫ |") or die "Can't run program: $!\n";
while() { $output .= $_;
} close(README);

Комментарий

Оператор ' . . . ' является удобным средством для запуска других программ и получения их выходных данных. Возврат из него происходит лишь после завершения вызванной программы. Для получения вывода Perl предпринимает некоторые дополнительные усилия, поэтому было бы неэффективно использовать '. . . ' и игнорировать возвращаемое значение:
ofsck -у /dev/rsd-la'; # ОТВРАТИТЕЛЬНО
И функция open, и оператор '. . . ' обращаются к командному интерпретатору для выполнения команд. Из-за этого они недостаточно безопасно работают в привилегированных программах.
Приведем низкоуровневое обходное решение с использованием pipe, fork и ехес:
use POSIX qw(:sys_wait_h);
pipe(README, WRITEME);
if ($pid = fork) {
# Родительский процесс
$SIG{CHLD} = sub { 1 while ( waitpid(-1, WNOHANG)) > 0 };
close(WRITEME);
} else {
die "cannot fork: $!" unless defined $pid;
# Порожденный процесс
open(STDOUT, ">&=writeme") or die "couldn't redirect stdout: $!";
close(README);
exec($program, $arg1, $arg2) or die "Couldn't run $program : $!\n";
}
while () {
$string .= $_;
# or push(@strings, $_) } close(README);


> Смотри также ----
perlsec(1); рецепты 16.2; 16.4; 16.19; 19.6.

16.2. Запуск другой программы

Проблема

Вы хотите запустить другую программу из своей, дождаться ее завершения и затем продолжить работу. Другая программа должна использовать те же STDIN и STDOUT, что и основная.

Решение

Вызовите функцию system со строковым аргументом, который интерпретируется как командная строка:
$status = system("vi $myfile");

Если вы не хотите привлекать командный интерпретатор, передайте syster список:
$status = system("vi", $myfile);

Комментарий

Функция system обеспечивает самую простую и универсальную возможность запуска других программ в Perl. Она не возвращает выходные данные внешней программы, как '. . . ' или open. Вместо этого ее возвращаемое значение (фактически) совпадает с кодом завершения программы. Во время работы новой программы основная приостанавливается, поэтому новая программа может взаимодейство-вать с пользователем посредством чтения данных из STDIN и записи в STDOUT. При вызове с одним аргументом функция system (как и open, exec и '...') использует командный интерпретатор для запуска программы. Это может пригодиться для перенаправления или других фокусов:
system("cmd1 args | cmd2 | cmd3 outfile");
system("cmd args outfile2>errfile");

Чтобы избежать обращений к интерпретатору, вызывайте system со списком аргументов:
$status = system($program, $arg1, $arg);
die "$program exited funny: $?" unless $status == 0;
Возвращаемое значение не является обычным кодом возврата; оно включает номер сигнала, от которого умер процесс (если он был). Это же значение присваивается переменной $? функцией wait. В рецепте 16.19 рассказано о том, как декодировать tuj.
Функция system (но не '...'!) игнорирует SIGINT и SIGQUIT во время работы порожденных процессов. Сигналы убивают лишь порожденные процессы. Если вы хотите, чтобы основная программа умерла вместе с ними, проверьте возвращаемое значение system или переменную $9:
if (($signo = system((sarglist)) &= 127)
{ die "program killed by signal $signo\n";
}
Чтобы игнорировать SIGINT, как это делает system, установите собственный обработчик сигнала, а затем вручную вызовите fork и ехес:
if ($pid = fork) {
# Родитель перехватывает INT и предупреждает пользователя
local $SIG{INT} = sub < print "Tsk tsk, no process interruptus\n" };
waitpid($pid, 0);
} else {
die "cannot fork: $!" unless defined $pid;
# Потомок игнорирует INT и делает свое дело
$SIG{INT} = "ignore";
exec("summarize", "/etc/logfiles") or die "Can't exec: $!\n";
}
($pid = fork) ? waitpid($pid, 0) : exec(@argv)
or die "Can't exec: $!\n";

Некоторые программы просматривают свое имя. Командные интерпретаторы узнают, были ли они вызваны с префиксом -, обозначающим интерактивность. Программа ехрп в конце главы 18 при вызове под именем vrfy работает иначе; такая ситуация возникает при создании двух ссылок на файл (см. описание ехрп). По этой причине не следует полагать, что $0 всегда содержит имя вызванной программы.
Если вы хотите подсунуть запускаемой программе другое имя, укажите настоящий путь в виде "косвенного объекта" перед списком, передаваемым system (также работает для exec). После косвенного объекта не ставится запятая, по аналогии с вызовом printf для файлового манипулятора или вызовом методов объекта без ->.
$shell = '/bin/tcsh';
system $shell '-csh'; # Прикинуться другим интерпретатором

Или непосредственно:
system {'/bin/tcsh'} '-csh'; # Прикинуться другим интерпретатором

В следующем примере настоящее имя программы передается в виде косвенного объекта
{'/home/tchrist/scripts/expn '}. Фиктивное имя 'vrfy' передается в виде первого настоящего аргумента функции, и программа увидит его в переменной $0.
# Вызвать ехрn как vrfy system {'/home/tchrist/scripts/expn'} 'vrfy', @ADDRESSES;

Применение косвенных объектов с system более надежно. В этом случае аргументы заведомо интерпретируются как список, даже если он состоит лишь из од ного элемента. Это предотвращает расширение метасимволов командным интерпретатором или разделение слов, содержащих пропуски.
@args = ( "echo surprise" );
system @args; # Если Oargs == 1, используются
# служебные преобразования интерпретатора
system { $args[0] } @args; # Безопасно даже для одноаргументного списка

Первая версия (без косвенного объекта) запускает программу echo и передает ей аргумент "surprise". Вторая версия этого не делает - она честно пытается запустить программу "echo surprise", не находит ее и присваивает $9 ненулевое значение, свидетельствующее об ошибке.

> Смотри также --------------------------------
perlsec(1); описание функций waitpid, fork, exec, system и open в perlfunc(1); рецепты 16.1; 16.4; 16.19; 19.6.

16.3. Замена текущей программы

Проблема

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

Решение

Воспользуйтесь встроенной функцией exec. Если exec вызывается с одним аргументом, содержащим метасимволы, для запуска будет использован командный интерпретатор:
exec("archive *.data")
or die "Couldn't replace myself with archive: $!\n";

Если exec передаются несколько аргументов, командный интерпретатор не используется:
exec("archive", "accounting.data")
or die "Couldn't replace myself with archive: $!\n";

При вызове с одним аргументом, не содержащим метасимволов, аргумент разбивается по пропускам и затем интерпретируется так, словно функция ехес была вызвана для полученного списка:
ехес("archive accounting.data")
or die "Couldn't replace myself with archive: $!\n";

Комментарий

Функция Perl ехес обеспечивает прямой интерфейс к системной функции ехес1р(2), которая заменяет текущую программу другой без изменения идентификатор процесса. Программа, вызвавшая ехес, стирается, а ее место в таблице процессов операционной системы занимает программа, указанная в качестве аргумента ехес. В результате новая программа сохраняет тот же идентификатор процесса ($$), что и у исходной программы. Если указанную программу запустить не удалось, ехес возвращает false, а исходная программа продолжает работу. Не забывайте проверять такую ситуацию.
При переходе к другой программе с помощью ехес не будут автоматически вызваны ни блоки END, ни деструкторы объектов, как бы это произошло при нормальном завершении процесса.

> Смотри также --------------------------------
Описание функции ехес в perlfunc(1) страница руководства ехес1р(2) вашей системы (если есть); рецепт 16.2.

16.4. Чтение или запись в другой программе

Проблема

Вы хотите запустить другую программу и либо прочитать ее вывод, либо предоставить входные данные.

Решение

Вызовите open с символом | в начале или конце строки. Чтобы прочитать вывод программы, поставьте | в конце:
$pid = open(readme, "program arguments |") or die "couldn't fork: $!\n";
while () {
# ...
} close(README) or die "Couldn't close: $!\n";

Чтобы передать данные, поставьте | в начале:
$pid = open(writeme, "| program arguments") or die "couldn't fork: $!\n";
print WRITEME "data\n":
close(WRITEME) or die "Couldn't close: $!\n";

Комментарий

При чтении происходящее напоминает '...', разве что на этот раз у вас имеется идентификатор процесса и файловый манипулятор. Функция open также использует командный интерпретатор, если встречает в аргументе метасимволы, и не использует в противном случае. Обычно это удобно, поскольку вы избавляетесь от хлопот с расширением метасимволов в именах файлов и перенаправлением ввода/вывода.
Однако в некоторых ситуациях это нежелательно. Конвейерные вызовы open, в которых участвуют непроверенные пользовательские данные, ненадежны при работе в режиме меченых данных или в ситуациях, требующих абсолютной уверенности. Рецепт 19.6 показывает, как имитировать эффект конвейерных вызовов open без риска, связанного с использованием командного интерпретатора.
Обратите внимание на явный вызов close для файлового манипулятора. Когда функция open используется для подключения файлового манипулятора к порожденному процессу, Perl запоминает этот факт и при закрытии манипулятора автоматически переходит в ожидание. Если порожденный процесс к этому моменту не завершился, Perl ждет, пока это произойдет. Иногда ждать приходится очень, очень долго:
$pid = open(f, "sleep 100000]"); # Производный процесс приостановлен
close(F); # Родитель надолго задумался

Чтобы избежать этого, уничтожьте производный процесс по значению PID, полу-ченному от open, или воспользуйтесь конструкцией pipe-fork-exec (см. рецепт 16.10). При попытке записать данные в завершившийся процесс, ваш процесс получит сигнал SIGPIPE. По умолчанию этот сигнал убивает ваш процесс, поэтому про-граммист-параноик на всякий случай установит обработчик SIGPIPE.
Если вы хотите запустить другую программу и предоставить содержимое ее STDIN, используется аналогичная конструкция: |
$pid = open(writeme, "| program args");
print WRITEME "hello\n"; # Программа получит hello\n в STDIN
close(WRITEME); # Программа получит EOF в STDIN

Символ | в начале аргумента функции open, определяющего имя файла, сооб-и щает Perl о необходимости запустить другой процесс. Файловый манипулятор, от-крытый функцией open, подключается к STDIN порожденного процесса. Все,что вы запишете в этот манипулятор, может быть прочитано процессом из STDIN. После закрытия манипулятора (close) при следующей попытке чтения из STDIN порожденный процесс получит eof. Описанная методика может применяться для изменения нормального вывода вашей программы. Например, для автоматической обработки всех данных утнли-_ той постраничного вывода используется фрагмент вида:
$pager = $env{pager} || '/usr/bin/less'; # xxx: может не существовать
open(STDOUT, "| $радег");

Теперь все данные, направленные в стандартный вывод, будут автоматически проходить через утилиту постраничного вывода. Вам не придется исправлять дру-гие части программы.
Как и при открытии процесса для чтения, в тексте, передаваемом командному интерпретатору, происходит расширение метасимволов. Чтобы избежать обращения к интерпретатору, следует воспользоваться решением, аналогичным приведенному выше. Как и прежде, родитель должен помнить о close. При закрытии файлового манипулятора, подключенного к порожденному процессу, родитель блокируется до завершения потомка. Если порожденный процесс не завершается, то и закрытие не произойдет. Приходится либо заранее убивать порожденный процесс, либо использовать низкоуровневый сценарий pipe-fork-exec.
При использовании сцепленных открытий всегда проверяйте значения, возвращаемые open и close, не ограничиваясь одним open. Дело в том, что возвращаемое значение open не говорит о том, была ли команда успешно запущена. При сцепленном открытии команда выполняется вызовом fork для порожденного процесса. Если возможности создания процессов в системе не исчерпаны, fork немедленно возвращает PID порожденного процесса.
К тому моменту, когда порожденный процесс пытается выполнить команду ехес, он уже является самостоятельно планируемым. Следовательно, если команда не будет найдена, практически не существует возможности сообщить об этом функции open, поскольку она принадлежит другому процессу!
Проверка значения, возвращаемого close, позволяет узнать, успешно ли выполнилась команда. Если порожденный процесс завершается с ненулевым кодом (что произойдет в случае, если команда не найдена), то close возвращает false, a переменной $? присваивается статус ожидания процесса. Об интерпретации содержимого этой переменной рассказано в рецепте 16.2.

> Смотри также --------------------------------
Описание функции open в perlfunc(1); рецепты 16.10; 16.15; 16.19; 19.6.

16.5. Фильтрация выходных данных

Проблема

Требуется обработать выходные данные вашей программы без написания отдельного фильтра.

Решение

Присоедините фильтр с помощью разветвляющего (forking) вызова open. Например, в следующем фрагменте вывод программы ограничивается сотней строк:
head(100);
while (о) { print;
}
sub head {
my $lines = shift 11 20;
return if $pid = open(stdout, "|-");
die "cannot fork: $!" unless defined $pid;
while () {
print;
last unless --$lines ;
} exit;
}

Комментарий

Создать выходной фильтр несложно - достаточно открыть STDOUT разветвляющим вызовом open, а затем позволить порожденному процессу фильтровать STDIN в STDOUT и внести те изменения, которые он посчитает нужным. Обратите внимание: выходной фильтр устанавливается до генерации выходных данных. Это вполне логично - нельзя отфильтровать вывод, который уже покинул вашу программу.
Все подобные фильтры должны устанавливаться в порядке очередности стека -последний установленный фильтр работает первым.
Рассмотрим пример, в котором используются два выходных фильтра. Первый фильтр нумерует строки; второй - снабжает их символами цитирования (как в сообщениях электронной почты). Для файла /etc/motd результат выглядит примерно так:
1: > Welcome to Linux, version 2.0.33 on a i686
2: >
3: > "The software required 'Windows 95 or better',
4: > so I installed Linux."
Если изменить порядок установки фильтров, вы получите следующий результат:
> 1: Welcome to Linux, Kernel version 2.0.33 on a i686 > 2:
> 3: "The software required 'Windows 95 or better', >
4: so I installed Linux." Исходный текст программы приведен в примере 16.1. Пример 16.1. qnumcat
#!/usr/bin/perl
# qnumcat - установка сцепленных выходных фильтров
number(); # Установить для STDOUT нумерующий фильтр
quote(); # Установить для STDOUT цитирующий фильтр
while (<>) { # Имитировать /bin/cat print;
}
close STDOUT; # Вежливо сообщить потомкам о завершении exit;
sub number {
my $pid;
return if $pid = open(stdout, "|-");
die "cannot fork: $!" unless defined $pid;
while () { printf "%d: %s", $., $_ } exit;
}
sub quote { my $pid;
return if $pid = open(stdout, "|-");
die "cannot fork: $!" unless defined $pid;
while () { print "> $_" } exit;
}

Как и при любых разветвлениях, для миллиона процессов такое решение не подойдет, но для пары (или даже нескольких десятков) процессов расходы будут небольшими. Если ваша система изначально проектировалась как многозадачная (как UNIX), все обойдется дешевле, чем можно себе представить. Благодаря виртуальной памяти и копированию во время записи такие операции выполняются достаточно эффективно. Разветвление обеспечивает элегантное и недорогое решение многих (если не всех) задач, связанных с многозадачностью.

> Смотри также --------------------------------
Описание функции open в perlfunc(1), рецепт 16.4.

16.6. Предварительная обработка ввода

Проблема

Ваша программа умеет работать лишь с обычным текстом в локальных файлах. Однако возникла необходимость работать с экзотическими файловыми форматами - например, сжатыми файлами или Web-документами, заданными в виде URL.

Решение

Воспользуйтесь удобными средствами Perl для работы с каналами и замените имена входных файлов каналами перед тем, как открывать их. Например, следующий фрагмент автоматически восстанавливает архивные файлы, обработанные утилитой gzip:
@ARGV = map { /\.(gz|z)$/ ? "gzip -de $_ |" : $_ } @argv;
while (<>) { # .......
}
А чтобы получить содержимое URL перед его обработкой, воспользуйтесь программой GET из модуля LWP (см. главу 20 "Автоматизация в Web"):
@ARGV = mар { mft"\w+://# ? "get $_ |" : $_ } @argv;
while (<>) { # .......
}

Конечно, вместо HTML-кода можно принять простой текст. Для этого достаточно воспользоваться другой командой (например, lynx -dump).

Комментарий

Как показано в рецепте 16.1, встроенная функция Perl open очень удобна: каналы открываются в Perl так же, как и обычные файлы. Если то, что вы открываете, похоже на канал, Perl открывает его как канал. Мы используем эту особенность и включаем в имя файла восстановление архива или иную предварительную обработку. Например, файл "OQtails.gz" превращается .в "gzcat -de 09tails.gz|". Эта методика применима и в других ситуациях. Допустим, вы хотите прочитать/etc/passwd, если компьютер не использует NIS, и вывод ypcat passwd в противном случае. Мы определяем факт использования NIS по выходным данным программы domainname, после чего выбираем в качестве открываемого файла строку "
$pwdinfo = 'domainname' =~ /"(\(none\))?$/ ? '< /etc/passwd' : 'ypcat passwd |';
open(PWD, $pwdinfo) or die "can't open $pwdinfo: $!";
Но и это еще не все! Даже если вы не собирались встраивать подобные возможности в свою программу, Perl делает это за вас! Представьте себе фрагмент вида:
print "File, please? ";
chomp($file = <>);
open (FH, $file) or die "can't open $file: $!";

Пользователь может ввести как обычное имя файла, так и строку вида "webget http://www. perl. corn |" - и ваша программа вдруг начинает получать выходные данные от webget! А если ввести всего один символ, дефис (-), то при открытии для чтения будет интерполирован стандартный ввод. В рецепте 7.7 эта методика использовалась для автоматизации обработки ARGV.
> Смотри также --------------------------------
Рецепты 7.7; 16.4.

16.7. Чтение содержимого STDERR

Проблема

Вы хотите выполнить программу с помощью system, '. . . ' или open, но содержимое ее STDERR не должно выводиться в ваш STDERR. Необходимо либо игнорировать содержимое STDERR, либо сохранять его отдельно.

Решение

Воспользуйтесь числовым синтаксисом перенаправления и дублирования для файловых дескрипторов. Для упрощения примеров мы не проверяем возвращаемое значение open, но вы обязательно должны делать это в своих программах! Одновременное сохранение STDERR и STDOUT:
$output = 'cmd 2>&1'; # Для '... '
# или
$pid = open(ph, "cmd 2>&1 |"); # Для open
while () { } # Чтение

Сохранение STDOUT с игнорированием STDERR:
$output = 'cmd 2>/dev/null'; # Для '...'
# или
$pid = open(ph, "cmd 2>/dev/null |"); # Для open
while () { } # Чтение
Сохранение STDERR с игнорированием STDOUT:
$output = 'cmd 2>&1 1>/dev/null'; # Для '...'
# или
$pid = open(ph, "cmd 2>&1 1>/dev/null |"); # Для open
while () { } # Чтение

Замена STDOUT и STDERR команды, то есть сохранение STDERR и направление STDOUT в старый STDERR:
$output = 'cmd 3>&1 1>&2 2>&3 3>&-'; # Для '...'
# или
$pid = open(ph, "cmd 3>&1 1>&2 2>&3 3>&- "); # Для open
while () { } # Чтение

Чтобы организовать раздельное чтение STDOUT и STDERR команды, проще и надежнее всего будет перенаправить их в разные файлы, а затем прочитать из этих файлов после завершения команды:
system("prog args 1>/tmp/program,stdout 2>/tmp/program.stderr");

Комментарий

При выполнении команды оператором '...', сцепленным вызовом open или syste' для одной строки Perl проверяет наличие символов, имеющих особый смысл для командного интерпретатора. Это позволяет перенаправить файловые дескрипторы повой программы. STDIN соответствует файловому дескриптору с номером О, STDOUT - 1, a STDERR - 2. Например, конструкция 2>файл перенаправляет STDERR в файл. Для перенаправления в файловый дескриптор используется специальная конструкция &N, где N - номер файлового дескриптора. Следовательно, 2>&1 направляет STDERR в STDOUT.
Ниже приведена таблица некоторых интересных перенаправлений файловых дескрипторов.
Значение
01>/dev/null Игнорировать STDOUT
2>/dev/null Игнорировать STDERR
2>&1 Направить STDERR в STDOUT
2>&- Закрыть STDERR (не рекомендуется)
З<>/dev/tty Связать файловый дескриптор 3 с /dev/tty в режиме чтения/записи
На основании этой таблицы мы рассмотрим самый сложный вариант перена-правления в решении:
$output = 'cmd 3>&1 1>&2 2>&3 3>&-';

Он состоит из четырех этапов.
Этап 1: 3>&1
Скопировать файловый дескриптор 1 в новый дескриптор 3. Прежнее место назначения STDOUT сохраняется в только что открытом дескрипторе.
Этап 2: 1>&2
Направить STDOUT по месту назначения STDERR. В дескрипторе 3 остается прежнее значение STDOUT.
Этап 3: 2>&3
Скопировать файловый дескриптор 3 в дескриптор 2. Данные STDERR будут поступать туда, куда раньше поступали данные STDOUT.
Этап 4: 3>&- Перемещение потоков закончено, и мы закрываем временный файловый дескриптор. Это позволяет избежать "утечки" дескрипторов. Если подобные цепочки сбивают вас с толку, взгляните на них как на обычные переменные и операторы присваивания. Пусть переменная $fd1 соответствует STDOUT, a $fd2 - STDERR. Чтобы поменять значения двух переменных, понадобится временная переменная для хранения промежуточного значения. Факти чески происходит следующее:
$fd3 = $fd1;
$fd1 = $fd2;
$fd2 = $fd3;
$fd3 = undef;
Когда все будет сказано и сделано, возвращаемая оператором '. . . ' строка будет соответствовать STDERR выполняемой команды, a STDOUT будет напран лен в прежний STDERR. Во всех примерах важна последовательность выполнения. Это связано с тем что командный интерпретатор обрабатывает перенаправления файловых дескрипторов слева направо.
system("prog args 1>tmpfile 2>&1");
system("prog args 2>&1 1>tmpfile");

Назад
Вперед