До революции UNIX всевозможные источники и приемники данных не имели ничего общего. Чтобы две программы пообщались друг с другом, приходилось идти на невероятные ухищрения и отправлять в мусор целые горы перфокарт. При виде этой компьютерной Вавилонской б
В наши дни этот жестокий и нестандартный стиль программирования в основном ушел в прошлое. Современные операционные системы всячески стараются создать иллюзию, будто устройства ввода/вывода, сетевые подключения, управляющие данные процессов, другие програ
Поскольку чтение и запись данных осуществляется через простые байтовые потоки, любая программа может общаться с любой другой программой. Трудно переоценить всю элегантность и мощь такого подхода. Пользователи перестают зависеть от сборников магических зак
Интерпретация файлов как неструктурированных байтовых потоков однозначно определяет круг возможных операций. Вы можете читать и записывать последовательные блоки данных фиксированного размера в любом месте файла, увеличивая его размер при достижении конца
Что нельзя сделать с неструктурированным файлом? Поскольку вставка и удаление байтов возможны лишь в конце файла, вы не сможете вставить или удалить записи, а также изменить их длину. Исключение составляет последняя запись, которая удаляется простым усече
Самый распространенный тип файлов - текстовые файлы, а самый распространенный тип операций с ними - построчное чтение и запись. Для чтения строк используется оператор о (или его внутренняя реализация, readline), а для записи - функция print. Эти способы т
При достижении конца файла оператор о возвращает undef или ошибку, поэтому его следует использовать в цикле следующего вида:
while (defined ($line = )) {
chomp $line;
$size = length $line;
print "$size\n"; # Вывести длину строки
}
Поскольку эта операция встречается довольно часто, в Perl для нее предусмотрена сокращенная запись, при которой строки читаются в $_ вместо $line. Пере менная $_ используется по умолчанию и в других строковых операциях и вообще куда удобнее, чем может пок
while () {
chomp;
print length, "\n"; # Вывести длину строки
}
В скалярном контексте оператор о читает следующую строку. В списковом контексте он читает оставшиеся строки:
@lines = ; При чтении очередной записи через файловый манипулятор о увеличивает значение специальной переменной $. (текущий номер входной записи). Переменная сбрасывается лишь при явном вызове close и сохраняет значение при повторном открытии уже открытого манип
Заслуживает внимания и другая специальная переменная - $/, разделитель входных записей. По умолчанию ей присваивается "\п", маркер конца строки. Ей можно присвоить любое желаемое значение - например, "\0" для чтения записей, разделяемых нуль-байтами. Для
undef $/;
$whole file = ; # Режим поглощения
Запуск Perl с флагом -0 позволяет задать $/ из командной строки:
% perl -040 -е '$word = о; print "First word is $word\n";' Цифры после -О определяют восьмеричное значение отдельного символа, который будет присвоен $/. Если задать недопустимое значение (например, -0777), Perl присваивает $/ неопределенное значение undef. Если задать -00, $/ присваивается "". Ограничение в
% perl -ne 'BEGIN < $/="%%\n" } chomp; print if /unix/i' fortune.dat Запись строк и других данных выполняется функцией print. Она записывает своп аргументы в порядке указания и по умолчанию не добавляет к ним разделители строк или записей:
print HANDLE "One", "two", "three"; # "Onetwothree" print "Baa baa black sheep.\n";
# Передается выходному манипулятору
# по умолчанию
Между манипулятором и выводимыми данными не должно быть запятых Если поставить запятую, Perl выдает сообщение об ошибке "No comma allowed after filehandle". По умолчанию для вывода используется манипулятор STDOUT. Для выбора другого манипулятора применяет
Во всех системах строки разделяются виртуальным разделителем "\п", который называется переводом строки (newline). He существует такого понятия, как символ перевода строки. Это всего лишь иллюзия, которая по общему сговору поддерживается операционной систе
Записи фиксированной длины читаются функцией read. Функция получает три аргумента: файловый манипулятор, скалярную переменную и количество читаемых байт. Возвращается количество прочитанных байт, а в случае ошибки - undef. Для записи используется функция
$rv = read(HANDLE, $buffer, 4096)
or die "Couldn't read from HANDLE : $!\n";
it $rv - количество прочитанных байт, # $buffer содержит прочитанные данные
Функция truncate изменяет длину файла, который задается с помощью манипулятора или по имени; Функция возвращает true, если усечение прошло успешно, и false в противном случае:
truncate(HANDLE, $length)
or die "Couldn't truncate: $!\n"; .;
truncate("/tmp/$$.pid", $length)
or die "Couldn't truncate: $!\n"; Для каждого файлового манипулятора отслеживается текущая позиция в файле. Операции чтения/записи выполняются именно в этой позиции, если при открытии не был указан флаг 0_APPEND (см. рецепт 7.1). Чтобы узнать текущую позицию файлового манипулятора, во
$pos = tell(DATAFILE);
print "I'm $pos bytes from the start of DATAFILE.\n";
Функция seek получает три аргумента: файловый манипулятор, новое смещение (в байтах) и число, определяющее интерпретацию смещения. Если оно равно О, смещение отсчитывается от начала файла (в соответствии со значениями, возвращаемыми tell); I - от текущей
seek(LOGFILE, 0, 2) or die "Couldn't seek to the end: $!\n";
seek(DATAFILE, $pos, 0) or die "Couldn't seek to $pos: $!\n";
seek(OUT, -20, 1) or die "Couldn't seek back 20 bytes: $!\n"; Все сказанное выше относится к буферизованному вводу/выводу. Другими словами, операции о, print, read, seek и tell используют буферы для повышения скорости. В Perl также предусмотрены небуферизованные операции ввода/ вывода: sysopen, sysread, syswrite
Функции sysread и syswrite отличаются от своих аналогов, о и print. Они получают одинаковые аргументы - файловый манипулятор; скалярную переменную, с которой выполняется чтение или запись; и количество читаемых или записываемых байт. Кроме того, они могут
$written = syswrite(DATAFILE, $mystring, length($mystring));
die "syswrite failed: $!\n" unless $wntten == length($mystring);
$read = sysread(INFILE, $block, 256, 5);
warn "only read $read bytes, not 256" if 256 != $read; Функция syswrite посылает содержимое $mystring в DATAFILE. При вызове sysread из INFILE читаются 256 символов, сохраняемых с шестого символа ь $block, при этом первые пять символов остаются без изменений. И sysread и syswrite возвращают фактическое ко
Функция sysseek является небуферизованной заменой для seek и tell. Она получает те же аргументы, что и seek, но возвращает новую позицию при успешном вызове или undef в случае ошибки. Текущая позиция внутри файла определяется следующим образом:
$pos = sysseek(HANDLE, 0, 1); # Не изменять позицию
die "Couldn't sysseek: $!\n" unless defined $pos; Мы описали базовые операции с файлами, которые находятся в вашем распоряжении. Искусство программирования как раз и заключается в применении простейших операций для решения сложных проблем - например, определения количества строк в файле, перестановки
Имеется файл с длинными строками, которые делятся на две и более строки. Символ \ означает, что данная строка продолжается на следующей. Вы хотите объединить разделенные строки. Подобное разделение длинных строк на короткие встречается в make-файлах, сцен
Решение
Последовательно объединяйте прочитанные строки, пока не встретится строка без символа продолжения:
while (defined($line = ) ) { chomp $line;
if ($line =~ s/\\$//) { $line .= ;
redo unless eof(FH);
} # Обработать полную запись в $line
}
Комментарий
Рассмотрим пример входного файла:
DISTFILES = $(DIST_COMMON) $(SOURCES) $(HEADERS) \ .
$(TEXINFOS) $(INFOS) $(MANS) $(DATA) DEP_DISTFILES = $(DIST_COMMON) $(SOURCES) $(HEADERS) \
$(TEXINFOS) $(INFO_DEPS) $(MANS) $(DATA) \
$(EXTRA_DIST)
Вы хотите обработать текст, игнорируя внутренние разрывы строк. В приведенном примере первая запись занимает две строки, вторая - три строки и т. д.
Алгоритм работает следующим образом. Цикл while читает строки, .которые могут быть, а могут и не быть полными записями, - они могут заканчиваться символом \ (и переводом строки). Оператор подстановки s/// пытается удалить \ в конце строки. Если подстановк
У файлов такого формата имеется одна распространенная проблема - невидимые пробелы между \ и концом строки. Менее строгий вариант подстановки выглядит так:
if ($line =- s/\\\s*$//) {
# Как и прежде
}
К сожалению, даже если ваша программа проищет мелкие погрешности, существуют и другие, которые этого не делают. Будьте снисходительны к входным данным и строги - к выходным.
> Смотри также ---------------------------'o---
Описание функции chomp в perlfunc(1); описание ключевого слова redo в разделе "Loop Control" perlsyn(1).
Во многих системах существует программа we, подсчитывающая строки в файле:
$count = 'we -I < $file';
die "we failed: $?" if $?;
chomp($count); Кроме того, можно открыть файл и последовательно читать строки до конца, увеличивая значение счетчика:
open(FILE, "< $file") or die "can't open'$file: $!";
$count++ while ;
# $count содержит число прочитанных строк
Самое быстрое решение предполагает, что строки действительно завершаются "\n":
$count += tr/\n/\n/ while sysread(FILE, $_, 2 ** 16);
Комментарий
Хотя размер файла в байтах можно определить с помощью -s $file, обычно полученная цифра никак не связана с количеством строк. Оператор -s рассматривается в главе 9 "Каталоги".
Если вы не хотите или не можете перепоручить черную работу другой программе, имитируйте работу we - самостоятельно откройте и прочитайте файл:
open(FILE, "< $file") or die "can't open $file: $!";
$count++ while ;
# $count содержит число прочитанных строк
Другой вариант выглядит так:
open(FILE, "< $file") or die "can't open $file: $!";
for ($count=0; ; $count++) { } Если вы не читаете из других файлов, можно обойтись без переменной $count. Специальная переменная $. содержит количество прочитанных строк с момента последнего явного вызова close для файлового манипулятора:
while ;
$count = $.; В этом варианте все записи файла последовательно читаются без использования временных переменных.
Чтобы подсчитать абзацы, присвойте перед чтением глобальному разделителю входных записей $/ пустую строку (""), и тогда оператор о будет считывать не строки, а целые абзацы:
$/=''; # Включить режим чтения абзацев
open(FILE, $file) or die "can't open $file: $!";
1 while ;
$para_count = $.;
> Смотри также -------------------------------
Описание специальной переменной $/ в perlvaf(1) введение главы 9; страница руководства wc(1).
Требуется выполнить некоторую операцию с каждым словом файла, по аналогии с функцией to reach.
Решение
Разделите каждую строку но пропускам с помощью функции split:
while (<>) {
for $chunk (split) {
# Сделать что-то с $chunk
}
}
Или воспользуйтесь оператором т//д для последовательного извлечения фрагментов строки:
while (<>) {
while ( /(\w[\w'-]*)/g ) {
# Сделать что-то с $1
}
}
Комментарий
Сначала необходимо решить, что же подразумевается под "словом". Иногда это любые последовательности символов, кроме пропусков; иногда - идентификаторы программ, а иногда - слова английского языка. От определения зависит и используемое регулярное выражение
Два варианта решения, приведенные выше, работают по-разному. В первом варианте шаблон определяет, что не является словом. Во втором варианте все наоборот - шаблон решает, что им является.
На основе этой методики нетрудно подсчитать относительные частоты всех слов в файле. Количество экземпляров каждого слова сохраняется в хэше:
# Подсчет экземпляров слов в файле %seen =();
while (<>) {
while ( /(\w['\w-]*)/g ) { $seen{lc $1}++;
}
}
# Отсортировать выходной хэш по убыванию значений foreach
$word ( sort { $seen{$b} <=> $seen{$a}-} keys %seen)
{ pnntf "%5d %s\n", $seen{$word}, Sword;
}
Чтобы программа подсчитывала количество строк вместо слов, уберите второй цикл while и замените его на $seen{lc $_}++:
# Подсчет экземпляров строк в файле %seen =();
while (<>) {
$seen{lc $_}++;
} foreach $line ( sort { $seen{$b} <=> $seen{$a} } keys %seen ) {
printf "%5d %s", $seen{$line}, $line;
} Порой слова могут выглядеть довольно странно - например, "M.I.Т", "Micro-$oft", "o'clock", "49ers", "street-wise", "and/or", "&", "c/o", "St.", "TschuB" или
"Nino". Помните об этом при выборе шаблона. В двух последних примерах вам придется включить в программу директиву use locale и использовать метасимвол \w в текущем локальном контексте.
> Смотри также -------------------------------
Описание функции split в perlfunc(1) рецепты 6.3; 6.23.
Требуется обработать каждую строку или абзац файла в обратном направлении.
Решение
Прочитайте все строки в массив и организуйте обработку элементов массива от конца к началу:
@lines = ;
while ($line = pop @lines) { # Сдел чтo итп-то с $line
}
Или занесите строки в массив в обратном порядке:
@lines = reverse ;
foreach $line (@lines) { # Сделать что-то с $line
}
Комментарий
Ограничения, связанные с доступом к файлам (см. введение), не позволяют последовательно читать строки с конца файла. Приходится читать строки в память и обрабатывать их в обратном порядке. Конечно, расходы памяти при этом будут по крайней мере не меньше р
В первом варианте массив строк перебирается в обратном порядке. Такая обработка является деструктивной, поскольку при каждой итерации из массива выталкивается последний элемент. Впрочем, то же самое можно сделать и недеструктивно:
for ($i = $slines; $i != -1; $i--) {
$line = $lines[$i];
} Во втором варианте генерируется массив строк, изначально расположенных в обратном порядке. Его тоже можно обработать недеструктивно. Мы получаем массив с обратным порядком строк, поскольку присваивание @lines обеспечива-
ет вызов reverse в списковом контексте, что, в свою очередь, обеспечивает списковый контекст для оператора . В списковом контексте о возвращает список всех строк 4)айла.
Показанные решения легко распространяются на чтение абзацев, достаточно изменить значение $/:
# Внешний блок обеспечивает существование временной локальной копии $/
{
local $/ = '';
@Daraaraphs = reverse ;
}
foreach $paragraph @paragraphs) { # Сделать что-то
}
Смотри также -------------------------------
Описание функции reverse в perlfunc(1); описание специальной переменной $/ в perlvar(1); рецепты 4.10; 1.6.
Требуется читать данные из непрерывно растущего файла, однако при достижении конца файла (текущего) следующие попытки чтения завершаются неудачей.
Решение
Читайте данные, пока не будет достигнут конец файла. Сделайте паузу, сбросьте флаг EOF и прочитайте новую порцию данных. Повторяйте, пока процесс не прервется. Флаг EOF сбрасывается либо функцией seek:
for (::) {
while () { .... }
sleep $SOMETIME;
seek(FH, 0, 1);
} либо методом clearer r модуля IO::Handle:
use 10::Seekable:
for (;;) {
while () { .... }
sleep $SOMETIME;
FH->clearerr();
}
Комментарий
При достижении конца файла во время чтения устанавливается внутренний флаг, который препятствует дальнейшему чтению. Для сброса этого флага проще всего воспользоваться методом clearerr, если он поддерживается (присутствует в модулях IO::Handle и FileHandl
$naptime = 1;
use 10::Handle;
open (LOGFILE, "/tmp/logfile") or die "can't open /tmp/logfile: $!";
for (;;) {
while () { print } # Или другая операция
sleep $naptime;
LOGFILE->clearerr(); # Сбросить флаг ошибки ввода/вывода
} Если простейший вариант в вашей системе не работает, воспользуйтесь функцией seek. Приведенный выше фрагмент с seek пытается переместиться на 0 байт от текущей позиции, что почти всегда завершается успехом. Текущая позиция при этом не изменяется, но з
Если и этот вариант не работает (например, из-за того, что он полагается ни так называемую "стандартную" реализацию ввода/вывода библиотек С), попробуйте следующий фрагмент - он явно запоминает старую позицию в файле и напрямую возвращается к ней:
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. Таким образом, первая строка сохраняется
Начнем с конкретных примеров, а затем перейдем к абстрактным.
Разумеется, для файла из одной строки (N=1) все предельно честно: первая строка сохраняется всегда, поскольку 1/1 = 100 %. Для файла из двух строк N = 2. Первая строка сохраняется всегда; когда вы достигаете второй строки, она с вероятностью 50 % заменяет
В общем случае для файла из 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 $_;
}
Комментарий
Самое простое решение - прочитать все строки файла и переставить их в памяти. Смещения строк в файле неизвестны, поэтому нельзя перетасовать список с номерами строк и затем извлечь строки в порядке их появления в файле. Впрочем, даже при известных смещени
> Смотри также -------------------------------
Рецепты 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];
Комментарий
Каждый вариант имеет свои особенности и может пригодиться в конкретной ситуации. Линейное чтение легко программируется и идеально подходит для коротких файлов. Индексный метод обеспечивает ускоренную выборку, по требует предварительного построения индекса
Необходимо знать, с какого числа начинается нумерация строк - с 0 или 1. Переменной $. присваивается 1 после чтения первой строки, поэтому при линейном чтении нумерацию желательно начинать с 1. В индексном механизме широко применяются смещения, и нумераци
Ниже показаны три реализации одной и той же программы, 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.