Смотри также: Описание функции reverse в perlfunc(1); описание специальной переменной $/ в perlvar(1); рецепты 4.10; 1.6.
будут прочитаны новые данные.
Если и этот вариант не работает (например, из-за того, что он полагается ни так называемую "стандартную" реализацию ввода/вывода библиотек С), попробуйте следующий фрагмент - он явно запоминает старую позицию в файле и напрямую возвращается к ней:
for (;;) {
for ($curpos = tell(LOGFILE); ; $curpos = tell(LOGFILE)) {
# Обработать $_
}
sleep $naptime;
seek(LOGFILE, $curpos, 0);
# Вернуться к прежней позиции
}
Некоторые Файловые системы позволяют удалить файл во время чтения из него. Вероятно, в таких случаях нет смысла продолжать работу с файлом. Чтобы программа в подобных ситуациях завершалась, вызовите stat для манипулятора и убедитесь в том, что количество ссылок па него (третье поле возвращаемого списка) не стало равным нулю:
exit if (stat(LOGFILE))[3] == О
Модуль File::stat позволяет записать то же самое в более попятном виде:
use File::stat;
exit if stat(*LOGFILE)->nlink == 0;
Смотри также: Описание функции seek в perlfunc(1) документация по стандартным модулям POSIX и IO::Seekable; страницы руководства tail(1) и stclio(3).
Проблема
Требуется прочитать из файла случайную строку.
Решение
Воспользуйтесь функцией rand и переменной $, (текущим номером строки):
srand:
rand($.) < 1 && ($line = $_) while 0;
# $line - случайно выбранная строка
Комментарий
Перед вами - изящный и красивый пример неочевидного решения. Мы читаем все строки файла, но не сохраняем их в памяти. Это особенно важно для больших файлов. Вероятность выбора каждой строки равна 1/N (где N - количество прочитанных строк).
Следующий фрагмент заменяет хорошо известную программу fortune:
$/ = "%%\n";
$data = '/usr/share/games/fortunes';
srand;
rand($.) < 1 && ($adage = $_) while о;
print $adage;
Если вам известны смещения строк (например, при наличии индекса) и их общее количество, можно выбрать случайную строку и перейти непосредственно к
ее смещению в файле. Впрочем, индекс доступен далеко не всегда.
Приведем более формальное пояснение работы данного алгоритма. Функция rand ($. ) выбирает случайное число от 0 до текущего номера строки. Строка с номером N сохраняется в возвращаемой переменной с вероятностью 1/N. Таким образом, первая строка сохраняется с вероятностью 100 %, вторая - с вероятностью 50 %, третья - 33 % и т. д. Вопрос лишь в том, насколько это честно для любого положительного целого N.
Начнем с конкретных примеров, а затем перейдем к абстрактным.
Разумеется, для файла из одной строки (N=1) все предельно честно: первая строка сохраняется всегда, поскольку 1/1 = 100 %. Для файла из двух строк N = 2. Первая строка сохраняется всегда; когда вы достигаете второй строки, она с вероятностью 50 % заменяет первую. Следовательно, обе строки выбираются с одинаковой вероятностью, и для N = 2 алгоритм тоже работает корректно. Для файла из трех строк N = 3. Третья строка сохраняется с вероятностью 1/3 (33 %). Вероятность выбора одной из двух первых строк равна 2/3 (66 %). Но как показано выше, две строки имеют одинаковую вероятность выбора (50 %). Пятьдесят процентов от 2/3 равны 1/3. Таким образом, каждая из трех строк файла выбирается с вероятностью 1/3.
В общем случае для файла из N+1 строк последняя строка выбирается с вероятностью 1/(N+1), а одна из предыдущих строк - N/(N+1). Деление N/(N+1) на
N дает вероятность 1/(N+1) для каждой из N первых строк и те же 1/(N+1) для строки с номером N+1. Следовательно, алгоритм корректно работает для любого положительного целого N.
Нам удалось случайным образом выбрать из файла строку со скоростью, пропорциональной количеству строк в файле. При этом максимальный объем используемой памяти даже в худшем случае равен размеру самой длинной строки.
Смотри также: Описание специальной переменной $, в perlvar{1); рецепты 2.7-2.8.
Проблема
Требуется скопировать файл и случайным образом переставить строки копии.
Решение
Прочитайте все строки в массив, перетасуйте элементы массива (см. рецепт 4.17) и запишите полученную перестановку:
# Используется функция shuffle из главы 4
while (INPUT) {
push(@lines, $_);
}
@reordered = shuffle(@lines);
foreach (@reordered) {
print OUTPUT $_;
}
Комментарий
Самое простое решение - прочитать все строки файла и переставить их в памяти. Смещения строк в файле неизвестны, поэтому нельзя перетасовать список с номерами строк и затем извлечь строки в порядке их появления в файле. Впрочем, даже при известных смещениях такое решение, вероятно, будет работать медленнее, поскольку придется многократно перемещаться по файлу функцией seek вместо простого последовательного чтения.
Смотри также: Рецепты 2.7-2.8; 4.17.
Проблема
Требуется извлечь из файла строку с известным номером.
Решение
Простейший выход - читать строки до обнаружения нужной:
# Выборка строки с номером
$OESIRED_LINE_NUMBER $. = 0;
do { $LINE = } until $. == $desired_line_number || eof;
Если подобная операция должна выполняться многократно, а файл занимает не слишком много места в памяти, прочитайте его в массив:
@lines = ;
$LINE = $lines[$desired_line_number];
Если вы собираетесь многократно извлекать строки по номеру, а файл не помещается в памяти, постройте индекс смещений для отдельных строк и переходите к началу строки 4iy"KHiien seek:
# Применение : build_index(*МАНИПУЛЯТОР_ДАННЫХ, *МАНИПУЛЯТОР_ИНДЕКСА)
sub build_index {
my $data_file = shift;
my $index_file = shift;
my $offset = 0;
while (<$data_file>) {
print $index_file pack("N", $offset);
$offset = tell($data_file);
}
}
# Применение : line_with_index(*МАНИПУЛЯТОР_ДАННЫХ, *МАНИПУЛЯТОР_ИНДЕКСА,$НОМЕР_СТРОКИ)
# Возвращает строку или undef, если НОМЕР_СТРОКИ выходит за пределы файла sub
line_with_index {
my $data_file = shift
my $index_file = shift
my $line_number = shift
my $size; # Размер элемента индекса
my $i_offset; # Смещение элемента в индексе
my Sentry; # Элемент индекса
my $d_offset; # Смещение в файле данных
$size = length(pack("n", 0));
$i_offset = $size * ($line_number-1);
seek($index_file, $i_offset, 0) or return;
read($index_file, $entry, $size);
$d_offset = unpack("n", sentry);
seek($data_file, $d_offset, 0);
return scalar(<$data_file>);
}
# Применение:
open(FILE, "< $file") or die "Can't open $file for reading: $!\n";
open(INDEX, "+>$file.idx")
or die "Can't open Sfile.idx for read/write: $!\n";
build_index(*FILE, *INDEX);
$line = line_with_index(*file, "index, $seeking);
При наличии модуля DB_ File можно воспользоваться методом DB_RECNO, который связывает массив с файлом (по строке па элемент массива):
use DB_File;
use Fcnti;
$tie = tie($!\n";
# Извлечь строку
$line = $lines[$sought-1];
Комментарий
Каждый вариант имеет свои особенности и может пригодиться в конкретной ситуации. Линейное чтение легко программируется и идеально подходит для коротких файлов. Индексный метод обеспечивает ускоренную выборку, по требует предварительного построения индекса. Он применяется в случаях, когда индексируемый файл редко изменяется по сравнению с количеством просмотров. Механизму DB_File присущи некоторые начальные издержки, зато последующая выборка строк выполняется намного быстрее, чем при линейном чтении. Обычно он применяется для многократных обращений к большим файлам.
Необходимо знать, с какого числа начинается нумерация строк - с 0 или 1. Переменной $. присваивается 1 после чтения первой строки, поэтому при линейном чтении нумерацию желательно начинать с 1. В индексном механизме широко применяются смещения, и нумерацию лучше начать с 0. DB_File интерпретирует записи файла как элементы массива, индексируемого с 0, поэтому строки также следует нумеровать с 0.
Ниже показаны три реализации одной и той же программы, print_line. Программа получает два аргумента - имя файла и номер извлекаемой строки.
Версия print_line из примера 8.1 просто читает строки файла до тех пор, пока не найдет нужную.
Пример 8.1. printJine-vl
#!/usr/bin/perl -w
# print_line-v1 - линейное чтение
@ARGV == 2 or die "usage: print_line filename line_number\n";
($filename, $line_number) = @>ARGV;
open(INFILE, "< $filename") or die "Can't open $filename for reading: $!\n";
while () {
$line = $_;
last if $. == $line_number;
}
if ($. != $line_number) {
die "Didn't find line $line_number in $filename\n";
} print;
Версия из примера 8.2 сначала строит индекс. При большом количестве обращений индекс строится один раз, а затем используется во всех последующих чтениях.
Пример 8.2. print_line-v2
#!/usr/bin/perl -w
# print_line-v2 - построение индекса
# Функции build_index и line_with_index приведены выше.
@ARGV == 2 or
die "usage: print_line FILENAME LINE_NUMBER";
($filename, $line_number) = @argv;
open(ORIG, "< $filename")
vor die "Can't open $filename for reading: $!":
# Открыть индекс и при необходимости построить его
# Если две копии программы замечают, что индекс не существует,
# они могут одновременно попытаться построить его.
# Проблема легко решается с применением блокировки.
$indexname = "$filename.index";
sysopen(IDX, $indexname, 0_CREAT|0_RDWR)
or die "Can't open $indexname for read/write: $!";
build_index(*ORIG, *IDX) if -z $indexname;
$line = line_with_index(*ORIG, *IDX, $line_number);
die "Didn't find line $line_number in $filename" unless defined $line;
print $line;
Версия с модулем DB_File из примера 8.3 похожа на волшебство.
Пример 8.3. print_line-v3
#!/usr/bin/perl -w
# print_line-v3 - решение с применением DB_File use DB_File;
use Fcnti;
@ARGV == 2 or
die "usage: print_line FILENAME LINE_NUMBER\n";
($filename, $line_number) = @ARGV;
$tie = tie(@lines, "DB_File", $filename, O.RDWR, 0666, $DB_RECNO) or die "Cannot open
file $filename: $!\n";
unless ($line_number < $tie->length) {
die "Didn't find line $line_nu(nber in $filename\n"
}
print $lines[$line_number-1]; # Легко, правда?
Смотри также: Описание функции tie в perlfunc(1); описание специальной переменной $. в perlvar(1), документация по стандартному модулю DB_File.