ЭЛЕКТРОННАЯ БИБЛИОТЕКА КОАПП |
Сборники Художественной, Технической, Справочной, Английской, Нормативной, Исторической, и др. литературы. |
Содержимое файловВведениеДо революции UNIX всевозможные источники и приемники данных не имели ничего общего. Чтобы две программы пообщались друг с другом, приходилось идти на невероятные ухищрения и отправлять в мусор целые горы перфокарт. При виде этой компьютерной Вавилонской башни норой хотелось бросить программирование и подыскать себе менее болезненное хобби - например, податься в секту флаггелаитов. В наши дни этот жестокий и нестандартный стиль программирования в основном ушел в прошлое. Современные операционные системы всячески стараются создать иллюзию, будто устройства ввода/вывода, сетевые подключения, управляющие данные процессов, другие программы, системные консоли и даже терминалы пользователей представляют собой абстрактные потоки байтов, именуемые файлами. Теперь можно легко написать программу, которая нисколько не заботится о том, откуда взялись ее входные данные и куда отправятся результаты. Поскольку чтение и запись данных осуществляется через простые байтовые потоки, любая программа может общаться с любой другой программой. Трудно переоценить всю элегантность и мощь такого подхода. Пользователи перестают зависеть от сборников магических заклинаний JCL (или СОМ) и могут собирать собственные нестандартные инструменты, используя простейшее перенаправление ввода/вывода и конвейерную обработку. Интерпретация файлов как неструктурированных байтовых потоков однозначно определяет круг возможных операций. Вы можете читать и записывать последовательные блоки данных фиксированного размера в любом месте файла, увеличивая его размер при достижении конца. Чтение/запись блоков неременной длины (например, строк, абзацев и слов) реализуется в Perl на базе стандартной библиотеки ввода/вывода С. Что нельзя сделать с неструктурированным файлом? Поскольку вставка и удаление байтов возможны лишь в конце файла, вы не сможете вставить или удалить записи, а также изменить их длину. Исключение составляет последняя запись, которая удаляется простым усечением файла до конца предыдущей записи. В остальных случаях приходится использовать временный файл или копию файла в памяти. Если вам приходится часто заниматься этим, вместо обычных файлов лучше подойдет база данных (см. главу 14 "Базы данных"). Самый распространенный тип файлов - текстовые файлы, а самый распространенный тип операций с ними - построчное чтение и запись. Для чтения строк используется оператор о (или его внутренняя реализация, 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 -040 -е '$word = о; print "First word is $word\n";' Цифры после -О определяют восьмеричное значение отдельного символа, который будет присвоен $/. Если задать недопустимое значение (например, -0777), Perl присваивает $/ неопределенное значение undef. Если задать -00, $/ присваивается "". Ограничение в один восьмеричный символ означает, что вы не сможете присвоить $/ многобайтовую строку - например, "%%\п" для чтения файлов программы fortune. Вместо этого следует воспользоваться блоком BEGIN: % 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. Для выбора другого манипулятора применяется функция select (см. главу 7 "Доступ к файлам"). Во всех системах строки разделяются виртуальным разделителем "\п", который называется переводом строки (newline). He существует такого понятия, как символ перевода строки. Это всего лишь иллюзия, которая по общему сговору поддерживается операционной системой, драйверами устройств, библиотеками С и Perl. Иногда это приводит к изменению количества символов в прочитанных или записываемых строках. Подробности заговора изложены в рецепте 8.11. Записи фиксированной длины читаются функцией read. Функция получает три аргумента: файловый манипулятор, скалярную переменную и количество читаемых байт. Возвращается количество прочитанных байт, а в случае ошибки - undef. Для записи используется функция print: $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). Чтобы узнать текущую позицию файлового манипулятора, воспользуйтесь функцией tell, а чтобы задать ее - функцией seek. Поскольку стандартная библиотека ввода/вывода стремится сохранить иллюзию того, что "\п" является разделителем строк, вы не сможете обеспечить переносимый вызов seek для смещений, вычисляемых посредством подсчета символов. Вместо этого seek следует вызывать только для смещений, возвращаемых tell: $pos = tell(DATAFILE);
8.1. Чтение строк с символами продолженияПроблемаИмеется файл с длинными строками, которые делятся на две и более строки. Символ \ означает, что данная строка продолжается на следующей. Вы хотите объединить разделенные строки. Подобное разделение длинных строк на короткие встречается в make-файлах, сценариях командного интерпретатора, конфигурационных файлах и многих языках сценариев.РешениеПоследовательно объединяйте прочитанные строки, пока не встретится строка без символа продолжения:while (defined($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/// пытается удалить \ в конце строки. Если подстановка заканчивается неудачей, значит, мы нашли строку без \. В противном случае мы читаем следующую запись, приписываем ее к накапливаемой переменной $line и возвращаемся к началу цикла while с помощью redo. Затем выполняется команда chomp. У файлов такого формата имеется одна распространенная проблема - невидимые пробелы между \ и концом строки. Менее строгий вариант подстановки выглядит так: if ($line =- s/\\\s*$//) { # Как и прежде } К сожалению, даже если ваша программа проищет мелкие погрешности, существуют и другие, которые этого не делают. Будьте снисходительны к входным данным и строги - к выходным. > Смотри также ---------------------------'o---
8.2. Подсчет строк (абзацев, записей) в файлеПроблемаТребуется подсчитать количество строк в файле.РешениеВо многих системах существует программа 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. Специальная переменная $. содержит количество прочитанных строк с момента последнего явного вызова close для файлового манипулятора: while $count = $.; В этом варианте все записи файла последовательно читаются без использования временных переменных. Чтобы подсчитать абзацы, присвойте перед чтением глобальному разделителю входных записей $/ пустую строку (""), и тогда оператор о будет считывать не строки, а целые абзацы: $/=''; # Включить режим чтения абзацев open(FILE, $file) or die "can't open $file: $!"; 1 while $para_count = $.; > Смотри также -------------------------------
8.3. Обработка каждого слова в файлеПроблемаТребуется выполнить некоторую операцию с каждым словом файла, по аналогии с функцией 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 в текущем локальном контексте. > Смотри также -------------------------------
8.4. Чтение файла по строкам или абзацам в обратном направленииПроблемаТребуется обработать каждую строку или абзац файла в обратном направлении.РешениеПрочитайте все строки в массив и организуйте обработку элементов массива от конца к началу:@lines = while ($line = pop @lines) { # Сдел чтo итп-то с $line } Или занесите строки в массив в обратном порядке: @lines = reverse foreach $line (@lines) { # Сделать что-то с $line } КомментарийОграничения, связанные с доступом к файлам (см. введение), не позволяют последовательно читать строки с конца файла. Приходится читать строки в память и обрабатывать их в обратном порядке. Конечно, расходы памяти при этом будут по крайней мере не меньше размера файла. В первом варианте массив строк перебирается в обратном порядке. Такая обработка является деструктивной, поскольку при каждой итерации из массива выталкивается последний элемент. Впрочем, то же самое можно сделать и недеструктивно:for ($i = $slines; $i != -1; $i--) { $line = $lines[$i]; } Во втором варианте генерируется массив строк, изначально расположенных в обратном порядке. Его тоже можно обработать недеструктивно. Мы получаем массив с обратным порядком строк, поскольку присваивание @lines обеспечива- ет вызов reverse в списковом контексте, что, в свою очередь, обеспечивает списковый контекст для оператора $/: # Внешний блок обеспечивает существование временной локальной копии $/ { local $/ = ''; @Daraaraphs = reverse } foreach $paragraph @paragraphs) { # Сделать что-то } Смотри также -------------------------------
8.5. Чтение из дополняемого файлаПроблемаТребуется читать данные из непрерывно растущего файла, однако при достижении конца файла (текущего) следующие попытки чтения завершаются неудачей.РешениеЧитайте данные, пока не будет достигнут конец файла. Сделайте паузу, сбросьте флаг 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 и FileHandle). Кроме того, можно вызвать метод POSIX: : clearerr:$naptime = 1; use 10::Handle; open (LOGFILE, "/tmp/logfile") or die "can't open /tmp/logfile: $!"; for (;;) { while ( sleep $naptime; LOGFILE->clearerr(); # Сбросить флаг ошибки ввода/вывода } Если простейший вариант в вашей системе не работает, воспользуйтесь функцией seek. Приведенный выше фрагмент с seek пытается переместиться на 0 байт от текущей позиции, что почти всегда завершается успехом. Текущая позиция при этом не изменяется, но зато для манипулятора сбрасывается признак конца файла, благодаря чему при следующем вызове for (;;) { for ($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; > Смотри также --------------------------------
8.6. Выбор случайной строки из файлаПроблемаТребуется прочитать из файла случайную строку.РешениеВоспользуйтесь функцией 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. Нам удалось случайным образом выбрать из файла строку со скоростью, пропорциональной количеству строк в файле. При этом максимальный объем используемой памяти даже в худшем случае равен размеру самой длинной строки. > Смотри также --------------------------------
8.7. Случайная перестановка строкПроблемаТребуется скопировать файл и случайным образом переставить строки копии.РешениеПрочитайте все строки в массив, перетасуйте элементы массива (см. рецепт 4.17) и запишите полученную перестановку:# Используется функция shuffle из главы 4 while (INPUT) { push(@lines, $_); } @reordered = shuffle(@lines); foreach (@reordered) { print OUTPUT $_; } КомментарийСамое простое решение - прочитать все строки файла и переставить их в памяти. Смещения строк в файле неизвестны, поэтому нельзя перетасовать список с номерами строк и затем извлечь строки в порядке их появления в файле. Впрочем, даже при известных смещениях такое решение, вероятно, будет работать медленнее, поскольку придется многократно перемещаться по файлу функцией seek вместо простого последовательного чтения.> Смотри также -------------------------------
8.8. Чтение строки с конкретным номеромПроблемаТребуется извлечь из файла строку с известным номером.РешениеПростейший выход - читать строки до обнаружения нужной:# Выборка строки с номером $OESIRED_LINE_NUMBER $. = 0; do { $LINE = Если подобная операция должна выполняться многократно, а файл занимает не слишком много места в памяти, прочитайте его в массив: @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( # Извлечь строку $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]; # Легко, правда? [> Смотри также -------------------------------
|