Next Previous Contents

Unix Review Column 10

Randal Schwartz

Сентябрь 1996

В своем предыдущем выпуске я воссоздал часть обычной утилиты 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";

Хорошо, это заканчивает мое обсуждение обвязок. Я надеюсь, что вы насладились им.


Next Previous Contents