Глава 17В этой главе:
Открытие и закрытие DBM-хешей Использование DBM-хеша Базы данных произвольного доступа с записями фиксированной длины Базы данных с записями переменной длины (текстовые) Упражнения Работа с пользовательскими базами данныхDBM-базы данных и DBM-хетиВ большинстве UNIX-систем сть стандартная библиотека, которая называется DBM. Зта библиотека представляет собой простую систему управления базами данных, которая позволяет программам записывать набор пар ключ-значение в пару файлов. В зтих файлах хранятся значення базы данных в промежутках между вызовами программ, использующих ее, и зти программы могут вводить в базы данных новые значення, обновлять суще-ствующие и удалять старые.Библиотека DBM довольно проста, но, учитывая ее доступность, неко-торые системные программы активно используют зту библиотеку для своих довольно скромных нужд. Например, sendmail (а также ее варианты и производные) хранит базу данных aliases (соответствие адресов злектронной почты и имен получателей) как DBM-базу данных. Самое популярне ПО телеконференций Usenet использует DBM-базу данных для хранения инфор-мации о текущих и недавно просмотренных статьях. Главные файлы базы данных Sun NTS (урожденной YP) также хранятся в формате DBM.Per! обеспечивает доступ к такому же механизму DBM довольно умным способом: посредством процесса, похожего на открытие файла, с DBM-базой данных можно связать хеш. Зтот хеш (называемый DBM-массивом) исполь-зуется для доступа к DBM-базе данных и внесення в нее изменений.Создание нового злемента в зтом массиве влечет за собой немедленное изменение в базе данных. Удаление злемента приводит к удалению значення из DBM-базы данных и т.д.* Размер, количество и вид ключей и значений в DBM-базе данных ограничены. В зависимости от того, какой версией библиотеки DBM вы пользуетесь, зти же ограничения могут иметь место и для DBM-массива. Подробности см. на man-странице AnyDBM_File. В общем, если вы сумеете сделать так, чтобы и ключи, и значення упаковывались не больше чем в 1000 символов с произвольными двоичными значеннями, то все будет нормально.Открытие и закрытие DBM-хешей Чтобы связать DBM-базу данных с DBM-массивом, применяется функ-ция dbmopen, которая используется следующим образом:dbmopen( %ИМЯ МАССИВА, "имя_ОВМ-фа{та", $режим}Параметр %имя_массива зто имя Perl-хеша. (Если в данном хеше уже сть значення, они выбрасываются.) Хеш соединяется с DBM-базой данных, заданной параметром имя_овм-файла. Она обычно хранится на диске в виде пары файлов с именами имя_ОВМ-файла.сЁи и имя_ОВМ-файла.рад.Параметр $режим зто число, которое соответствует битам прав доступа к названным двум файлам, если файлы создаются заново. Обычно оно указывается в восьмеричном формате; часто используемое значение 0644 предоставляет право доступа только для чтения всем, кроме владельца, который имеет право на чтение и запись. Если зти файлы существуют, данный параметр не действует. Например:dbmopen(%FRED, "mydatabase", 0644); # открьггь %FRED на mydatabaseЗтот вызов связывает хеш %fred с файлами mydatabase. dir и ту database.pag, расположенными в текущем каталоге. Если зти файлы не существуют, они создаются с правами доступа 0644, которые модифицируются с учетом текущего значення, установленного командой umask.Функция dbmopen возвращает значение "истина", если базу данных можно открыть или создать; в противном случае возвращается "ложь" точно так же, как при вызове функции open. Если вы не хотите создавать файлы, используйте вместо параметра $режим значение undef. Например:dbmopen(%A,"/etc/xx",undef) || die "cannot open DBM /etc/xx"; * Если, как в данном случае, (^awibi/etc/xx.dirvi/etc/xx.pagoTKpbnb нельзя, то вызов dbmopen возвращает значение "ложь" без попытки создать зти файлы.DBM-массив остается открытым в течение выполнения всей программы. Когда программа завершается, разрывается и связь с DBM-базой данных. Зту связь можно разорвать и способом, близким к закрытию дескриптора файла с помощью функции dbmclose:dbmclose(%A); Как и функция close, dbmclose возвращает значение "ложь", если что-нибудь происходит не так, как надо.Использование DBM-хеша После открытия базы данных обращения к DBM-хешу преобразуются в обращения к базе данных. Изменение значення в хеше или ввод в него нового значення вызывает немедленную запись соответствующих злементов в файлы на диске. Например, после открытия массива %fred из предыдущего примера мы можем обращаться к злементам базы данных, вводить в нее новые злементы и удалять существующие:$FRED{"fred"} = "bedrock"; # создать (или обновить) злемент delete $FRED("barney"}; # удалить злемент базн данных foreach $key (keys %FRED) ( # пройти по всем значенням print "$key has value of $FRED{$key)\n";} Последний цикл должен просмотреть весь файл на диске дважды: один раз для выборки ключей, а второй для поиска значений, соответствующих зтим ключам. Если вы просматриваете DBM-хеш, то более зффективным способом с точки зрения зксплуатации диска является использование опе-рации each, которая делает всего один проход:while (($key, $value) = each(%FRED) ) ( print "$key has value of $value\n"; } Если вы обращаетесь к системним DBM-базам данных, например к базам данных, созданным системами sendmail и NIS, вы должны иметь в виду, что в плохо написанных С-программах в конце строк иногда стоит символ NUL (\0). Программам библиотеки DBM зтот NUL не нужен (они обрабатывают двоичные данные с помощью счетчика байтов, а не строки с символом NUL на конце), позтому он хранится как часть данных. В таком случае вы должны добавлять символ NUL в конец своих ключей и отбрасывать NUL, стоящий в конце возвращаемых значений, иначе данные не будут иметь смысла.Например, чтобы найти имя merlyn в базе данных псевдонимов, можно сделать так:dbmopen(%ALI, "/etc/aliases", undef) I I die "no aliases?"; $value = $ALI {"merlyn\0" 1; # обратите внимание на добавленный NULchop ($value) ; # удалить добавленный NULprint "Randal's mail is headed for: $value\n"; # показать результатВ вашей версии UNIX база данных псевдонимов может храниться не в каталоге /etc, а в каталоге /usr/lib. Чтобы вияснить, где именно она хранится, придется провести маленькое расследование. Новые версии sendmail зтим NUL-дефектом не страдают. ''Базы данных произвольного доступа с записями фиксированной длины Еще одна форма хранения данных файл на диске, предназначенный для записей фиксированной длины. В зтой схеме данные состоят из ряда записей одинаковой длины. Нумерация зтих записей либо не имеет значення, либо определяется по какой-нибудь схеме индексации.Например, у нас может быть ряд записей со следующими данными: 40 символов имя, один символ инициал, 40 символов фамилия и двухбайтовое целое возраст. Таким образом, длина каждой записи состав-ляет 83 байта. Если бы мы читали все зти данные в базе данных, то делали бы зто порциями по 83 байта до тех пор, пока не добрались до конца. Если бы мы хотели перейти к пятой записи, то мы пропустили бы четыре раза по 83 байта (332 байта) и прочитали бы непосредственно пятую запись.Perl поддерживает программы, которые используют файл с подобными записями. Помимо того, что вы уже зна те, понадобятся еще несколько операций:1. Открытие файла на диске для чтения и записи.2. Переход в зтом файле на произвольную позицию.3. Выборка данных фиксированной длины, а не до следующего символа новой строки.4. Запись данных блоками фиксированной длины.В функции open перед спецификацией, задающей способ открытия файла (для чтения или записи), необходимо записать знак плюс, указав таким образом, что данный файл в действительности открывается и для чтения, и для записи. Например:open (А, "+<Ь"); # открьеть файл b для чтения-записи (ошибка, если файл отсутствует)open(C, "+>d"); # создать файл d с доступом для чтения-записиopen (Е, "+ f"); # открить или создать файл f с доступом для чтения-записиОтметим, что все, что мы сделали зто добавили знак плюс к специфи-кации, задающей направление ввода-вывода данных в файл.Открыв файл, мн должны перейти на определенную позицию в нем. Зто делается с помощью функции seek, которая принимает те же три параметра, что и библиотечная програм ма./yeeA^.?/ Первый параметр зто дескриптор файла, а второй параметр задает смещение, которое интерпретируется в совокупности с третьим параметром. Как правило, в качестве третього параметра ставится нуль, чтобы второй параметр задавал абсолютную позицию для следующего чтения из файла или записи в файл. Например, чтобы перейти к пятой записи в дескрипторе файла names (как описано выше), можно сделать так:seek(NAMES,4*83,0) ; После перемещения указателя в файле на нужную позицию следующая операция ввода или вывода будет начинаться с зтой позиции. Для вывода используйте функцию print, но не забудьте, что записываемые данные должны иметь строго определенную длину. Чтобы сформировать запись правильной длины, можно воспользоваться функцией pack::print NAMES pack("A40 A A40 s", $first, $middle, $last, $age); В данном случае pack задает 40 символов для $ first, один символ для $middle, еще 40 символов для $last и коротке целое (два байта) для $аде. Определенная таким образом запись будет иметь в длину 83 байта и начинаться с текущей позиции в файле.Наконец, нам нужно узнать, как выбрать конкретную запись. Конструк-ция <names> возвращает все данные, начиная с текущей позиции Ё до следующего символа новой строки, однако в нашем случае предполагасгея, что данные занимают 83 байта й, вероятно, символ новой строки непосред-ственно в записи отсутствует. Позтому вместо нее мы используем функцию read, которая по внешнему виду и принципу работы очень похожа на свою UNIX-коллегу:$count = read(NAMES, $buf, 83); Первый параметр функции read дескриптор файла. Второй параметр зто скалярная переменная, в которую будут записаны прочитанные данные. Третий параметр задает количество байтов, которые нужно прочитать. Возвращает функция read количество фактически прочитанных байтов; как правило, оно равно затребованному количеству байтов, если только дескриптор файла открыт и если вы не находитесь слишком близко к концу файла.Получив зти 83-символьные данные, разбейте их на компоненты с помощью функции unpack:($first, $middle, $last, $age) = unpack("A40 A A40 s", $buf); Как видно, строки, определяющие формат, в функциях pack и unpack одинаковы. В большинстве программ зту строку заносят в переменную, указы-ваемую в начале программы, и даже вычисляют с помощью функции pack длину записей, а не используют везде константу 83:$names = "А40 А А40 s";$names_length = length(pack($names)); # вероятно, 83Базы данных с записями переменной длины (текстовые) Многие системные базы данных ОС UNIX (й довольно большое число пользовательских баз данных) представляют собой набори понятных чело-веку текстовых строк, каждая из которых образует одну запись. Например, каждая строка файла паролей соответствует одному пользователю системы, а строка файла хостов одному хост-имени.Корректируются зти базы данных в основном с помощью простих текстовых редакторов. Процедура обновлення базы данных состоит из чтения ее в какую-то временную область (память или другой дисковий файл), внесення необходимых изменений и либо записи результата обратно в исходный файл, либо создания нового файла с тем же именем, с одновре-менным удалением или переименованием старой версии. Зтот процесе можно рассматривать как разновидность копирования: данные копируются из исходной базы данных в новую ее версию с внесением изменений в процессе копирования. Perl поддерживает редактирование такого типа в строчно-ориентирован-ных базах данных методом редактирования на месте. Редактирование на месте зто модификация способа, посредством которого операция "ромб" (<>) считывает данные из списка файлов, указанного в командной строке. Чаще всего зтот режим редактирования включается путем установки аргу-мента командной строки -Ё, но его можно запустить и прямо из программы, как показано в приведенных ниже примерах.Чтобы запустить режим редактирования на месте, присвойте значение скалярной переменной $ л Ё. Оно играет важную роль и будет сейчас рассмот-рено.Когда используется конструкция о и переменная $ЛI имеет значение, отличное от undef, к списку неявних действий, которые выполняет операция "ромб", добавляются шаги, отмеченные в приведенном ниже коде комментарием ## inplace ##:$ARGV = shift 6ARGV; open(ARGV,"<$ARGV") ; rename($ARGV,"$ARGV$AI"); ## INPLACE ## unlink($ARGV); ## INPLACE ## open(ARGVOUT,">$ARGV"); ## INPLACE ## select(ARGVOUT) ,- ## INPLACE ## В результате в операции "ромб" при чтении используется старый файл, а запись в дескриптор файла по умолчанию осуществляется в новую копию зтого файла. Старый файл остается в резервной копии, суффикс имени файла которой равен значеним переменной $AI. (При зтом биты прав доступа копируются из старого файла в новый.) Зти шаги повторяются каждый раз, когда новый файл берется из массива @argv.Типичные значення переменной $ЛI .bak или ~, т.е. резервные файлы создаются почти так же, как зто делается в текстовом редакторе. Странное и полезное значение $ЛI пустая строка (""), благодаря которой старый файл после редактирования аккуратно удаляется. К сожалению, если система при выполнении вашей программы откажет, то вы потеряете все свои старые данные, позтому значение "" рекомендуется использовать только храбрецам, дуракам и излишне доверчивым.Вот как можно путем редактирования файла паролей заменить регистра-ционный shell всех пользователей на /bin/sh'.8ARGV = ("/etc/passwd"); # снабдить информацией операцию "ромб"$"1 == ".bak"; # для надежности записать /etc/passwd.bakwhile (о) { # основной цикл, по разу для каждой строки файла# /etc/passwd s#: ( л: ] *$#:/bin/sh#; # заменить shell на /bin/sh print; # послать выходную информацию в ARGVOUT: новий# /etc/passwd Как видите, зта программа довольно проста. Однако ее можно заменить всего лишь одной командой с несколькими аргументами командной строки, например: perl -р -Ё.bak -е 's#: [л:]*$#:/bin/sh#' /etc/passwdКлюч -р охватывает вашу программу циклом while, который включает оператор print. Ключ -Ё устанавливает значение переменной $^1. Ключ -е определяет следующий аргумент как фрагмент Perl-кода для тела цикла, а последний аргумент задает начальне значение массива @argv.Более подробно аргументы командной строки рассматриваются в книге Programming Perl и на man-странице perlrun.Упражнения 1. Создайте программу, которая открывает базу данных псевдонимов send-mail и выводит на зкран все ее злементы.2. Создайте две программы: одну для чтения данных с помощью опе-рации о, разбивки их на слова и обновлення DBM-файла с запомина-нием числа зкземпляров каждого слова, а вторую для открытия зтого DBM-файла и отображения результатов, рассортированных по количест-ву зкземпляров каждого слова в убывающем порядке. Вьшолните первую программу по отношению к нескольким файлам и посмотрите, осущест-вляет ли вторая программа правильную сортировку. |