урок: предыдущий следующий
В этой главе мы покажем, как можно манипулировать самими файлами, а не только содержащимися в них данными. При демонстрации процедуры доступа к файлам и каталогам мы будем пользоваться семантикой UNIX (a также POSIX и Linux). Есть и другие механизмы доступа к файловым системам, но описываемые здесь средства являются стандартными для современных файловых систем.
Когда вы из командной строки shell задаете выполнение какой-либо команды, он обычно создает новый процесс. Этот новый процесс становится порожденным процессом shell, выполняется независимо, но в координации с последним.
Аналогичным образом Perl-программа в состоянии запускать новые процессы и может делать это, как и большинство других операций, несколькими способами.
Самый простой способ запуска нового процесса — использовать для этого функцию system. В простейшей форме эта функция передает в совершенно новый shell /bin/sh одну строку, которая будет выполняться как команда. По выполнении команды функция system возвращает код завершения данной команды (если все было нормально — это, как правило, 0). Вот пример того, как Perl-программа выполняет команду date c помощью shell*:
Здесь мы не проверяем возвращаемое значение, но неудачный исход выполнения команд н date вряд ли возможен.
Куда идет результат этой команды? Откуда поступают исходные данные, если они нужны команде? Хорошие вопросы, и ответы на них позволят узнать, чем отличаются различные способы создания процессов.
* В данном случае shell фактически не используется, поскольку Рсгі сам выполняет операций shell, если командная строка достаточно проста, как в данном случае.
Три стандартных файла для функции system (стандартный ввод, стандартный вывод и стандартный вывод ошибок) наследуются от Perl-процесса. Таким образом, результат выполнения команды date в приведенном выше примере направляется туда же, куда поступает результат выполнения функции print stdout — скорее всего, на дисплей вызвавшего ее пользователя. Поскольку вы запускаете shell, то можете переадресовать стандартный вывод, пользуясь обычными операциями переадресации. Например, чтобы направить результаты работы команды date в файл right_now, нужно сделать что-то вроде этого:
|
system("date >right_now") && die "cannot create right_now";
|
На этот раз ми не только посылаем результат команды date в файл, выполняя переадресацию в shell, но и проверяем статус возврата. Если статус возврата — значение "истина" (не нуль), это значит, что с командой shell что-то произошло, и функция die выполнит свою миссию. Данное правило обратно обычным правилам выполнения операций в Perl: ненулевое возвращаемое значение операций system, как правило, указывает на какую-то ошибку.
Аргументом функции system может бить все, что пригодно для передачи в shell, поэтому можно задавать сразу несколько команд, разделяя их точками с запятой или символами новой строки. Процессы, после которых указан символ &, запускаются, но программа не ждет их завершения, т.е. в данном случае все происходит аналогично тому, как если бы вы ввели в shell строку, которая заканчивается символом &.
Вот пример задания команд date и who в shell с передачей результатов в файл, заданный Perl-переменной. Все это выполняется в фоновом режиме, чтобы для продолжения выполнения Perl-сценария не нужно било ждать завершения данного процесса.
$where = "who_out.".++$і; # получить новое имя файла
system "(date; who) >$where &";
|
В этом случае функция system возвращает код выхода shell и показывает таким образом, успешно ли бил запущен фоновый процесс, но не сообщает, были ли успешно выполнены команды date и who. В этой заключенной в двойные кавычки строке производится интерполяция переменных, поэтому переменная $where заменяется своим значением (это делает Perl, а не shell). Если бы вы хотели обратиться к переменной shell с именем $where, вам нужно было бы поставить перед знаком доллара обратную косую или использовать строку в одинарных кавычках.
Помимо стандартных дескрипторов файлов, порожденный процесс наследует от родительского процесса много других вещей. Это текущее значение, заданное командой umask, текущий каталог и, конечно, идентификатор пользователя.
Кроме того, порожденный процесс наследует все переменные среды. Эти переменные обычно изменяются командой csh setenv или же соответствующим присваиванием и команд ой export shell (/bin/sh). Переменные среды используются многими утилитами, включая сам shell, для изменения порядка работы этих утилит и управления ими.
В Perl предусмотрена возможность проверки и изменения текущих переменных среды посредством специального хеша, который называется %env. Каждый ключ этого хеша соответствует имени переменной среды, а соответствующее значение — значению переменной. Содержимое данного хеша отражает параметры среды, переданные Perl родительским shell; изменение хеша изменяет параметры среды, которую использует Perl и порожденные им процессы, но не среды, используемой родительскими процессами.
Вот простая программа, которая работает, как printenv:
foreach $key (sort keys %ENV) {
print "$key = $ENV{$key}\n";
}
|
Обратите внимание: знак равенства здесь — это не символ операции присваивания, а просто текстовый символ, с помощью которого функция print выдает сообщения вида TERM=xterm или U3ER=merlyn.
Вот фрагмент программы, с помощью которого значение переменной path изменяется таким образом, чтобы поиск команды grep, запущенной функцией system, производился только в "обычных" местах:
$oldPATH = $ENV{"PATH"}; # сохранить предыдущий путь
$ENV{"PATH"} = "/bin:/usr/bin:/usr/ucb"; # ввести известный путь
system("grep fred bedrock >output"); # запустить команду
$ENV{"PATH"} = $oldPATH; # восстановить предыдущий путь
|
Как много текста придется набирать! Гораздо быстрее будет просто установить локальнее значение для этого элемента хеша.
Несмотря на наличие некоторых недостатков, операция local может делать одну вещь, которая не под силу операции my: она способна присваивать временное значение одному элементу массива или хеша.
{
local $ENV{"PATH"} = "/bin:/usr/bin:/usr/ucb";
system("grep fred bedrock >output");
}
|
Функция system может принимать не один аргумент, а список аргументов. В этом случае Perl не передает список аргументов в shell, а рассматривает первый аргумент как подлежащую выполнению команду (при необходимости производится ее поиск согласно переменной path), а остальные аргументы — как аргументы команды без обычной для shell интерпретации. Другими словами, вам не нужно заключать в кавычки пробельные символы и беспокоиться об аргументах, которые содержат угловые скобки, потому что все это — просто символы, передаваемые в программу. Таким образом, следующие две команды эквивалентны:
system "grep 'fred flintstone' buffaloes"; # с использованием shell
system "grep","fred flintstone","buffaloes"; # без использования shell
|
Применение в функции system списка, а не одной строки, экономит также один процесс shell, поэтому поступайте так при любой возможности. (Если форма функции system c одним аргументом достаточно проста, Perl сам оптимизирует код, полностью убирая вызов shell и обращаясь к соответствующей программе непосредственно, как если бы вы использовали вызов функции с несколькими аргументами.)
Вот еще один пример эквивалентных форм:
@cfiles = ("fred.c","barney.c"); # что компилировать
@options = ("-DHARD","-DGRANITE"); # опции
system "cc -o slate @options @cfiles"; # c shell
system "cc","-o","slate",@options,@cfiles; # без shell
|
Еще один способ запуска процесса — заключить командную строку для /Ып/sh в обратные кавычки. Как и в shell, этот механизм запускает команду и ожидает ее завершения, получая данные со стандартного вывода по мере их поступления:
|
$now = "the time is now".`date`; # получает текст и дату
|
Значение переменной $now теперь представляет собой текст the time is now и результат выполнения команды date(/) (включая конечный символ новой строки):
|
the time is now Fri Aug 13 23:59:59 PDT 1996
|
Если взятая в обратные кавычки команда используется не в скалярном, а в списочном контексте, то возвращается список строковых значений, каждое из которых представляет собой строку (оканчивающуюся символом новой строки*) из результата выполнения команды. В примере с командой date у нас был бы всего один элемент, потому что она видала всего одну строку текста. Результат работы команды who выглядит так:
|
merlyn tty42 Dec 7 19:41 fred ttylA Aug 31 07:02 barney ttylF Sep 1 09:22
|
Вот как можно получить этот результат в списочном контексте:
foreach $_ (`who`) { # один раз для каждой строки текста из who
($who,$where,$when) = /(\S+)\s+(\S+)\s+(.*)/;
print "$who on $where at $when\n";
}
|
* Или символом, который у вас занесен в переменную $/.
При каждом выполнении этого цикла используется одна строка выходных данных команды who, потому что взятая в обратные кавычки команда интерпретируется в списочном контексте.
Стандартный ввод и стандартный вывод ошибок команды, взятой в обратные кавычки, наследуются от Perl-процесса*. Это значит, что обычно стандартный вывод таких команд ви можете получить как значение строки, заключенной в обратные кавычки. Одна из распространенных операций — объединение стандартного вывода ошибок со стандартным выводом, чтобы команда в обратных кавычках "подбирала" их оба. Для этого используется конструкция shell 2>&1:
|
die "rm spoke!" if `rm fred 2>&1`;
|
Здесь Perl-процесс завершается, если rm посылает какое-нибудь сообщение — либо на стандартный вывод, либо на стандартный вывод ошибок, потому что результат больше не будет пустой строкой (пустая строка соответствовала бы значению "ложь").
* На самом деле все не так просто. См. соответствующий ответ в разделе 8 сборника часто задаваемых вопросов по Perl ("Как перехватить stderr из внешней команды?"). Если у вас Perl версии 5.004, этот сборник распространяется как обычная man-страница — в данном случае perlfaq8(/).
Следующий способ запуска процесса — создание процесса, который выглядит как дескриптор файла (аналогично библиотечной подпрограмме popen(3), если вы с ней знакомы). Мы можем создать для процесса дескриптор файла, который либо получает результат работы процесса, либо подает в него входные данные*. Ниже приведен пример создания дескриптора файла для процесса who(/). Поскольку этот процесс выдает результат, который мы хотим прочитать, мы создаем дескриптор файла, открытый для чтения:
|
open(WHOPROC, "who |"); # открыть who для чтения
|
Обратите внимание на вертикальную черту справа от who. Эта черта информирует Perl о том, что данная операция open относится не к имени файла, а к команде, которую необходимо запустить. Поскольку черта стоит справа от команды, данный дескриптор файла открывается для чтения. Это означает, что предполагается прием данных со стандартного вывода команды who. (Стандартный ввод и стандартный вывод ошибок продолжают использоваться совместно с Perl-процессом.) Для остальной части программы дескриптор whoproc — это просто дескриптор файла, который открыт для чтения, что означает возможность выполнения над файлом всех обычных операций ввода-вывода. Вот как можно прочитать данные из команды who в массив:
* Но не одновременно. Примеры двунаправленной связи приведены в главе 6 книги Programming Perl и на man-странице реrlірс(/).
Аналогичным образом для запуска команды, которой необходимы входные данные, мы можем открыть дескриптор файла процесса для записи, поставив вертикальную черту слева от команды, например:
open(LPR,"|Ipr -Psiatewriter") ;
print LPR Srockreport;
close(LPR);
|
В этом случае после открытия lpr мы записываем в него данные и закрываем данный файл. Открытие процесса с дескриптором файла позволяет выполнять команду параллельно с Perl-программой. Задание для дескриптора файла команды close заставляет Perl-программу ожидать завершения процесса. Если дескриптор не будет закрыт, процесс может продолжаться даже после завершения Perl-программы.
Открытие процесса для записи предполагает, что стандартный ввод команды будет получен из дескриптора файла. Стандартный вывод и стандартный вывод ошибок используются этим процессом совместно с Perl. Как и прежде, вы можете использовать переадресацию ввода-вывода в стиле /bin/sh. Вот как в нашем последнем примере можно отбрасывать сообщения об ошибках команды Ipr.
|
open(LPR,"|Ipr -Psiatewriter >/dev/null 2>&1");
|
С помощью операций >/dev/null обеспечивается отбрасывание стандартного вывода путем переадресации его на нулевое устройство. Операция 2>&1 обеспечивает передачу стандартного вывода ошибок туда, куда направляется стандартный вывод, поэтому сообщения об ошибках также отбрасываются.
Можно даже объединить все эти фрагменты программы и в результате получить отчет обо всех зарегистрированных пользователях, кроме Фреда:
open (WHO,"who |") ;
open (LPR,"|Ipr - Psiatewriter");
while (<WHO>) {
unless (/fred/) { # не показывать имя Фред
print LPR $_;
}
}
close WHO;
close LPR;
|
Считывая из дескриптора who по одной строке, этот фрагмент кода выводит в дескриптор lpr все строки, которые не содержат строкового значения fred. В результате на принтер выводятся только те строки, которые не содержат имени fred.
Вовсе не обязательно указывать в команде open только по одной команде за один прием. В ней можно задать сразу весь конвейер. Например, следующая строка запускает процесс ls(/), который передает свои результаты по каналу в процесс tail(/), который, в свою очередь, передает свои результаты в дескриптор файла whopr:
|
open(WHOPR, "ls | tail -r |");
|
Еще один способ создания нового процесса — клонирование текущего Perl-процесса с помощью UNIX-функции fork. Функция fork делает то же самое, что и системный вызов fork(2): создает клон текущего процесса. Этот клон (он называется порожденным процессом, а оригинал — родительским) использует тот же выполняемый код, те же переменные и даже те же открытые файлы. Различаются эти два процесса по возвращаемому значению функции fork: для порожденного процесса оно равно нулю, а для родительского — ненулевое (или undef, если этот системный вызов окажется неудачным). Ненулевое значение, получаемое родительским процессом,— это не что иное как идентификатор порожденного процесса. Вы можете проверить возвращаемое значение и действовать соответственно:
if (!defined($child_pid = fork())) {
die "cannot fork: $!";
}
elsif ($pid) {
# я — родительский процесс
}
else {
# я — порожденный процесс
}
|
Чтобы максимально эффективно использовать этот клон, нам нужно изучить еще несколько функции, которые весьма похожи на своих UNIX-тезок: это функции wait, exit и ехес.
Самая простая из них — функция ехес. Это почти то же самое, что и функция system, за тем исключением, что вместо запуска нового процесса для выполнения shell-команды Perl заменяет текущий процесс на shell. После успешного выполнения ехес Perl-программа исчезает, поскольку вместо нее выполняется затребованная программа. Например,
заменяет текущую Perl-программу командой date, направляя результат этой команды на стандартный вывод Perl-программы. После завершения команды date делать больше нечего, потому что Perl-программа давно исчезла.
Все это можно рассматривать и по-другому: функция system похожа на комбинацию функции fork c функцией ехес, например:
# МЕТОД І... использование system:
system("date");
# МЕТОД 2... использование fork/exec:
unless (fork) {
# fork выдала нуль, поэтому я — порожденный процесс и я выполняю:
exec ("date") ; # порожденный процесс становится командой date
}
|
Использовать fork и exec таким способом — не совсем правильно, потому что команда date и родительский процесс "пыхтят" одновременно, их результаты могут переметаться и испортить все дело. Как дать родительскому процессу указание подождать, пока не завершится порожденный процесс? Именно это и делает функция wait; она ждет завершения данного (да и любого, если быть точным) порожденного процесса. Функция waitpid более разборчива: она ждет завершения не любого, а определенного порожденного процесса:
if (!defined($kidpid = fork())) {
# fork возвратила undef, т.е. неудача
die "cannot fork: $!";
}
elsif ($pid == 0) {
# fork возвратила О, поэтому данная ветвь — порожденный процесс
exec("date");
# если exec терпит неудачу, перейти к следующему оператору
die "can't exec date: $!";
}
else {
# fork возвратила не 0 и не undef,
# поэтому данная ветвь — родительский процесс
waitpid($kidpid, 0);
}
|
Если все это кажется вам слишком сложным, изучите системные вызовы fork(2) и ехес(2), отыскав материалы о них в каком-нибудь руководстве по ОС UNIX, потому что Perl просто передает вызовы этих функций прямо в системные вызовы UNIX.
Функция exit обеспечивает немедленный выход из текущего Perl-процесса. Она используется для прерывания Perl-программы где-нибудь посередине или — вместе с функцией fork — для выполнения Perl-кода в процессе с последующим выходом. Вот пример удаления нескольких файлов из каталога в фоновом режиме с помощью порожденного Perl-процесса:
unless (defined ($pid = fork)) {
die "cannot fork: $!";
}
unless ($pid) {
unlink </tmp/badrock.*>; # удалить эти файлы
exit; # порожденный процесс останавливается здесь
}
# родительский процесс продолжается здесь
waitpid($pid, 0); # после уничтожения порожденного процесса нужно все убрать
|
Без использования функций exit порожденный процесс продолжал бы выполнять Perl-код (со строки "# родительский процесс продолжается здесь") — а как раз этого нам и не нужно.
Функция exit может иметь необязательный параметр, служащий числовым кодом выхода, который воспринимается родительским процессом. По умолчанию выход производится с нулевым кодом, показывающим, что все прошло нормально.
Операции, служащие для запуска процессов, перечислены в таблице 14.1.
Таблица 14.1. Операции запуска процессов
| Операция |
Стандартный ввод |
Стандартный вывод |
Стандартный вывод ошибок |
Нужно ли ожидать завершения процесса |
| System() |
Наследуется от программы |
Наследуется от программы |
Наследуется от программы |
Да |
| Строка в обратных кавычках |
Наследуется от программы |
Принимается как строковое значение |
Наследуется от программы |
Да |
| Запуск процесса как дескриптора файла для вывода при помощи команды open() |
Соединен с дескриптором файла |
Наследуется от программы |
Наследуется от программы |
Только во время выполнения close() |
| fork, exec, wait, waitpid |
Выбирается пользователем |
Выбирается пользователем |
Выбирается пользователем |
Выбирается пользователем |
Самый простой способ создать процесс — использовать для этого функцию system. На стандартный ввод, вывод и вывод ошибок это не влияет (они наследуются от Perl-процесса). Строка в обратных кавычках создает процесс и передает данные со стандартного вывода этого процесса как строковое значение для Perl-программы. Стандартный ввод и стандартный вывод ошибок не изменяются. Оба эти метода требуют завершения процесса до выполнения другого кода.
Простой способ получить асинхронный процесс (процесс, который позволяет продолжать выполнение Perl-программы до своего завершения) — открыть команду как дескриптор файла с созданием канала для стандартного ввода или стандартного вывода этой команды. Команда, открытая как дескриптор файла для чтения, наследует стандартный ввод и стандартный вывод ошибок от Perl-программы; команда, открытая как дескриптор файла для записи, наследует от Perl-программы стандартный вывод и стандартный вывод ошибок.
Самый гибкий способ запустить процесс — заставить программу вызвать функции fork, ехес и wait или waitpid, которые полностью соответствуют своим UNIX-тезкам. С помощью этих функции вы можете запустить какой-либо процесс синхронно или асинхронно, а также конфигурировать по своему усмотрению стандартный ввод, стандартный вывод и стандартный вывод ошибок*.
* Полезно такое знать о формах типа open(STDERR, ">&stdout") , используемых для точной настройки дескрипторов файлов. См. пункт open в главе 3 книги Programming Perl или на man-странице perlfunc(/).
Один из вариантов организации меж процессного взаимодействия основан на передаче и приеме сигналов. Сигнал — это одноразрядное сообщение (означающее, что "произошло данное событие"), которое посылается в процесс из другого процесса или из ядра. Сигналам присваиваются номера, обычно из диапазона от единицы до небольшого числа, например 15 или 31. Одни сигналы (фиксированные) имеют предопределенное значение и посылаются в процесс автоматически при возникновении определенных обстоятельств (например, при сбоях памяти или в исключительных ситуациях, возникающих при выполнении операций с плавающей запятой). Другие сигналы генерируются исключительно пользователем из других процессов, но не из всех, а только их тех, которые имеют разрешение на передачу сигналов. Передача сигнала разрешается только в том случае, если вы являетесь привилегированным пользователем или если передающий сигнал процесс имеет тот же идентификатор пользователя, что и принимающий.
Ответ на сигнал называется действием сигнала. Фиксированные сигналы выполняют определенные действия по умолчанию, например, осуществляют прерывание или приостановку процесса. Остальные сигналы по умолчанию полностью игнорируются. Почти для любого сигнала действие по умолчанию может быть переопределено, с тем чтобы данный сигнал либо игнорировался, либо перехватывался (с автоматическим вызовом указанной пользователем части кода).
Все это аналогично тому, что делается и в других языках программирования, но сейчас излагаемый материал приобретет специфический Perl-оттенок. Когда Perl-процесс перехватывает сигнал, асинхронно и автоматически вызывается указанная вами подпрограмма, моментально прерывая выполнявшийся до нее код. Когда эта подпрограмма завершается, выполнение прерванного кода возобновляется, как будто ничего не случилось (за исключением появления результатов действий, выполненных этой подпрограммой,— если она вообще что-нибудь делала).
Обычно подпрограмма-обработчик сигнала делает одно из двух: прерывает программу, выполнив "очистку", или устанавливает какой-то флаг (например, глобальную переменную), которую данная программа затем проверяет*.
* Попытка выполнения действий более сложных, чем вышеописанные, вероятнее всего, запутает ситуацию; большинство внутренних механизмов Perl "не любят", когда их вызывают одновременно в основной программе и из подпрограммы. Ваши системные библиотеки тоже этого "не любят".
Для того чтобы зарегистрировать подпрограммы-обработчики сигналов в Perl, нужно знать имена сигналов. После регистрации обработчика сигнала Perl при получении этого сигнала будет вызывать выбранную подпрограмму.
Имена сигналов определяются на man-странице signal(2), а также, как правило, в подключаемом С-файле /usr/include/sys/signal.h. Эти имена обычно начинаются с букв sig, например sigint, sigquit и sigkill. Чтобы объявить подпрограмму my_sigint_catcher () обработчиком сигнала sigint, мы должны установить соответствующее значение в специальном хеше %sig. В этом хеше в качестве значения ключа int (это sigint без sig) следует указать имя подпрограммы, которая будет перехватывать сигнал sigint:
|
$SIG{'INT'} = 'my_sigint_catcher';
|
Но нам понадобится также определение этой подпрограммы. Вот пример простого определения:
sub my_sigint_catcher {
$saw_sigint = 1; # установить флаг
}
|
Данный перехватчик сигналов устанавливает глобальную переменную и сразу же возвращает управление. Выполнение программы продолжается с той позиции, в которой оно было прервано. Обычно сначала обнуляется флаг $saw_sigint, соответствующая подпрограмма определяется как перехватчик сигнала sigint, а затем следует код основной программы, например:
$saw_sigint = 0; # очистить флаг
$SIG{'INT'} = 'my_sigint_catcher'; # зарегистрировать перехватчик
foreach (@huge_array) {
# что-нибудь сделать
# еще что-нибудь сделать
# и еще что-нибудь
if ($saw_sigint) { # прерывание нужно?
# здесь "очистка"
last;
}
}
$SIG{'INT'} = 'DEFAULT'; # восстановить действие по умолчанию
|
Особенность использования сигнала в данном фрагменте программы состоит том, что значение флага проверяется в важнейших точках процесса вычисления и используется для преждевременного выхода из цикла; при этом выполняется и необходимая "очистка". Обратите внимание на последний оператор в приведенном выше коде: установка действия в значение default восстанавливает действие конкретного сигнала по умолчанию (следующий сигнал sigint немедленно прервет выполнение программы). Еще одно полезное специальное значение вроде этого — ignore, т.е. "игнорировать сигнал" (если действие по умолчанию — не игнорировать сигнал, как у sigint). Для сигнала можно установить действие ignore, если не нужно выполнять никакой "очистки" и вы не хотите преждевременно завершать выполнение основной программы.
Один из способов генерирования сигнала sigint — заставить пользователя нажать на клавиатуре терминала соответствующие прерыванию клавиши (например, [Ctrl+C]). процесс тоже может генерировать сигнал sigint, используя для этого функцию kill. Данная функция получает номер или имя сигнала и посылает соответствующий сигнал в процессы (обозначенные идентификаторами) согласно списку, указанному после сигнала. Следовательно, для передачи сигнала из программы необходимо определить идентификаторы процессов-получателей. (Идентификаторы процессов возвращаются некоторыми функция-ми, например функцией fork, и при открытии программы как дескриптора файла функцией open). Предположим, вы хотите послать сигнал 2 (известный также как sigint) в процессы 234 и 237. Это делается очень просто:
kill(2,234,237); # послать SIGINT в 234 и 237
kill('INT',234,237); # то же самое
|
Ответы к упражнениям приведены в приложении А.
- Напишите программу, которая получает результат команды date и вычисляет текущий день недели. Если день недели — рабочий день, выводить get to work, в противном случае выводить go play.
- Напишите программу, которая получает все реальные имена пользователей из файла /etc/passwd, а затем трансформирует результат команды who, заменяя регистрационное имя (первая колонка) реальным именем. (Совет: создайте хеш, где ключ — регистрационное имя, а значение — реальное имя.)
- Попробуйте выполнить эту задачу с использованием команды who как в обратных кавычках, так и открытой как канал. Что легче?
- Модифицируйте предыдущую программу так, чтобы ее результат автоматически поступал на принтер. (Если у вас нет доступа к принтеру, то, вероятно, вы можете послать самому себе сообщение электронной почты.)
- Предположим, функция mkdir перестала работать. Напишите подпрограмму, которая не использует mkdir, а вызывает /bin/mkdir с помощью функции system. (Убедитесь в том, что она работает с каталогами, в именах которых есть пробел.)
- Расширьте программу из предыдущего упражнения так, чтобы в ней устанавливались права доступа (с помощью функции chmod).
|