Next Previous Contents

9. БД на плоских файлах

9.1 Я хочу какую-нибудь простейшую БД и прямо сейчас!

Вы можете использовать простой текстовый файл с разделителями. Например, если мы пишем нечто типа телефонной книги, то вполне вероятно предположить, что ни в чьем имени, ни в номере телефона не встретится последовательность ::, так что именно ее и можно использовать в качестве разделителей.

Файл с данными может выглядеть так:

phones.data
Иванов И.И.::888-0000::Какая-то улица, 17, кв 40
Сидоров П.И.::888-8429::Другая улица, 5, кв 21
...... и тд.

тогда программа, которая читает данные, может быть примерно такого вида:

dump_phones.pl
#!/usr/bin/perl
$filename = 'phones.data';
# открываем файл
open DATA, $filename or die "Невозможно открыть $filename: $!";

# читаем построчно из файла
while (<DATA>) {
        chomp; # удаление символа конца строки
        # теперь в $_ есть строка и мы ее разделяем на переменные
        ($name, $phone, $address) = split(/::/);
        # и выведем на печать
        print "Имя: $name, телефон: $phone, адрес: $address\n";
}
close DATA;

Больше проблем возникает в случае, если надо удалить или отредактировать запись, но и их можно довольно просто и элегантно решить, если использовать механизм редактирования на месте (inplace edit) -- при использовании операции "ромб"(<>), можно читать из одного файла, а писать в другой:

change_phones.pl
#!/usr/bin/perl 
$^I = '~'; # запускаем inplace edit
while (<>) { # Обратите внимание, что мы не открывали файл: при такой
        #конструкции имя файла берется из командной строки
        chomp;
        ($name, $phone, $address) = split(/::/);
        if (.... некоторое условие, при котором мы оставляем наши данные ... )
        {
                print "$name::$phone::$address\n"; # теперь данные есть в новом файле
        }
}

если запустить это программу как

change_phones.pl phones.data,
то в текущем каталоге будут два файла: phones.data, с записями, которые удовлетворили нашим условиям и phones.data~ -- предыдущая копия.

Также, во многих случаях, всю программу такого типа можно записать как one-liner:

perl -i~ -n -e 'print if(... условие)'

Файлы CSV

Схема хранения данных, использованная в предыдущем примере весьма удобна, но страдает одним недостатком -- в значениях не может использоватся разделитель, или его приходится специальным образом обрабатывать при чтении и записи таких файлов.

Text::CSV именно это и делает, позволяя оперировать файлами в формате CSV (comma-separated values).

Например, записывать:

#!/usr/bin/perl -w
use strict;
use Text::CSV_XS;

my ($name, $phone, $address) = ('ТОО "Рога и Копыта"', '888-00-11', 'Какая-то улица, 17');

my $csv = Text::CSV_XS->new({
        # надо для русских букв
        'binary'=>1
        });
$csv->combine($name, $phone, $address) or die "Combine failed";

print $csv->string();
# Выведет "ТОО ""Рога и Копыта""",888-0000,"Какая-то улица, 17"

и читать

#!/usr/bin/perl -w
use strict;
use Text::CSV_XS;

my $csv = Text::CSV_XS->new({
        'binary'=>1
        });

open F, "<phones.csv" or die "phones.csv: $!";

my $line_num = 1;
# Читаем по одной строке из файла
while (my $line = <F>) {
        # Пробуем разобрать на поля.  parse() возвращает 1, если все прошло ok
        # и 0, если строка ему не понравилась.
        if ($csv->parse($line)) {
                # fields возвращает значения
                my ($name, $phone, $address) = $csv->fields();
                printf "%-30s %-15s %-30s\n", $name, $phone, $address;
        } else {
                # error_input вернет неправильную строку.
                die "Неправильная строка #$line_num: ", $csv->error_input();
        }       
        $line_num++;
}
close F;

phones.csv
"ТОО ""Рога и Копыта""",888-0000,"Широкий бульвар, 4"
"Петров А.А.",887-00-00,"Длинный проспект, 124"

Двоичные файлы

Для чтения двоичных файлов в Perl можно использовать функции read и unpack. К примеру, если использовать двоичный файл для хранения телефонной книги такого формата:

40 символов -- фамилия, И.О.

10 символов -- номер телефона,

60 символов -- адрес,

то строка описания формата для unpack будет выглядеть так:

$format_str = 'A40 A10 A60'
, а сама программа, аналогичная первому примеру:
binary_phones.pl
#!/usr/bin/perl
$format_str = 'A40 A10 A60';
open DATA, 'binary.dat' or die "$!";
while (read(DATA, $buf, 40+10+60)) { # <DATA> не покатит: такая
# конструкция будет читать до символа перевода строки, а это не то, что нужно
        ($name, $phone, $address) = unpack($format_str, $buf);
        # Теперь в $name, $phone, $address есть данные и с ними можно делать
        # все, что захочется
}
close DATA;

Чтобы вывести в файл такую запись можно использовать конструкцию типа

print FILE pack($format_str, $name, $phone, $address);

9.2 Можно ли как-нибудь из Perl получить доступ к dbf файлам?

Да, можно. На http://www.fi.muni.cz/~adelton/ есть модуль XBase, который позволяет читать/писать dbf. При чтении он даже поддерживает индексы. Кроме того, в комплект поставки также входит модуль DBD::XBase, при помощи которого можно оперировать dbf на SQL (более подробно про DBI -- далее).

9.3 А к MS access .mdb?

К файлам MS Access нельзя обращаться из perl напрямую.

К MS Access можно обращаться по ODBC, при помощи DBD::ODBC.

9.4 Зачем и как нужно запирать (lock) файлы?

Представьте себе ситуацию когда одновременно работают несколько копий одной и той же программы (к примеру, cgi-скрипты, обслуживающие запросы), читающие/пишущие в один файл, тогда рано или поздно возникнет ситуация при которой один скрипт прочитал данные, произвел над ними некоторые действия и собрался записать их назад в файл, но в это же время другой скрипт тоже прочитал данные, тоже произвел над ними действия, но (!) он прочитал старые данные, которые он и запишет поверх данных, выданных другим скриптом. Таким образом, в файле останутся данные записанные одним из скриптов -- в лучшем случае, в худшем -- структура файла будет испорчена. Чтобы этого избежать в Unix и большинстве других ОС есть системный вызов flock(2) или аналогичный.

Как использовать flock

К примеру, скрипт который записывает имена вызывающих хостов в файл. (На деле такой список, конечно, можно получить из журнала регистрации web-сервера).

lock_exm.pl
#!/usr/bin/perl
use Fcntl; # Импорт констант
open (HOSTS, '>>hosts.log'); # Файл открыт для добавления записи
flock(HOSTS, LOCK_EX); 
# Теперь файл заблокирован: Если любой другой скрипт тоже вызовет flock на
# этом файле, его flock не вернет управление в программу, пока мы не
# разблокируем файл. Обратите внимание: flock -- декларативная функция, если
# один из скриптов ее не использует при записи, то вся ваша блокировка не
# работает.
print HOSTS $ENV{REMOTE_HOST}, "\n"; # записали строку
close HOSTS; # Файл при закрытии разблокируется автоматически -- unlock вызывать не надо.

# Вывести сообщение для пользователей
print "Content-Type: text/plain\n\n";
print "Название вашего хоста записано\n";

Более подробный рассказ о flock и пример доступны на http://w3.stonehenge.com/merlyn/WebTechniques/col04.html

9.5 Чего делать на системах где flock() нет?

Судя по perlfaq5(1), можно использовать модуль File::Lock с CPAN.


Next Previous Contents