HTML page

Глава 1 Строки

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 ; # Строка-заголовок while ( {
@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

#

1

 

 

2

3

 

 

4

 

 

5 6

 

 

 

 

7

 

 

# Позиции

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

# 8

14

20

26 30

34

41

47

59

63

67

72

 

 

Я I

 

 

 

 

|

I I

|

I

|

I

|

|

|

 

 

END

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

FLAGS

UID

PID

PPID

PRI

NI

SIZE

RSS

WCHAN

STA

TTY

TIME

COMMAND

100

0

1

0

0

0

760

432

doselect

S

?

0:02

init

140

0

187

1

0

0

784

452

doselect

S

?

0:02

syslogd

100100

101

428

1

0

0

1436

944

doexit

S

1

0:00

/bin/

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

login

100140

99 30217

402

0

0

1552

1008

posixlock

S

7

0:00

httpd

0

101

593

428

0

0

1780

1260

copythread

S

1

0:00

-tcsh

100000

101 30639

9562

17

0

924

496

 

 

R

Р1

0:00

ps axl

0

101

25145

9563

0

0

2964

2360

idetaperea

S

Р2

0:06

trn

100100

0 10116

9564

0

0

1412

928

setupframe

Т

РЗ

0:00

ssh -C

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

WWW

100100

0 26560

26554

0

0

1076

572

setupframe

т

Р2

0:00

less

100000

101 19058

9562

0

0

1396

900

setupframe

т

Р1

0:02

nvi /

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Imp/a



В программе 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), пользователь может указать произвольный критерий отбора:
sub id() { $_->{ID} }
sub title() { $_->{TITLE} }
sub executive { title ='/(?: vice-)?president/i }
# Критерии отбора указываются при вызове дгер @slowburners = дгер { id < 10 && !executive } employees; По причинам, связанным с безопасностью и быстродействием, такой подход редко встречается в реальных механизмах, описанных в главе 14 "Базы данных". В частности, он не поддерживается в SQL, но имея в своем распоряжении Peri и некоторую долю изобретательности, нетрудно создать свой собственный вариант. Подобная методика использована в поисковой системе http://mox. perl.com/ cgi-bin/MxScreen, но вместо получения данных от ps записи представляют собой хэши Peri, загружаемые из базы данных.
copyright 2000 Soft group
v