В своем предыдущем выпуске я воссоздал часть обычной утилиты Unix под названием grep, при этом добавив к ней дополнительную функциональность --- возможность игнорирования не-текстовых файлов. Сейчас я постараюсь показать как использовать существующую ``обвязку'' для использования существующей команды для выполнения самого поиска, но оставляя свойство поиска только в текстовых файлах.
Обвязка заменяет запуск отдельной программы: в моих скриптах я запускаю ``textgrep'' вместо ``grep'', например, если я хочу быть более ясным, то я могу назвать свою обвязку ``grep'', а затем поместить настоящий ``grep'' туда, где он не будет найден с помощью путей поиска, и известное только моей обвязке.
В этом случае, обвязка запускается с теми же аргументами что и стандартная программа grep. Обвязка будет проверять только имена файлов (игнорируя остальные аргументы) и будет удалять имена двоичных файлов. Все оставшиеся имена и ключи для команды будут прозрачно переданы настоящей команде grep.
Первая часть программы используется для отделения имен файлов от ключей программы. Давайте глянем на этот код:
while ($ARGV[0] =~ /^-[a-z]$/) { push @OPTS, shift; }
Здесь, мы сначала смотрим на первый элемент @ARGV
(обозначенный
как $ARGV[0]
), и если он выглядит как ключ команды (минус за
которым следует одиночная буква), то мы переносим этот элемент из массива
@ARGV
в массив @OPTS
. По умолчанию оператор ``shift''
удаляет первый элемент массива @ARGV
.
Hо этот метод не работает при использовании ключей ``-e'' и ``-f'', которые получают дополнительный параметр. Скорее всего этот параметр не будет выглядеть как ключ, так что мы должны сделать дополнительный шаг:
while ($ARGV[0] =~ /^-[a-z]$/) { if ($ARGV[0] =~ /^-[ef]$/) { push @OPTS, shift; } push @OPTS, shift; }
Теперь, если встречается ключ -e
или -f
, то
переносятся два аргумента, а не один. Как вы можете видеть, написание
обвязки требует достаточно полного понимания аргументов программы для
которой создается обвязка.
Далее, нам необходимо обработать то, что осталось в @ARGV
,
отбрасывая имена нетекстовых файлов. Это тот же код, что был использован в
предыдущей заметке:
@ARGV = "-" unless @ARGV; @ARGV = grep { -T or $_ eq "-" } @ARGV; exit 0 unless @ARGV;
Первый шаг вставляет ``-'' если массив пуст. Это не изменяет значения
@ARGV
, но дает нам возможность правильной обработки в
дальнейшем. Второй шаг удаляет из @ARGV
любые имена файлов,
которые не являются текстовыми (что определяется оператором -T
)
или не равны ``-'' (означая стандартный ввод). Стандартный ввод всегда
рассматривается как текстовый файл. Третья строка заставляет прекратить
работу с нулевым значением, если у нас нет имен обрабатываемых файлов.
В заключение, нам необходимо запустить grep с измененной командной строкой:
exec "grep", @OPTS, @ARGV;
Оператор ``exec'' запускает команду ``grep'' (которая находится в
текущих путях поиска), и передает ей ключи (из массива @OPTS
),
если они есть и затем список имен обрабатываемых файлов (из массива
@ARGV
).
Это выполняет большинство действий. Я мог бы назвать эту программу ``textgrep'', и она бы работала. Однако давайте глянем на обвязку, подобную описанной выше: что-то для замены команды ``grep'', чтобы я все равно мог запускать ее как ``grep''. Сначала, мне необходимо переименовать команду grep в что-то другое (например, в ``/usr/bin/realgrep''), и объяснить обвязке где ее найти:
$real_grep = "/usr/bin/realgrep";
Далее, я хочу запускать этот realgrep, но уверять, что он запущен как
``grep'' (в тех случаях, когда это его беспокоит). Это делается обманом о
его имени и расположении. Я хочу, чтобы настоящий grep верил, что он имеет
тоже название что и мой скрипт textgrep (который будет назван grep). К
счастью этот путь доступен в переменной $0
, и может быть передан в
новую программу как ``argv[0]'', используя интересное свойство
exec
:
exec $0 $real_grep, @OPTS, @ARGV;
Заметьте, что $0
вставлена между оператором exec
и
именем запускаемой программы.
Hе так уж и много надо сделать, чтобы настоящая команда grep не смогла найти свое расположение. Помещая все кусочки вместе мы получаем:
#!/usr/bin/perl $real_grep = "/usr/bin/realgrep"; while ($ARGV[0] =~ /^-[a-z]$/) { if ($ARGV[0] =~ /^-[ef]$/) { push @OPTS, shift; } push @OPTS, shift; } @ARGV = "-" unless @ARGV; @ARGV = grep { -T or $_ eq "-" } @ARGV; exit 0 unless @ARGV; exec $0 $real_grep, @OPTS, @ARGV; die "cannot exec $real_grep: $!";
Я добавил оператор ``die'' для обработки ошибок запуска программы
$real_grep
.
Почему используется ``exec'', а не ``system''? Использование
exec
заставляет скрипт на Perl вызвать ``exec'' для программы
grep, вместо того, чтобы запускать grep как дочерний процесс. Если этот
шаг завершился удачно (как скорее всего и будет, если у нас используется
правильное имя файла), то интерпретатор Perl заменяется на программу
grep. Код после exec
должен обработать сбойный exec
(например оператором ``die'').
Существуют другие применения скриптов-обвязок. Hапример, предположим, что у меня имеется пакет для работы с базой данных, который должен иметь определенные значения umask, определенный текущий каталог и несколько важных для него переменных среды. Хотя вы можете написать его и на языке командного процессора, давайте все равно взглянем как это будет выглядеть на Perl:
#!/usr/bin/perl my $DB_HOME = "/home/merlyn/database"; chdir $DB_HOME or die "cannot get to db dir: $!"; umask 2; $ENV{'DB_HOME'} = $DB_HOME; $ENV{'PATH'} .= ":$DB_HOME"; exec "accounting", @ARGV; die "Cannot exec accounting: $!";
Здесь я устанавливаю переменную Perl $DB_HOME
, задающую
каталог моего пакета. Затем я выполняю переход в этот каталог, и
устанавливаю значение umask
равным ``002'' (при этом снимается
доступ на запись для остальных пользователей). Затем, я устанавливаю
значение переменной среды DB_HOME
равным значению переменной Perl
с тем же именем. Заметьте, что переменные Perl не экспортируются
автоматически, так что этот код аналогичен:
setenv DB_HOME $DB_HOME
в C-shell, или:
export DB_HOME
в Bourne shell. Также я обновляю переменную среды PATH
,
добавляя туда каталог DB_HOME
, как один из каталогов содержащих
программы. Заметьте, что я добавляю новый каталог в конец списка, а не в
начало. Если бы я хотел добавить его в начало, я должен был бы записать это
выражение вот так:
$ENV{'PATH'} = "$DB_HOME:$ENV{'PATH'}";
И затем происходит запуск программы из моего пакета, за которым следует
диагностическое сообщение, в том случае если выполнение exec
прошло неудачно. Заметьте, что я передаю своей программе массив
@ARGV
без всякой обработки. Если бы были некоторые общие
аргументы, такие как ``-d $DB_HOME'', то эта строка выглядела бы вот
так:
exec "accounting", "-d", $DB_HOME, @ARGV;
(Это вероятно достаточно тупой пакет, который требует так много действий
по установке DB_HOME
, но к счастью вы смогли увидеть как я обошел
это ограничение).
Другим типом обвязок являются те, которые вносят беспорядок в стандартные файловые дескрипторы, для того, чтобы немного изменить поведение. Hапример, стандартная команда ``find'' имеет достаточно полезное свойство сообщать о каталогах, которые невозможно просканировать в стандартный поток сообщений об ошибках. Однако, когда нас не интересуют сообщения о том, что find не может сделать, то эти сообщения только раздражают нас.
Конечно, используя правильное перенаправление ввода-вывода, я могу
убрать сообщения стандартного вывода сообщений об ошибках
(stderr
). Однако, давайте создадим обвязку, вместо того, чтобы
полностью отбрасывать сообщения для STDERR
:
#!/usr/bin/perl open STDERR, ">/dev/null"; exec "find", @ARGV; die "Cannot exec find: $!";
Вот, достаточно просто. я мог бы поместить этот код в файл с именем ``qfind'' (сокращение для ``quiet find'', и затем запускать следующим образом:
qfind / -name '*perl*' -ls
и теперь я не буду получать сообщения, говорящие мне о каталогах в которые невозможен вход.
Я также мог бы сделать это для обвязки программы доступа к базе данных, для того, чтобы сохранять все сообщения в файле протокола (прямо перед запуском ``exec''):
open STDERR, ">>log";
Хорошо, это заканчивает мое обсуждение обвязок. Я надеюсь, что вы насладились им.