ЭЛЕКТРОННАЯ БИБЛИОТЕКА КОАПП |
Сборники Художественной, Технической, Справочной, Английской, Нормативной, Исторической, и др. литературы. |
1.18. Программа: psgrepМногие программы (в том числе ps, netstat, Is -/, find -Is и Icpdump) часто выдают большие объемы данных. Файлы журналов тоже быстро увеличиваются в размерах, что затрудняет их просмотр. Такие данные можно обработать программой-фильтром типа grep и отобрать из них лишь часть строк, однако регулярные выражения плохо согласуются со сложной логикой - достаточно взглянуть па ухищрения, па которые приходится пускаться в рецепте 6.17. В частности, нам хотелось бы иметь возможность обращаться с полноценными запросами к выводу программы или файлу журнала. Допустим, вы спрашиваете у ps: "Покажи мне все непривилегированные процессы размером больше 10Кб" или "Какие команды работают на псевдоконсолях?" Программа psgrep умеет делать все это и бесконечно большее, потому что в ней критерии отбора не являются регулярными выражениями; они состоят из полноценного кода Peri. Каждый критерий последовательно применяется к каждой строке вывода. В результате выводятся лишь те данные, которые удовлетворяют всем аргументам. Ниже приведены примеры критериев поиска и соответствующие им командные строки. o Строки со словами, заканчивающимися на sh:% psgrep '/sh\b/' o Процессы с именами команд, заканчивающимися на sh: % psgrep 'command =~ /sh$/' o Процессы с идентификатором пользователя, меньшим 10: % psgrep 'uid < 10' o Интерпретаторы с активными консолями: % psgrep 'command =~ '/"-/' 'tty ne "?'" o Процессы, запущенные на псевдоконсолях: % psgrep 'tty =~ /"[p-t]' o Отсоединенные непривилегированные процессы: % psgrep 'uid && tty eq "?"' o Большие непривилегированные процессы: % psgrep 'size > 10 * 2**10' 'uid != О' Ниже показаны данные, полученные при последнем вызове psgrep на нашем компьютере. Как и следовало ожидать, в них попал только net sea ре и его вспомогательный процесс: FLAGS UID PID PPID PRI NI SIZE RSS WCHAN STA TTY TIME COMMAND 0 101 9751 1 0 0 14932 9652 do.select S p1 0:25 netscape 100000 101 9752 9751 0 0 10636 812 do_select S p1 0:00 (dns helper) В примере 1.6 приведен исходный текст программы psgrep. Пример 1.6. psgrep #!/usr/bin/peri -w #psgrep - фильтрация выходных данных ps # с компиляцией пользовательских запросов в программный код # use strict; # Все поля из заголовка PS my ©fieldnames = qw(FLAGS UID PID PPID PRI NICE SIZE RSS WCHAN STAT TTY TIME COMMAND); # Определение формата распаковки (в примере # жестко закодирован формат ps для Linux) my $fmt = cut2fmt(8, 14, 20, 26, 30, 34, 41, 47, 59, 63, 67, 72); my %fields; # Для хранения данных die "Thanatos unless @ARGV; usage: $0 criterion ... Each criterion is a Peri expression involving: @fieldnames All criteria must be met for a line to be printed. Thanatos # Создать синонимы для uid, size, UID, SIZE и т.д. # Пустые скобки необходимы для создания прототипа без аргументов for my $name (@fieldname) { no strict 'rets'; -name = *{lc $name} = sub () { $fields{$name} }; } my $code = "sub is_desirable { " . join(" and ", @ARGV) . " } "; unless (eval $code.1) { die "Error in code: $@>\n\t$code\n"; } open (PS, "ps wwaxi |") || die "cannot fork: $!"; print scalar @fields{@fieldnames} = trim(unpack($fmt, $_)); print if is_desirable(); # Строки, удовлетворяющие критериям } close(PS) || die "ps failed!"; # Преобразовать позиции разреза в формат распаковки sub cut2fmt { my(@positions) = @_; my Stemplate = ' '; my $lastpos = 1; foreach $place(positions) { $template .= "A" . ($place - $lastpos) . " "; $lastpos = $place; } $template .= "A*"; return $template; } suu irim { my @out = @_; for (Oout) { s/"\s+//; s/\s+$//; } return wantarray ? Oout : $out[0]; } # Следующий шаблон использовался для определения позиций разреза. # Далее следует пример входных данных й-123456789012345678901234567890123456789012345678901234567890123456789012345
В программе psgrep объединены многие приемы, представленные в книге.
Об удалении начальных и конечных пропусков рассказано в рецепте 1.14. Преобразование
позиций разреза в формат unpack для извлечения полей с фиксированным положением
рассматривается в рецепте 1.1. Поиску регулярных выражений в строках посвящена
вся глава 6. Многострочный текст, передаваемый die, представляет собой
встроенный документ (см. рецепты 1.10 и 1.11). Присваивание @fields{@fieldnames}
заносит сразу несколько величин в хэш %fields. Хэши рассматриваются в рецептах
4.7 и 5.10. Входные данные программы-примера, расположенные под __END__,
описаны в рецепте 7.6. На стадии разработки для тестирования использовались
"консервированные" данные, полученные через файловый манипулятор DATA.
Когда программа заработала, мы перевели ее на получение данных из присоединенной
команды ps, однако исходные данные были оставлены для будущего переноса
на другие платформы и сопровождения. Конвейерный запуск других программ
рассматривается в главе 16 "Управление процессами и межпроцессные взаимодействия",
особенно в рецептах 16.10 и 16.13. Настоящая сила и выразительность psgrep
обусловлены тем, что в Peri строковые аргументы могут представлять собой
не просто строки, а программный код Peri. Похожий прием использован в рецепте
9.9, за исключением того, что is psgrep аргументы пользователя "упакованы"
в процедуру is_desirable. При этом компиляция строк в код Peri выполняется
всего один раз - еще перед запуском той программы, чей вывод мы обрабатываем.
Например, при запросе UID ниже 10 будет сгенерирована следующая строка:
eval "sub is_desirable { uid < 10 } " . 1; Загадочное . 1 в конце присутствует
для того, чтобы при компиляции пользовательского кода команда eval возвращала
истинное значение. В этом случае нам даже не придется проверять $@ на предмет
ошибок компиляции, как это делается в рецепте 10.12. Использование произвольного
кода Peri в фильтрах для отбора записей - невероятно мощная возможность,
но она не является абсолютно оригинальной. Peri многим обязан языку программирования
awk, который часто применялся для подобной фильтрации. Один из недостатков
awk заключался в том, что он не мог легко интерпретировать входные данные
в виде полей фиксированной длины (вместо полей, разделенных особыми символами).
Другой недостаток - отсутствие мнемонических имен полей; в awk использовались
имена $1, $2 и т. д. К тому же Peri может делать многое такое, на что не
способен awk. Пользовательские критерии даже не обязаны быть простыми выражениями.
Например, следующий вызов инициализирует переменную $id номером пользователя
nobody и затем использует ее в выражении:
% psgrep 'no strict "vars";
BEGIN { $id = getpwnamC'nobody") } uid == $id Но как использовать эти
слова, uid, command и size, даже не снабжая их символом $ для представления
соответствующих полей входных записей? Мы напрямую манипулируем с таблицей
символов, присваивая замыкания (closures) неявным тип-глобам (typeglobs),
которые создают функции с соответствующими именами. Замыкания описаны в
рецепте 11.4, а их присвоение тип-глобам для создания синонимов функций
- в рецепте 10.14. Однако в psgrep встречается нюанс, отсутствующий в этих
рецептах, - речь идет о пустых скобках в замыкании. Благодаря скобкам функция
может использоваться в выражениях везде, где допускается отдельная величина
(например, строка или числовая константа). В результате создается пустой
прототип, а функция обращения к полю (например, uid) вызывается без аргументов,
по аналогии со встроенной функцией time. Если не создать для функций пустые
прототипы, выражения "uid < 10" или "size / 2 > rss" приведут в замешательство
синтаксический анализатор - он увидит в них незаконченный глоб (wildcard
glob) или шаблон поиска. Прототипы рассматриваются в рецепте 10.11. Показанная
версия psgrep получает входные данные от команды ps в формате Red Hat Linux.
Чтобы перенести ее в другую систему, посмотрите, в каких столбцах начинаются
заголовки. Такой подход не ограничивается спецификой ps или системы UNIX.
Это общая методика фильтрации входных записей с использованием выражений
Peri, которая легко адаптируется для другой структуры записи. Поля могут
быть выстроены в столбцы, разделены запятыми или заключены в скобки. После
небольшого изменения в функциях отбора программа даже подойдет для работы
с пользовательской базой данных. Если у вас имеется массив записей (см.
рецепт 11.9), пользователь может указать произвольный критерий отбора:
|