Использование дескрипторов файлов для работы с файлами в Perl. Чтение и запись в файл


Когда в программе мы создаем переменные и храним в них разнообразные данные, мы теряем их по завершении работы программы. Если нам необходимо сохранить данные и использовать их в разрабатываемых программах, мы создаем файл, записываем в него данные и сохраняем его на диске. Практически любой язык программирования предоставляет программисту средства манипулирования файлами и хранимыми в них данными.

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

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

7.1. Дескрипторы файлов

Дескриптор - это символическое имя, которое используется в программе Perl для представления файла, устройства, сокета или программного канала. При создании дескриптора он "присоединяется" к соответствующему объекту данных и представляет его в операциях ввода/вывода. Мы дали наиболее полное определение дескриптора, чтобы читатель понимал, что дескриптор позволяет работать не только с данными файлов, но и с данными других специальных программных объектов, реализующих специфические задачи получения и передачи данных. Когда дескриптор присоединен к файлу, мы будем называть его дескриптором файла.

В программе дескриптор файла чаще всего создается при открытии файла функцией open (), которой передаются два параметра - имя дескриптора и строка с именем файла и режимом доступа:

open(LOGFILE, "> /temp/logfile.log");

Этот оператор создает дескриптор с именем LOGFILE и присоединяет его к файлу с указанным именем, который открывается в режиме записи (строка второго параметра начинается с символа ">"). В этом разделе мы не будем касаться вопросов, связанных с режимом открытия файла, а сконцентрируем наше внимание на дескрипторах. В следующем разделе режимы открытия файла будут рассмотрены нами подробнее.

Дескриптор, как указывалось, является символическим именем файла и представляет собой правильный идентификатор, который не может совпадать с зарезервированными словами Perl. В нашем примере создается дескриптор LOGFILE, "замещающий" в операциях ввода/вывода файл, к которому он присоединен (/temp/logfile.log). Например, известной нам функцией print о мы можем теперь записать в этот файл значение какой-либо переменной:

print LOGFILE $var;

Любой созданный дескриптор попадает в символьную таблицу имен Perl, в которой находятся также имена всех переменных и функций. Однако дескриптор не является переменной, хотя некоторые авторы и называют его файловой переменной. В имени дескриптора не содержится никакого префикса, присущего переменным Perl ($, @ или %). Поэтому его нельзя непосредственно использовать в операции присваивания и сохранить в переменной или передать в качестве параметра в функцию. Для подобных целей приходится использовать перед его именем префикс *, который дает ссылку на глобальный тип данных. Например, предыдущий оператор печати в файл, определенный дескриптором LOGFILE, можно осуществить с помощью следующих операторов, предварительно сохранив ссылку на дескриптор в переменной $iogf:

$logf = *LOGFILE; print $logf $var;

В операции print первая переменная $iogf замещает дескриптор файла LOGFILE, в который выводится значение второй переменной $var.

В любой программе Perl всегда существуют три предопределенные дескриптора (STDIN, STDOUT и STDERR), которые связаны со стандартными устройствами ввода/вывода и используются некоторыми функциями Perl в качестве умалчиваемых дескрипторов файлов ввода или вывода. Как мы уже знаем, дескриптор STDIN связан со стандартным устройством ввода (обычно клавиатура), STDOUT и STDERR - со стандартным устройством вывода (обычно экран монитора). Стандартное устройство ввода используется операцией о, если в командной строке вызова сценария Perl не задан список файлов. Дескриптор STDOUT ПО уМОЛЧаНИЮ ИСПОЛЬЗуеТСЯ ФУНКЦИЯМИ print И die, а

STDERR - функцией warn. Другие функции также используют предопределенные дескрипторы файлов для вывода своей информации.

При вызове программ в среде Unix и DOS можно перенаправлять стандартный ввод и вывод в другие файлы, задавая в командной строке их имена с префиксами > для файла вывода и < для файла ввода:

peri program.pl out.dat

При выполнении программы program.pi все исходные данные должны быть подготовлены в файле in.dat. Вывод будет сохранен в файле out.dat, а не отображаться на экране монитора.

Перенаправление стандартного ввода и вывода, а также стандартного отображения ошибок, можно осуществлять непосредственно в программе Perl. Для этого следует функцией ореп() связать соответствующий предопределенный дескриптор с некоторым дисковым файлом:

open(STDIN, "in.dat"); open(STDOUT, ">out.dat"); open(STDERR, ">err.dat");

Теперь весь стандартный ввод/вывод будет осуществляться через указанные в операторах open о файлы. Обратите внимание, что при переопределении стандартных файлов вывода и ошибок перед именами файлов стоит префикс ">", указывающий на то, что файлы открываются в режиме записи.

7.2. Доступ к файлам

Как мы уже знаем, для доступа к файлу из программы Perl необходим дескриптор. Дескриптор файла создается функцией open (), которая является списковой операцией Perl:

open ДЕСКРИПТОР, ИМЯ_ФАЙЛА; open ДЕСКРИПТОР;

При выполнении операции open с заданным в параметрах именем файла открывается соответствующий файл и создается дескриптор этого файла. В качестве дескриптора файла в функции open () можно использовать выражение - его значение и будет именем дескриптора. Имя файла задается непосредственно в виде строкового литерала или выражения, значением которого является строка. Операция open без имени файла открывает файл, имя которого содержится в скалярной переменной $ДЕСКРИПТОР, которая не может быть лексической переменной, определенной функцией ту(). Пример 7.1 демонстрирует использование операции open () для открытия файлов.

#! peri -w

$var = "out.dat";

$FILE4 = "file4.dat";

open FILE1, "in.dat"; # Имя файла задано строкой

open FILE2, $var; # Имя файла"задано переменной

open FILE3, "/perlourbook/01/".$var; # Имя файла вычисляется в выражении

open FILE4; # Имя файла в переменной $FILE4

Любой файл можно открыть в одном из следующих режимов: чтения, записи или добавления в конец файла. Это осуществляется присоединением соответствующего префикса к имени файла: < (чтение), > (запись), » (добавление). Если префикс опущен, то по умолчанию файл открывается в режиме чтения. Запись информации в файл, открытый в режиме записи (префикс >), осуществляется в начало файла, что приводит к уничтожению содержащейся в нем до его открытия информации. Информация, содержащаяся в файле, открытом в режиме добавления (префикс »), не уничтожается, новые записи добавляются в конец файла. Если при открытии файла в режиме записи или добавления не существует файла с указанным именем, то он создается, что отличает эти режимы открытия файла от режима чтения, при котором файл должен существовать. В противном случае операция открытия завершается с ошибкой и соответствующий дескриптор не создается.

Perl позволяет открыть файл еще в одном режиме - режиме чтения/записи. Для этого перед префиксом чтения <, записи > или добавления » следует поставить знак плюс +. Отметим различия между тремя режимами чтения/записи +<, +> и +». Первый и третий режимы сохраняют содержимое открываемого файла, тогда как открытие файла с использованием второго режима (+>) сначала очищает содержимое открываемого файла. Третий режим отличается от первых двух тем, что запись в файл всегда осуществляется в конец содержимого файла.

Открытие файла и создание для него дескриптора функцией open () охватывает все практически важные режимы работы с файлом. Однако возможности этой функции не позволяют задать права доступа для создаваемых файлов, а также вообще решить, следует ли создавать файл, если его не существует. Для подобного "тонкого" открытия файлов можно использовать функцию sysopeno, которая позволяет программисту самому задать отдельные компоненты режима работы с файлом: чтение, запись, создание, добавление, очистка содержимого и т. д. Синтаксис этой функции таков:

sysopen ДЕСКРИПТОР, ИМЯ_ФАЙЛА, ФЛАГ [, РАЗРЕШЕНИЕ];

Здесь параметр ИМЯ_ФАЙЛА представляет имя файла без префиксов функции open (), определяющих режим открытия файла. Последний задается третьим параметром ФЛАГ - числом, представляющим результат операции побитового ИЛИ (|) над константами режимов, определенными в модуле Fcnti. Состав доступных констант зависит от операционной системы. В табл. 7.1 перечислены константы режима, встречающиеся практически во всех операционных системах.

Таблица 7.1. Константы режима доступа к файлу

Константа

Значение

0_RDONLY

Только чтение

0_WRONLY

Только запись

O_RDWR

Чтение и запись

O_CREAT

Создание файла, если он не существует

О EXCL

Завершение с ошибкой, если файл уже существует

0_APPEND

Добавление в конец файла %

Права доступа (необязательный параметр РАЗРЕШЕНИЕ) задаются в восьмеричной системе и при их определении учитывается текущее значение маски доступа к процессу, задаваемого функцией umasko. Если этот параметр не задан, то Perl использует значение о666.

(О правах доступа читайте документацию Perl для установленной на вашем компьютере операционной системе.)

В примере 7.2 собраны операции открытия файлов функцией open (} и эквивалентные ИМ ОТКРЫТИЯ С ПОМОЩЬЮ фуНКЦИИ sysopen () .

use Fcnti;

# Только чтение

Open FF, "< file.txt";

sysopen FF, "file.txt", O_RDONLY;

# Только запись (создается, если не существует,

# и очищается содержимое, если существует)

Open FF, "> file.txt";

sysopen FF, "file.txt", 0_WRONLY | 0_CREAT | OJTRUNC;

# Добавление в конец (создается, если не существует)

Open FF, "» file.txt";

Sysopen FF, "file.txt", OJJRONLY I 0_CREAT I O_APPEND;

# Чтение/запись (файл должен существовать) open FF, "+< file.txt"; sysopen FF, "file.txt", O_RDWR;

# Чтение/запись (файл очищается)

Open FF, "+> file.txt";

sysopen FF, "file.txt", O_RDWR | 0_CREAT I OJTRUNC;

При открытии файла функции open о и sysopen о возвращают значение о, если открытие файла с заданным режимом произошло успешно, и неопределенное значение undef в противном случае. Всегда следует проверять успешность выполнения операции открытия файла, прекращая выполнение программы функцией die (). Эта функция отображает список передаваемых ей параметров и завершает выполнение сценария Perl:

open(FF, "+< $file") or"die "Нельзя открыть файл $file: $!";

Обратите внимание, в сообщении функции die () используется специальная переменная $!, в которой хранится системное сообщение или код ошибки. Эта информация помогает обнаружить и исправить ошибки в программе. Например, если переменная $fiie содержит имя не существующего файла, то при выполнении предыдущего оператора пользователь может увидеть сообщение следующего вида:

Нельзя открыть файл file.txt: No such file or directory at D:\PERL\EX2.PL line 4.

Английский текст этого сообщения представляет информацию, содержащуюся в Переменной $!.

Для полноты описания работы с функцией open о следует сказать, что если имя файла представляет строку "-", то открываемый файл соответствует стандартному вводу STDIN. Это означает, что ввод с помощью созданного дескриптора файла осуществляется со стандартного устройства ввода. Если имя файла задано в виде строки ">-", то это соответствует выводу на стандартное устройство вывода, представленное в программе дескриптором STDOUT.

Последнее, что нам хотелось бы осветить в связи с дескрипторами файлов, - это создание дескриптора-дубликата. Если в строке имени файла после префикса режима открытия следует амперсанд "&", то ее оставшаяся часть рассматривается как имя дескриптора файла, а не как имя открываемого файла. В этом случае создается независимая копия этого дескриптора с именем, заданным первым параметром функции open <). Оба дескриптора имеют общий указатель текущей позиции файла, но разные буферы ввода/вывода. Закрытие одного из дескрипторов не влияет на работу другого. В программах Perl возможность создания копии дескриптора в основном применяется для восстановления стандартных файлов ввода/вывода после их перенаправления на другие файлы (пример 7.3).

#! peri -w

# Создание копии дескриптора STDOUT open(OLDOUT, ">&STDOUT");

# Перенаправление стандартного вывода

open(STDOUT, "> file.out") or die "Невозможно перенаправить STDOUT: $!";

# Печать в файл file.out

print "Информация в перенаправленный STDOUTXn";

# Закрытие перенаправленного дескриптора стандартного вывода close(STDOUT) or die "Невозможно закрыть STDOUT: $!";

# Восстановить файл стандартного вывода

open(STDOUT, ">&OLDOUT") or die "Невозможно восстановить STDOUT: $!";

# Закрыть копию дескриптора стандартного вывода STDOUT close(OLDOUT) or die "Невозможно закрыть OLDOUT: $!";

# Печать в восстановленный файл стандартного вывода print "Информация в восстановленный STDOUTXn";

По завершении работы с файлом он закрывается функцией close о. Единственным необязательным параметром этой функции является дескриптор, ассоциированный с файлом:

close ДЕСКРИПТОР;

Эта функция возвращает значение Истина, если успешно очищен буфер ввода/вывода и закрыт системный дескриптор файла. Вызванная без параметра, функция close закрывает файл, связанный с текущим дескриптором, установленным функцией select о.

Следует отметить, что закрывать файлы в программе функцией close о не обязательно. Дело в том, что открытие нового файла с дескриптором, уже связанным с каким-либо файлом, закрывает этот старый файл. Более того, при завершении программы все открытые в ней файлы закрываются. Однако такое неявное закрытие файлов таит в себе потенциальные ошибки из-за невозможности определить, завершилась ли эта операция корректно. Может оказаться, что при записи в файл переполнится диск, или будет разорвана связь с удаленным устройством вывода. Подобные ошибки можно "отловить", если использовать явное закрытие файла и проверять содержимое специальной переменной $!:

closet FILEIO) or die "Ошибка закрытия файла: $!";

Существует еще один нюанс, связанный с явным закрытием файлов. При чтении из файла специальная переменная $. (если ее значение не изменено явным образом в программе) хранит номер последней прочитанной записи файла. При явном закрытии файла функцией close о значение этой переменной обнуляется, тогда как при неявном закрытии оно остается равным номеру последней прочитанной записи старого файла и продолжает увеличиваться при операциях чтения из нового файла.

Чтение информации из файла осуществляется операцией о, операндом которой является дескриптор файла. В скалярном контексте при первом выполнении эта операция читает первую запись файла, устанавливая специальную переменную $., отслеживающую количество прочитанных записей, равной 1. Последующие обращения к операции чтения из файла с тем же дескриптором приводят к последовательному чтению следующих записей. В списковом контексте эта операция читает все оставшиеся записи файла и возвращает список, элементами которого являются записи файла. Разделитель записей хранится в специальной переменной $/, и по умолчанию им является символ новой строки "\n". Perl позволяет задать и другой разделитель записей обычной операцией присваивания переменной $/ нового символа разделителя записей. В примере 7.4 демонстрируются некоторые приемы чтения из файла.

#! peri -w

open(F2, "out.dat") or die "Ошибка открытия файла: $!";

$linel = ; # Первая запись файла in.dat $line2 = ; # Вторая запись файла in.dat

@rest = ; # Оставшиеся записи файла in.dat

$/=":"; I Задание другого разделителя записей файла @f2 = ;

# Печать прочитанных записей файла out.dat for($i=0; $i<=$#f2; $i++) { print "$f2[$i]\n";

}

$/ = "\n"; # Восстановление умалчиваемого разделителя записей

close(Fl) or die $!; close(F2) or die $!;

open(F3, "out.dat") or die "Ошибка открытия файла: $!"; print ; # Печать всего файла close(F3) or die $!;

Несколько комментариев к программе примера 7.4. В переменные $iinel и $iine2 читаются соответственно первая и вторая строка файла in.dat, так как используется умалчиваемый разделитель записей "\п". Элементы массива @rest хранят строки с третьей по последнюю этого же файла: в операторе присваивания операция чтения выполняется в списковом контексте.

Перед чтением записей файла out.dat устанавливается новый разделитель записей - символ ":". Если файл out.dat, например, содержит только одну строку

111: 222: 333: Конец

То элементы массива @ f г будут содержать следующие значения:

$f2 = "111:" $f2[l] = "222:" $f2 = "333:" $f2 = "Конец"

При достижении конца файла операция о возвращает неопределенное значение, которое трактуется как Ложь. Это обстоятельство обычно используется для организации чтения записей файла в цикле:

While($line = ) {

print $line; f Печать очередной строки связанного

# с дескриптором F1 файла }

Запись в файл, открытый в режиме записи или добавления, осуществляется функцией print () с первым параметром, являющимся дескриптором файла:

Print ДЕСКРИПТОР СПИСОК_ВЫВОДД;

Эта операция записывает содержимое элементов списка в том порядке, в котором они определены в вызове функции, и не добавляет в конец списка разделителя записей. Об этом должен позаботиться сам программист:

$/=":"; # Разделитель записей

print Fl @recll, $/; # Запись в файл первой записи

print Fl @rec!2, $/; tt Запись в файл второй записи

No comma allowed after filehandle

Если в функции print не указан дескриптор файла, то по умолчанию вывод осуществляется в стандартный файл вывода с дескриптором STDOUT. Эту установку можно изменить функцией select (). Вызванная без параметров, она возвращает текущий умалчиваемый дескриптор для вывода функциями print () и write (). Если ей передается единственный параметр, то этот параметр должен быть дескриптором файла. В этом случае она также возвращает текущий умалчиваемый дескриптор и меняет его на дескриптор, определенный переданным ей параметром.

$oldfilehandle = select(Fl); I Сохранение текущего дескриптора по

# умолчанию и назначение нового F1

print $line; # Вывод в дескриптор F1 select($oldfilehandle); # Восстановление старого дескриптора

# по умолчанию print $line; # Вывод в старый дескриптор

Файлы в Perl интерпретируются как неструктурированные потоки байтов. При работе с файлом через дескриптор отслеживается его текущая позиция. Операции чтения/записи выполняются с текущей позиции файла. Если, например, была прочитана запись длиной 80 байт, то следующая операция чтения или записи начнется с 81 байта файла. Для определения текущей позиции в файле используется функция tell (), единственным параметром которой может быть дескриптор файла. Она возвращает текущую позицию в связанном с дескриптором файле. Эта же функция без параметра возвращает текущую позицию в файле, для которого была в программе выполнена последняя операция чтения.

Текущая позиция в файле автоматически изменяется в соответствии с выполненными операциями чтения/записи. Ее можно изменить с помощью функции seek о, которой передаются в качестве параметров дескриптор файла, смещение и точка отсчета. Для связанного с дескриптором файла устанавливается новая текущая позиция, смещенная на заданное параметром СМЕЩЕНИЕ число байт относительно точки отсчета:

seek ДЕСКРИПТОР, СМЕЩЕНИЕ, TO4KAJDTC4ETA;

Параметр ТОЧКА_ОТСЧЕТА может принимать одно из трех значений: о - начало файла, 1 - текущая позиция, 2 - конец файла. Смещение может быть как положительным, так и отрицательным. Обычно оно отрицательно для смещения относительно конца файла и положительно для смещения относительно начала файла. Для задания точки отсчета можно воспользоваться константами SEEK_SET, SEEK_CUR и SEEK_END из модуля ю: :Seekabie, которые соответствуют началу файла, текущей позиции и концу файла. Естественно, необходимо подключить этот модуль к программе с помощью ключевого слова use. Например, следующие операторы устанавливают одинаковые текущие позиции в файлах:

use 10::Seekable; seek FILE1, 5, 0; seek FILE2, 5, SEEK_SET;

Для перехода в начало или в конец файла следует использовать нулевое смещение относительно соответствующих точек отсчета при обращении к функции seek ():

seek FILE1, 0, 0; # Переход в начало файла seek FILE1, 0, 2; § Переход в конец файла

Кроме операции чтения записей файла о, Perl предоставляет еще два способа чтения информации из файла: функции getc () и read (). Первая читает один байт из файла, тогда как вторая читает записи фиксированной длины.

Функция getc возвращает символ в текущей позиции файла, дескриптор которого передан ей в качестве параметра, или неопределенное значение в случае достижения конца файла или возникновении ошибки. Если функция вызывается без параметра, то она читает символ из стандартного файла ввода STDIN.

getc; t Чтение символа из STDIN

getc Fl; # Чтение символа в текущей позиции файла с дескриптором F1

Функции read () передаются три или четыре параметра и ее синтаксис имеет вид:

read ДЕСКРИПТОР, ПЕРЕМЕННАЯ, ДЛИНА [,СМЕЩЕНИЕ] ;

Она читает количество байтов, определенное значением параметра ДЛИНА, в скалярную переменную, определяемую параметром ПЕРЕМЕННАЯ, из файла с дескриптором, заданным первым параметром ДЕСКРИПТОР. Возвращаемое значение - действительное количество прочитанных байтов, о при попытке чтения в позиции конца файла и неопределенное значение в случае возникновения ошибки. Параметр СМЕЩЕНИЕ определяет количество сохраняемых байтов из содержимого переменной ПЕРЕМЕННАЯ, т. е. запись прочитанных из файла данных будет добавлена к содержимому переменной после байта, определяемого значением параметра СМЕЩЕНИЕ. Отрицательное значение смещения -п (п - целое число) означает, что из содержимого переменной ПЕРЕМЕННАЯ отбрасываются последние п байтов и к оставшейся строке добавляется запись, прочитанная из файла. Пример 7.5 демонстрирует чтение записей фиксированной длины в предположении, что файл in.dat содержит три строки данных:

One Two Three

#! peri -w

open(Fl, "in.dat") or die "Ошибка открытия файла: $!";

$string = "1234567890";

read Fl, $string, 6; I Чтение шести байт в переменную без смещения

print $string,"\n"; # $string = "OneXnTw"

read Fl, $string, 6, length($string);

print $string,"\n"; # $string = "One\nTwo\nThre"

Функция length о возвращает количество символов (байтов) в строковых данных, хранящихся в скалярной переменной, переданной ей в качестве параметра. После выполнения первой операции чтения содержимое переменной $string было уничтожено, так как эта функция read о вызывалась без смещения. Тогда как при втором чтении хранившиеся данные в переменной $string были полностью сохранены.

Операции о, print, read, seek и tell относятся к операциям буферизованного ввода/вывода, т. е. они для повышения скорости выполнения используют буферы. Perl для выполнения операций чтения из файла и записи в файл предлагает также аналоги перечисленных функций, не использующие буферы при выполнении соответствующих операций с содержимым файла.

Функции sysread и syswrite являются не буферизованной заменой операции

о И ФУНКЦИИ print, а ФУНКЦИЯ sysseek Заменяет ФУНКЦИИ seek И tell.

Функции не буферизованного чтения и записи получают одинаковые параметры, которые соответствуют параметрам функции read:

sysread ДЕСКРИПТОР, ПЕРЕМЕННАЯ, ДЛИНА [,СМЕЩЕНИЕ]; syswrite ДЕСКРИПТОР, ПЕРЕМЕННАЯ, ДЛИНА [.СМЕЩЕНИЕ];

Смысл всех параметров аналогичен параметрам функции read (). Возвращаемым значением этих функций является истинное количество прочитанных/записанных байт, о в случае достижения конца файла или undef при возникновении ошибки.

Параметры функции sysseek о полностью соответствуют параметрам функции seek():

sysseek ДЕСКРИПТОР, СМЕЩЕНИЕ, ТОЧКА_ОТСЧЕТА;

Все, сказанное относительно использования функции seek о, полностью переносится и на ее не буферизованный аналог.

Функциональность буферизованной операции tell () реализуется следующим вызовом функции sysseek:

$position = sysseek Fl, 0, 1; # Текущая позиция указателя файла

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

#! peri -w use Fcntl;

# Открытие файла в режиме чтение/запись sysopen Fl, "in.dat", OJRDWR;

# Чтение блока в 14 байт

$read = sysread Fl, $string, 14;

Warn "Прочитано $read байт вместо 14\n" if $read != 14;

# Установка текущей позиции (на 15 байт). $position = sysseek Fl, 0, 1; die "Ошибка позиционирования: $!\п" unless defined $position;

# Запись строки в текущей позиции $string = "Новое значение"; $written = syswrite Fl, $string, length($string);

die "Ошибка записи: $!\n" if $written != length($string); # Закрытие файла

close Fl or die $!;

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

7.3. Операции с файлами

Перед изучением функций, выполняющих действия с целыми файлами, мы напомним читателю основные положения, связанные с организацией файловой системы UNIX и процедур доступа к файлам. Функции Perl разрабатывались для работы именно с этой файловой системой, хотя в определенной степени многое из того, о чем пойдет речь, применимо и к файловым системам других платформ.

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

Регистрационное имя легко запоминается пользователем, но для системы удобнее вести учет пользователей, идентифицируя их не по символическим регистрационным именам, а по числовым идентификаторам. Поэтому каждому пользователю системы UNIX помимо мнемонического регистрационного имени присваивается также числовой идентификатор пользователя (uid - User IDentifier) и идентификатор группы (gid - Group IDentifier), к которой он относится. Значения uid и gid приписываются процессу, в котором выполняется командный интерпретатор shell, запускаемый при входе пользователя в систему. Эти же идентификаторы передаются и любому другому процессу, запускаемому пользователем во время его сеанса работы в UNIX.

Файловая система UNIX представляет собой дерево, промежуточные вершины которого соответствуют каталогам, а листья файлам или пустым каталогам. Каждый файл идентифицируется своим уникальным полным именем, которое включает в себя полный путь (pathname) от корня файловой системы через промежуточные вершины (каталоги) непосредственно к файлу. Корневой каталог имеет предопределенное имя, представляемое символом "/". Этот же символ используется и для разделения имен каталогов в цепочке полного имени файла, например /bin/prog.exe.

Каждый файл в файловой системе UNIX характеризуется значительно большим объемом информации, чем, например, файл в файловой системе FAT. Эта информация включает, в частности, данные о владельце файла, группе, к которой принадлежит владелец файла, о том, кто имеет право на чтение файла, запись в файл, на выполнение файла и т. д. Эта информация позволяет задавать разные права доступа к файлу для следующих категорий пользователей: владелец файла, члены группы владельца, прочие пользователи. Вся существенная информация о файле хранится в специальной структуре данных, называемой индексным дескриптором (mode). Индексные дескрипторы размещаются в специальной области диска, формируемой при его форматировании в системе UNIX.

При запуске процесса с ним связываются два идентификатора пользователя: действительный (real) и эффективный (effective) и два аналогичных идентификатора группы пользователей. Действительные идентификаторы пользователя и группы - это постоянные идентификаторы, связываемые со всеми процессами, запускаемыми пользователем. Эффективные идентификаторы - это временные идентификаторы, которые могут устанавливаться для выполнения определенных действий. Например, при изменении пользователем пароля программа passwd автоматически устанавливает эффективные идентификаторы процесса таким образом, чтобы обеспечить права записи в файл паролей.

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

Для каждого зарегистрированного пользователя системы создается так называемый "домашний" (home) каталог пользователя, к которому он имеет неограниченный доступ, а также и ко всем каталогам и файлам, содержащимся в нем. Пользователь может создавать, удалять и модифицировать каталоги и файлы из своего домашнего каталога. Потенциально возможен доступ и ко всем другим файлам, однако он может быть ограничен, если пользователь не имеет достаточных привилегий.

Любой пользователь, создавший собственный файл, считается его владельцем. Изменить владельца файла из сценария Perl можно функцией chown (). Параметром этой функции является список, первые два элемента которого должны представлять новые.числовые идентификаторы uid и gid. Остальные элементы списка являются именами файлов, для которых изменяется владелец. Эта функция возвращает количество файлов, для которых операция изменения владельца и группы прошла успешно.

@list = (234, 3, "filel.dat", "file2.dat");

$number = chown(@list);

warn "Изменился владелец не у всех файлов!" if $number != @list-2;

Функция chmodo изменяет права доступа для файлов, представленных в списке, передаваемом ей в качестве параметра. Первым элементом этого списка должно быть трехзначное восьмеричное число, задающее права доступа для владельца, пользователей из группы, в которую входит владелец, и прочих пользователей. Каждая восьмеричная цифра определяет право на чтение файла, запись в файл и его выполнение (в случае если файл представляет выполняемую программу) для указанных выше групп пользователей. Установленные биты ее двоичного представления отражают соответствующие права доступа к файлу. Например, если установлены все три бита (восьмеричное число 7), то соответствующая группа пользователей обладает всеми перечисленными правами: может читать из файла, записывать в файл и выполнять его. Значение равное 6 определяет право на чтение и запись, 5 позволяет читать из файла, выполнять его, но не позволяет записывать в этот файл и т. д. Обычно не выполняемый файл создается с режимом доступа 0666 - все пользователи могут читать и записывать информацию в файл, выполняемый файл - с режимом 0777. Если владелец файла желает ограничить запись в файл пользователей не его группы, то следует выполнить следующий оператор:

Chmod 0664, "file.dat";

Возвращаемым значением функции chmodo, как и функции chowno, является количество файлов из списка, для которых операция изменения прав доступа завершилась успешно.

В структуре индексного дескриптора файла существует три поля, в которых хранится время последнего обращения (atime) к файлу, его изменения (mtime) файла и изменения индексного дескриптора Xctime): Функцией utimeO можно изменить время последнего обращения и модификации файла. Ее параметром является список, содержащий имена обрабатываемых файлов, причем первые два элемента списка - числовые значения нового времени последнего доступа и модификации:

gfiles = ("filel.dat", "file2.dat");

$now = time;

Utime $now, $now, 6files;

В этом фрагменте кода время последнего доступа и модификации файлов из списка @files изменяется на текущее время, полученное с помощью функции time.

Отметим, что при выполнении функции utime о изменяется и время последней модификации индексного дескриптора (ctime) - оно устанавливается равным текущему времени. Возвращаемым значением является количе- . ство файлов, для которых операция изменения времени последнего доступа и модификации прошла успешно.

Файловая система UNIX позволяет создавать ссылки на один и тот же файл. Это реализуется простым указанием одного и того же индексного дескриптора для двух элементов каталога. Такие ссылки называются жесткими (hard) ссылками, и операционная система не различает элемент каталога, созданный при создании файла, и ссылок на этот файл. При обращении к файлу по ссылке и по имени изменяются поля индексного дескриптора. Физически файл уничтожается только тогда, когда уничтожается последняя жесткая ссылка на файл.

В UNIX существует еще один тип ссылок на файл - символические ссылки. Эти ссылки отличаются от жестких тем, что они косвенно ссылаются на файл, имя которого хранится в блоке данных символической ссылки.

Жесткие ссылки создаются в Perl функцией linko, а символические - функцией symiinko. Синтаксис этих функций одинаков - их два параметра представляют имя файла, для которого создается ссылка, и новое имя файла-ссылки:

link СТАРЫЙ_ФАЙЛ, НОВЫЙ_ФАЙЛ; symlink СТАРЫЙ_ФАЙД, НОВЫЙ_ФАЙЛ;

При успешном создании жесткой ссылки функция link о возвращает Истина, иначе Ложь. Создание символической ссылки функцией symiinko сопровождается возвратом ею числа i в случае успешного выполнения операции иов противном случае.

The Unsupported function link function is unimplemented at D:\EX2.PL line 2.

The symlink function is unimplemented at D:\EX2.PL line 2.

Удалить существующие ссылки на файл можно функцией unlink о. Эта функция удаляет одну ссылку на каждый файл, заданный в списке ее параметров. Если ссылок на файл не существует, то удаляется сам файл. Функция возвращает количество файлов, для которых успешно прошла операция удаления. Вызов функции unlink без списка параметров использует содержимое специальной переменной $_ в качестве списка параметров. Следующий фрагмент кода удаляет все резервные копии файлов текущего каталога:

Unlink <*.bak>;

В структуре индексного дескриптора поле nlink содержит количество жестких ссылок на файл. Его можно использовать совместно с функцией unlink о для удаления всех ссылок на файл. Если ссылок нет, то это поле имеет значение 1 (только имя файла, определенное при его создании, ссылается на индексный дескриптор файла).

Две последние операции, связанные с файлами, - это переименование и усечение файла. Функция rename о меняет имя файла, заданного первым параметром, на имя, определяемое вторым параметром этой функции:

Rename "old.dat", "new.dat";

Этот оператор переименует файл old.dat в файл new.dat. Функция переименования файла возвращает i при успешном выполнении этой операции и о в противном случае.

Функция truncated усекает файл до заданной длины. Для задания файла можно использовать как имя файла, так и дескриптор открытого файла:

truncate ДЕСКРИПТОР, ДЛИНА; truncate ИМЯ_ФАЙЛА, ДЛИНА;

Функция возвращает значение Истина, если длина файла успешно усечена до количества байт, определенных в параметре ДЛИНА, или неопределенное значение undef в противном случае. Под усечением файла понимается не только уменьшение его длины, но и увеличение. Это означает, что значение второго параметра функции truncate о может быть больше истинной длины файла, что позволяет делать "дыры" в содержимом файла, которые в дальнейшем можно использовать для записи необходимой информации, не уничтожая уже записанную в файл (пример 7.7).

#! peri -w

# Создание файла с "дырами" for($i=l;$i<=3;$i++H

open(F, "»out.dat") or die $!; print F "Запись".$i;

close F;

open(F, "»out.dat") or die $!; truncate F, 19*$i;

close F;

} " tt Запись информации в "дыры" open(F, "+

seek F, 0,1;

read F,$reel,7;

seek F,0,l;

print F ""; } close F;

На каждом шаге первого цикла for примера 7.7 в конец файла out.dat записывается информация длиной 7 байтов, а потом его длина увеличивается на 12 байтов, образуя пустое пространство в файле. Следующий цикл for заносит в эти созданные "дыры" информацию длиной 12 байтов, не затирая хранящуюся в файле информацию. Обратите внимание, что для изменения длины файла функцией truncate приходится закрывать его и снова открывать. Это связано с тем обстоятельством, что функция truncate о добавляет пустое пространство в начало файла, сдвигая в конец его содержимое, если применять ее, не закрывая файл. Можете поэкспериментировать с программой примера 7.7, открыв файл перед выполнением первого цикла for, и закрыв его после завершения цикла. Содержимое файла даст вам наглядное представление о работе функции truncate в этом случае. У нас же после выполнения первого цикла for содержимое файла out.dat выглядит так:

Запись! Запись2 Запись3

\

По завершении всей программы файл будет содержать следующую строку:

ЗаписьKCONTENTS:1>Запись2<СОЫТЕЫТЗ:2>ЗаписьЗ<СОМТЕ№ГЗ:3>

7.4. Получение информации о файле

Мы знаем, что в файловой системе UNIX информация о файле хранится в его индексном дескрипторе (inode). состоит из 13 полей, для которых используются специальные обозначения. Все они перечислены в табл. 7.2.

Таблица 7.2. Структура индексного дескриптора

Поле

Описание

Номер устройства в файловой системе

Номер индексного дескриптора

Mode

Режим файла (тип и права доступа)

Nlink

Количество жестких ссылок на файл (в отсутствии ссылок равно 1)

Числовой идентификатор владельца файла

Числовой идентификатор группы владельца файла

Rdev

Идентификатор устройства (только для специальных файлов)

Size

Размер файла в байтах

A time

Время последнего обращения к файлу с начала эпохи

Mtime

Время последнего изменения файла с начала эпохи

С time

Время изменения индексного дескриптора с начала эпохи

Blksize

Предпочтительный размер блока для операций ввода/вывода

Blocks

Фактическое количество выделенных блоков для размещения файла

Для получения значений полей структуры индексного дескриптора файла в Perl предназначена функция stato. Ее единственным параметром может быть либо имя файла, либо дескриптор открытого в программе файла. Она возвращает список из 13 элементов, содержащих значения полей структуры индексного дескриптора файла в том порядке, как они перечислены в табл. 7.2. Типичное использование в программе Perl представлено ниже

($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size, $atime,$mtime,$ctime,$blksize,$blocks) = stat($filename);

Присваивание значений полей списку скалярных переменных с идентификаторами, соответствующими названиям полей, способствует лучшей читаемости программы, чем присваивание массиву скаляров:

@inode = stat($filename);

В последнем случае получить значение соответствующего поля можно только с помощью индекса, что не совсем удобно, так как надо помнить номер нужного поля структуры.

Если при обращении к функции stato не указан параметр, то она возвращает структуру индексного дескриптора файла, чье имя содержится в специальной переменной $_.

Функция получения информации о файле при успешном выполнении в списковом контексте возвращает список значений полей структуры индексного дескриптора файла или пустой список в случае неудачного завершения. В скалярном контексте она возвращает булево значение Истина или Ложь в зависимости от результатов своего выполнения.

Для удобства использования информации о файле функция stato при успешном выполнении кэширует полученные значения полей. Если вызвать эту функцию со специальным дескриптором файла _ (символ подчеркивания), то она возвратит информацию, хранящуюся в кэше от предыдущего ее вызова. Это позволяет проверять различные атрибуты файла без повторного вызова функции stat () или сохранения результатов ее выполнения в переменных программы.

Функцию stato можно использовать для получения структуры индексного дескриптора не только файла, но и жестких ссылок на него, а также каталогов, так как они являются также файлами, блоки данных которых содержат имена файлов каталога и их числовых индексных дескрипторов. Для получения информации о символических ссылках следует использовать функцию is tat о, которая возвращает список значений полей структуры индексного дескриптора самой ссылки, а не файла, на который она ссылается. Эта функция работает аналогично функции stat (), включая использование специального дескриптора _.

Кроме двух этих функций, позволяющих получать информацию о файлах системы, в Perl предусмотрен набор унарных операций, возвращающих значение только одного поля структуры индексного дескриптора. Эти операции в документации называются "операциями -х", так как их названия состоят из дефиса с последующим единственным символом. Все они являются унарными именованными операциями и имеют свой приоритет в сложных выражениях, о котором мы рассказывали в гл. 3. Полный перечень унарных операций проверки атрибутов файлов представлен в табл. 7.3.

Таблица 7.3. Унарные именованные операции проверки файлов

Операция

Проверяемый атрибут

Г

W

Записывать в файл может эффективный uid/gid

Х

Файл может выполняться эффективным uid/gid

О

Владельцем файла является эффективный uid

R

W

Записывать в файл может действительный uid/gid

Унарные операции применяются к строке, содержащей имя файла, к выражению, вычисляемым значением которого является имя файла, или к файловому дескриптору Perl. Если параметр операции не задан, то она тестирует файл, чье имя содержится в специальной переменной $_. Каждая операция проверки атрибута файла возвращает 1, если файл обладает соответствующим атрибутом, пустую строку "" в противном случае и неопределенное значение undef, если указанный в параметре файл не существует.

Несколько слов об алгоритме определения текстовых и двоичных файлов (операции -т и -в). Эти операции анализируют содержимое первого блока файла на наличие "странных" символов - необычных управляющих последовательностей или байтов с установленными старшими битами. Если обнаружено достаточно большое количество подобных символов (больше 30%), то файл считается двоичным, иначе текстовым. Любой файл с пустым первым блоком рассматривается как двоичный.

Если эти операции применяются к файловым дескрипторам Perl, то проверяется содержимое буфера ввода/вывода, а не первого блока файла. Обе эти операции, примененные к файловым дескрипторам, возвращают булево значение Истина, если ввязанный с дескриптором файл пуст или установлен на конец файла.

При выполнении унарных именованных операций проверки файла на самом деле неявно вызывается функция statо, причем результаты ее вычисления кэшируются, что позволяет использовать специальный файловый дескриптор _ для ускорения множественных проверок файла:

If(-s("filename") && -Т J {

^ Что-то делаем для текстовых файлов не нулевого размера.

}

7.5. Операции с каталогами

Как мы отмечали ранее, в UNIX каталоги являются файлами специального формата, помеченными в структурах своих индексных дескрипторов как каталоги (поле rdev). Содержимым блоков данных каталогов является множество пар, состоящих из объекта, содержащегося в каталоге, и числового значения его индексного дескриптора.

Для работы с каталогами в Perl предусмотрены функции открытия, закрытия и чтения содержимого каталога, синтаксис и семантика которых аналогичны синтаксису и семантики соответствующих операций с файлами:

opendir ДЕСКРИПТОР, ИМЯ_КАТАЛОГА; closedir ДЕСКРИПТОР; readdir ДЕСКРИПТОР;

Доступ к содержимому каталога осуществляется, как и в случае с файлом, через создаваемый функцией opendir о дескриптор каталога. Отметим, что для дескрипторов каталогов в таблице символов Perl создается собственное пространство имен. Это означает, что в программе могут существовать, совершенно не конфликтуя между собой, дескрипторы файла и каталога с одинаковыми именами:

open FF, "/usr/out.dat" # Дескриптор файла opendir FF, Vusr" # Дескриптор каталога

Замечание

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

Функция readdir о для открытого каталога в списковом контексте возвращает список имен всех файлов каталога или пустой список, если все имена уже были прочитаны. Эта же функция в скалярном контексте возвращает следующее имя файла каталога или неопределенное значение undef, если были прочитаны все имена файлов.

Функцией rewinddiro текущая позиция в каталоге устанавливается на начало, что позволяет осуществлять повторное чтение имен файлов каталога, не закрывая его. Единственным параметром этой функции является дескриптор открытого каталога.

Программа примера 7.8 проверяет, являются все файлы каталога двоичными (содержимое вложенных каталогов не проверяется).

#! peri -w

opendir FDIR, "/usr/prog"; while($name = readdir FDIR) { next if -d $name; # Каталог

print("$name: двоичный\п") if -B $name; tt Двоичный файл } closedir FDIR;

Функция readdir о возвращает относительное имя файла. Для получения полного имени файла следует создать его в программе самостоятельно. Например, добавить имя проверяемого каталога в примере 7.8:

Print("/usr/prog/$name: двоичныйХп") if -В $name; # Двоичный файл

Для создания нового каталога следует воспользоваться функцией mkdiro, параметрами которой являются имя каталога и режим доступа (восьмеричное число):

mkdir ИМЯ_КАТАЛОГА, РЕЖИМ;

Если задается не полное имя каталога, то он создается в текущем каталоге, устанавливаемом функцией chdirf). Возвращаемым значением, функции создания нового каталога mkdir о является Истина, если каталог создан, и Ложь, если произошла какая-то ошибка. В последнем случае в специальной переменной $! хранится объяснение не выполнения операции создания каталога.

Удалить каталог можно функцией rmdirf) с параметром, содержащим строку с именем каталога. Если параметр не задан, то используется специальная переменная $_. Как и функция создания каталога, эта функция возвращает значение Истина в случае успешного удаления каталога и Ложь в противном случае, записывая в переменную $! объяснение возникшей ошибки.

* * *

В этой главе мы узнали, как получить доступ к содержимому файлов и каталогов через соответствующие дескрипторы. Научились читать и записывать информацию в файлы, создавать и удалять каталоги. Познакомились с большим набором унарных именованных операций для получения информации об атрибутах файлов из полей структуры индексного дескриптора.

Вопросы для самоконтроля

1. Как можно получить доступ к файлу из программы Perl?

2. Перечислите операции, позволяющие читать содержимое файла и записывать в него информацию:

3. Какие существуют режимы открытия файла и чем они отличаются?

4. Что такое режим доступа файла и как его можно изменить в программе Perl?

5. Что такое дескриптор каталога и зачем он нужен?

6. Как можно получить имена всех файлов определённого каталога?

Упражнения

1. Найдите ошибки в программе:

#1 peri -w

$var = (stat "filel.dat");

Open FILE1 ">filel.dat";

print FILE1, "Длина файла: " . $var . "\n";

2. Напишите программу, которая удаляет каталог, имя которого передается через командную строку. Если сценарий запущен без параметров, то предусмотрите отображение сообщения о правильном синтаксисе его вызова. (Указание: сначала следует удалить все файлы из нижележащих каталогов, если таковые имеются.)

3. Напишите программу копирования одного файла в другой. Предусмотрите ввод имен файлов как через командную строку, так и с экрана монитора.

4. Напишите программу копирования содержимого одного каталога в другой каталог. Предусмотрите ввод имен каталогов как через командную строку, так и с экрана монитора.

5. Напишите программу чтения строки текстового файла с заданным номером. Предусмотрите случаи, когда номер заданной строки превосходит количество строк в файле. Если номер строки отрицательный, то следует прочитать все строки, начиная со строки с номером, равным абсолютному значению введенного отрицательного значения.



Главная страница » PERL » Циклы и ветвления.

Файловый ввод и вывод.

ФАЙЛОВЫЙ ВВОД И ВЫВОД Perl специально разрабатывался для того, чтобы служить адек- ватным средством для чтения и записи в текстовые файлы. Тем не менее, как вы узнаете далее, Perl выполняет функции по произ- вольному доступу и вводу-выводу бинарных файлов. Операции по ра- боте с файлами требуют указатель файла (file handle), который яв- ляется переменной, соответствующей конкретному файлу. По умолча- нию каждый скрипт на языке Perl имеет три стандартных указателя, которые Perl автоматически открывает при запуске скрипта: STDJN, STDOUT, STDERR. Эти три стандартных указателя отвечают стандар- тным потокам STDIN, STDOUT, STDERR языка программирования С. Кро- ме того, скрипт языка Perl может открыть дополнительные указате- ли для других специфических файлов. ОТКРЫТИЕ ФАЙЛОВ И ДРУГИХ ПОТОКОВ Для того чтобы скрипт использовал файл, он должен вызвать функцию open. Она имеет следующий вид: open(FileHandle[, FileName]) В отличие от функции open библиотеки времени выполнения язы- ка С, функция open языка Perl не содержит параметра mode в вызо- ве функции. Perl определяет режим (mode) открытия файла, основы- ваясь на имени файла. Таблица 12.3 иллюстрирует связь режима от- крытия файла и имени файла. Имя файла Операция Открыть файл только для чтения (аналогично функции fopen) FILE> Создать файл для записи (как функции fopen) >FILE> Открыть файл для добавления в его конец (как функции fopen) FILE> Создать файл для чтения/записи (как функции fopen) Открыть канал из процесса, исполняющего команду Открыть канал процессу, исполняющему команду Табл. 12.3. Соглашение об именах и режимах доступа файлов языка Perl Примечание: Режим канального (pipe) потока может существовать не на всех системах. Если в вызове функции open опущено имя файла, то Perl подра- зумевает, что имя файла содержится в строковой переменной $FileHandle. Когда скрипт завершил использование файла, он закры- вает его, используя функцию close, как показано ниже: close(FileHandle); фрагмент программы иллюстрирует использование функций open и close: open(InFile, "test.dat") || die; # открываем для чтения # test.dat open(OutFile, ">test.dat") || die; # создаём test.dat $AuxFile = ">>test.dat"; open(Aux, $AuxFile) || die; # открывает для дополнения # test.dat close(InFile); close(OutFile); close(Aux); Обратите внимание, что указатели файлов не имеют обычных од- носимвольных префиксов. Как вы узнаете далее, скрипты языка Perl могут хранить имена указателей в виде строк скалярных переменных и передавать указатель любой функции, которая может их обрабаты- вать. При необходимости Perl выполняет конвертацию значений. В операционной системе MS-DOS Perl поддерживает дополни- тельную функцию, которая называется hinmode и позволяет файлово- му вводу/выводу переключаться между текстовым и бинарным режима- ми. В большинстве же систем различие между текстовым и бинарным режимами не имеет значения. Однако для операционной системы MS-DOS символ новой строки представляет собой последовательность из двух символов (CR+LF). Поскольку большинство программ не ожи- дают встретить два символа в конце строки, то система ввода/выво- да должна выполнить преобразование. Для того чтобы можно было ис- пользовать функцию binmode, соответствующий указатель может быть открыт. Функция binmode имеет следующий формат: binmode(FileHandle); ПОСТРОЧНОЕ ЧТЕНИЕ И ЗАПИСЬ ДАННЫХ Простейшим способом для чтения скриптом строки из файла слу- жит использование оператора. В языке Perl указа- тель файла, окруженный треугольными скобками, становится симво- лом ввода (input-symbol). Например, следующий фрагмент программы иллюстрирует использование символа ввода для чтения и вывода на экран содержимого файла Test.dat. open(InFile, "Test.dat") || die; while ($line =) { print $line; # Выведет строку из файла } close(InFile); Когда символ ввода достигает конца файла, он возвращает зна- чение false, которое в данном случае заканчивает выполнение цик- ла while. Существует специальный (пустой) символ ввода, обозна- чаемый, который имеет весьма специальное, но полезное примене- ние. В первый раз, когда скрипт использует пустой символ ввода, он анализирует аргументы командной строки. Если строка @ARGV является пустой, то входной символ читает из STDIN. Если вмес- то того @ARGV не пуста, то Perl открывает первый из файлов, ука- занных в переменной @ARGV, и читает содержимое файла. Когда Perl заканчивает обработку одного файла, он приступает к следующему. После того как скрипт прочитал все файлы, символ возвращает значение false. Скрипты языка Perl также могут использовать символ ввода для чтения всего содержимого файла в массив так, что каждая строка файла становится элементом массива. Например, следующая инструк- ция читает из файла STDIN в массив @lines: @lines = ; Запись данных в файл также достаточно проста. Фактически вы это делали всякий раз, когда использовали функцию print. Полный формат функции print имеет следующий вид: print List; Если функция print не получает в качестве аргумента указате- ля файла, то она посылает вывод в STDOUT. Следующий фрагмент программы иллюстрирует использование функции print для добавле- ния данных в выходной файл: open(LogFile, ">>logfile.dat") || die; ############## ($m, $d, $y) = (localtime(time)) ; print LogFile "Captain"s log, Stardate ++m$/$d/$y\n"; close(LogFile); Примечание: Указатель файла и выходной список не разделяются за- пятой. ЧТЕНИЕ И ЗАПИСЬ БЛОКОВ ДАННЫХ Программисты часто рассматривают текстовые файлы как тексто- вые потоки просто потому, что один символ следует за другим до маркера конца файла. Если скрипт должен работать с файлом, кото- рый ориентирован на работу с блоками, а не потоками, то скрипт может использовать функции sysread и syswrite для обработки фик- сированных блоков данных. Функции sysread и syswrite имеют сле- дующие форматы: $result = sysread(FileHandle, $Var, Length[, Offset]); $result = syswrite(FileHandle, $Var, Length[, Offset]); Если в вызове функций указывается сдвиг от начала файла (Offset), то функции выполнят поиск места, с которого они начнут операции ввода/вывода. Функции sysread и syswrite обе передают данные, используя скалярную переменную строкового типа. Пос- кольку функции обрабатывают фиксированные блоки памяти, то дан- ные могут содержать бинарные значения, включая нули и маркеры конца файла. Если в вызове функции указывается сдвиг от начала файла (Offset), то функция выполняет поиск места в файле, с кото- рого начинает выполнять операции ввода/вывода. Если вы работаете с блоками данных, то скрипты могут также использовать следующие функции ввода/вывода: $result = seek(FileHandle, Position, Base); $result = tell(FileHandle); $result = eof(FileHandle); Функция seek работает в точности так же, как fseek - фун- кция библиотеки времени выполнения языка С. Параметр Position за- дает позицию относительно начала отсчета, которая в свою очередь задается параметром Base следующим образом: ? 0 Поиск от начала файлов? 1 Поиск от текущей позиции? 2 Поиск от конца файла Функция tell языка Perl работает в точности так же, как фик- ция ftell библиотеки времени выполнения языка С. Эта функция воз- вращает текущую позицию в файле, с которой выполняются операции чтения или записи. Наконец, функция eof, так же как и функция feof языка С, возвращает значение или, кото- рое скрипт может использовать для определения достижения конца файла. ОБРАБОТКА БИНАРНЫХ ДАННЫХ Хотя Perl ориентирован в первую очередь на обработку текста, он также может обрабатывать бинарные данные. Скрипты могут пере- мещать бинарные данные частями, используя строковые переменные, и выполнять байтовые операции ввода/вывода, используя функции sysread и syswrite. Однако для того, чтобы выполнить что-нибудь с данными, скрипт вынужден конвертировать данные в свои скалярные форматы. ХРАНЕНИЕ БИНАРНЫХ ДАННЫХ Когда скрипт на языке Perl читает блок бинарных данных, ис- пользуя функцию sysread, он помещает эти бинарные данные в ска- лярную строковую переменную. Perl не заботится о том, что это за данные, содержат ли они нули или значения, не являющиеся ASCII-символами. В пределах символьной строки Perl принимает бай- ты как байты. В отличие от языка С, Perl не использует строк, оканчивающихся нуль-символом. Если данные соответствуют кодовой таблице ASCII, то скрипт может их обрабатывать, как любой текст. Но если данные представляют собой бинарные величины, то скрипт обязан распаковать их перед тем, как Perl сможет обработать эти данные. РАСПАКОВКА СТРОК БИНАРНЫХ ДАННЫХ В ПЕРЕМЕННЫЕ ЯЗЫКА PERL Для того чтобы скрипт получил доступ к бинарным данным, он должен распаковать их, перейдя в свой скалярный формат. Скрипты Perl распаковывают данные, используя функцию unpack, которая имеет следующий формат: $result = unpack(Template, Expression); Expression является обычной строковой переменной, которая содержит бинарные данные, прочитанные функцией sysread, но может быть также выражением, которое необходимо интерпретировать как строку. Template представляет собой символьную строку-шаблон, описывающую, как интерпретировать значения в операнде Expression. Следующий фрагмент программы иллюстрирует использование функции unpack: ($r, $g, $b) = unpack("C3", $color);# распакует в 3 символа @longwords = unpack("L*", $data); # распакует в список длинных # слов @stuff = unpack("S2L", $bin); # распакует в 2 shorts и long Каждый символ шаблона может сопровождаться числом, указываю- щим, сколько раз использовать этот символ. Если вместо числа стоит звездочка (*), то операция будет выполняться для всех ос- тающихся данных в строке. Если число не поставлено, то она выпол- няется однократно. Скрипт может поместить любое число символов шаблона в строку Template. В таблице 12.4 перечисляются символы, входящие в строковый параметр Template вместе с описанием влия- ния каждого из них на выполнение функции unpack. Символ шаблона Описание a Строка ASCII без нулевого символа А Строка ASCII без нулевого символа b Битовая строка (младший бит идет первым) В Битовая строка (старший бит идет первым) с Однобайтовый символ со знаком С Однобайтовый символ без знака d Значение с плавающей запятой, двойной точности f Значение с плавающей запятой, одинарной точности шаблона h Строка шестнадцатиричных значений (младшие разряды идут первыми) Н Строка шестнадцатиричных значений (старшие разряды идут первыми) i Целое со знаком I Целое без знака l Целое со знаком типа long L То же, только без знака n Короткое целое N Длинное целое p Указатель на строку s Короткое целое со знаком S Короткое целое без знака u Раскодировка строки v Короткое целое V Длинное целое х Пропустить вперед один байт Х Пропустить назад один байт @ Перейти на указанную позицию в строке Табл. 12.4. Символы шаблона УПАКОВКА ДАННЫХ В БИНАРНЫЕ СТРОКИ Для вывода бинарных данных скрипт должен запаковать скаляр- ные величины в строки бинарных символов. Для этого используется функция pack, формат которой указан ниже: $result = pack(Template, List); Следующий фрагмент программы иллюстрирует использование фун- кции pack: $color = pack("C3", $r, $g, $b); $data = pack("L*", @longword); $bin = pack("S2L", @stuff); Функция pack использует те же самые символы шаблона, что и функция unpack, за исключением символов а. А, и, х, X, @. РАБОТА С КАТАЛОГАМИ Perl предоставляет не только широкий набор функций для обра- ботки файлов, но также несколько очень удобных функций для скани- рования каталогов. В следующих разделах мы рассмотрим некоторые из основных функций для работы с каталогами в деталях. ОТКРЫТИЕ, ЧТЕНИЕ И ЗАКРЫТИЕ КАТАЛОГОВ Скрипты на языке Perl позволяют открывать и читать содержи- мое файлов. Точно так же эти скрипты открывают каталоги и читают имена содержащихся в них слайдов. Для открытия каталога скрипты используют функцию opendir, передавая указатель каталога и путь к нему. Для чтения списка файлов, содержащихся в каталоге, скрипт использует функцию readdir. Наконец, для закрытия каталога ис- пользуется функция closedir. Следующий фрагмент программы иллюс- трирует использование функции readdir для того, чтобы вывести на экран список файлов в текущем каталоге: opendir(Dir, $INC) || die; while ($file = readdir(Dir)) { print "$file \n" } closedir(Dir); В этом фрагменте используется переменная $INC Format, List); $result = sprintf(Format, List); По умолчанию функция printf посылает форматированный выход на стандартный выход STDIO, а функция sprintf возвращает формати- рованную строку. В обоих случаях формат строк почти аналогичен функциям языка С, исключая только отсутствие поддержки функциями языка Perl спецификатора длины (*). Следующий фрагмент программы иллюстрирует использование функций printf и sprintf. $precision = 2; $pi = 3.1415; printf("%.2f\n", $pi); # выведет 3.14 printf("%.${precision}f", $pi); # выведет 3.14 ВЫЗОВ ВНЕШНИХ ПРОГРАММ ИЗ СКРИПТА НА ЯЗЫКЕ PERL Будучи в известном смысле заменой скриптов shell, Perl обес- печивает поддержку системного взаимодействия, включая вызов внеш- них программ. В следующих разделах рассматривается несколько спо- собов вызова внешних программ из скриптов Perl. Имейте, однако, в виду, что позволяя скриптам выполнять системные команды, вы тем самым открываете бреши в системе безопасности вашего узла. При- держивайтесь общего правила не выполнять внешних команд из скрип- та на языке Perl. Тем не менее, если вы вынуждены выполнять внеш- ние команды из скрипта, то можете использовать для этих целей встроенные функции system, exec или fork. РЕГУЛЯРНЫЕ ВЫРАЖЕНИЯ На протяжении этой главы вы познакомились с примерами фун- кций для обработки строк. Многие из них основаны на концепции ре- гулярных выражений. Как вы видите из следующих разделов, скрипты языка Perl широко используют регулярные выражения для обработки текста. Если регулярные выражения внове для вас, то не беспокой- тесь. Спустя короткое время после того, как вы познакомитесь с несколькими разделами этой главы, регулярные выражения станут для вас просты и понятны. ОБЗОР РЕГУЛЯРНЫХ ВЫРАЖЕНИЙ Регулярные выражения являются причудливым термином, возник- шим в компьютерной науке и служащим для обозначения образца, сос- тоящего из символов. Скрипты на языке Perl используют символьные образцы просто для того, чтобы провести анализ входных данных, расщепляя их на части. Часто скрипт может проанализировать вход- ные данные, основываясь на пробелах, запятых, символах табуляции и других разделителях. Но когда входные данные имеют произ- вольный формат то лучше всего с такой задачей справляются регу- лярные выражения. СИНТАКСИС РЕГУЛЯРНЫХ ВЫРАЖЕНИЙ Для сокращения размеров регулярных выражений, Perl ис- пользует специальные символы. Таблица 12.6 содержит список неко- торых из символов, используемых скриптами языка Perl в регуляр- ных выражениях. Символ Описание. Соответствует любому символу (за исключением символа новой строки) (..) Группирует последовательность элементов + Удовлетворяет предыдущему образцу один или большее количество раз? Удовлетворяет образцу нуль или один раз * Соответствует образцу один или нуль раз [...] Соответствует символу из заданного множества [^...] Соответствует символу из множества, полученного отрицанием (...|...|...) Соответствует одной из альтернатив ^ Соответствует началу строки $ Соответствует образцу в конце строки {n, m} Соответствует образцу от n до m раз {n} Соответствует образцу точно n раз {n,} Соответствует образцу минимум n раз \n\t etc. Соответствует знаку новой линии, символу табуляции и т. д. \b Соответствует на границе слова \В Соответствует внутри границ слова \d Соответствует цифре \D Соответствует не цифре \s Соответствует пробелу \S Соответствует не пробелу \w Соответствует букве или цифре \W Соответствует символу, не являющемуся ни буквой, ни цифрой Табл. 12.6. Символы, используемые в регумрных выражениях Perl помещает регулярные выражения (образцы, шаблоны) в слэ- ши, т. е. в наклонные черточки, например, в виде /pattern/. Сле- дующий фрагмент программы иллюстрирует регулярные выражения язы- ка Perl: # the following regular expressions are true if: /ig/ # string contains "ig" /(b|d|f)ig/ # string contains "big", "dig" or "fig" /+/ # string contains a number /*/ # string contains an identifier Если эти выражения кажутся вам бессмысленными, не беспокой- тесь. В этой главе мы собираемся рассмотреть несколько регуляр- ных выражений. Сейчас просто запомните, что Perl помещает регу- лярные выражения между двумя наклонными чертами-слэшами, как по- казано выше. ИСПОЛЬЗОВАНИЕ РЕГУЛЯРНЫХ ВЫРАЖЕНИЙ ДЛЯ ПОИСКА ПО КЛЮЧЕВЫМ СЛОВАМ Скрипты языка Perl используют регулярные выражения для того, чтобы упростить сравнение строк. Для того чтобы проверить, содер- жит ли строка заданный образец, скрипт может использовать регу- лярные выражения следующим образом: if ($str =~ /pattern/) В данном случае регулярные выражения принимают значение, если образец найден в строке ($str). Если строка по со- держит образца, то выражение возвращает значение. Напри- мер, следующее выражение проверяет, содержит ли строка текст Web Programming: if ($str =~ /Web Programming/) Для того, чтобы проверить полное совпадение, выражение дол- жно привязать сравнение к началу и концу строки. Например, сле- дующее выражение имеет значением величину, если и только если переменная $str принимает одно из трех значений: ,) : ($str =~ /^ba(na) {2,4}$/) Аналогичным образом, следующее выражение истинно тогда и только тогда, когда переменная $str содержит слово и не яв- ляется частью другого слова, такого как. ($str =~ /\bthe\b/) ИСПОЛЬЗОВАНИЕ РЕГУЛЯРНЫХ ВЫРАЖЕНИЙ ДЛЯ АНАЛИЗА ВХОДНЫХ ДАННЫХ По мере усложнения ваших скриптов Perl возникнет много слу- чаев, когда вы захотите узнать больше, чем просто проверить, сов- падает ли образец со строкой или нет. Например, может потребо- ваться, чтобы скрипт извлек определенное значение строки. Используя символы группировки () внутри регулярного выражения, скрипт может извлечь соответствующие образцу значения из строки и сформировать из них список. Например, следующий фрагмент програм- мы использует регулярные выражения для того, чтобы извлечь меся- цы, дни и годы из списка: $str = " January 1, 1997, "; ($m, $d, $y) = $str =~ /\s*(\S*)\s + (\d+)\D + (\d{4})/; В этом случае можно прочитать регулярные выражения следую- щим образом: ? Пропустить вначале любой специальный символ; ? записать все символы, не являющиеся специальными, в переменную $m (переменная для обозначения месяцев); ? пропустить специальный символ; ? поместить все цифры в переменную $d (переменная для записи дней); ? пропустить все знаки, не являющиеся цифрами; ? записать четыре цифры в переменную $у (переменная для обозначения лет). Perl поддерживает также другую форму сравнения с образцом, использующую оператор (=~), который добавляет отрицание результа- та: (!~). Этот оператор эквивалентен выражению!($str =~/pattern/). РЕГУЛЯРНЫЕ ВЫРАЖЕНИЯ ДЛЯ ПОИСКА И ЗАМЕНЫ СТРОК До сих пор вы использовали операторы, проверяющие на соот- ветствие образцу. Оказывается, Perl поддерживает два других регу- лярных выражения, которые модифицируют проверяемую строковую пе- ременную. В записанной дальше инструкции Perl замещает часть строки, которая соответствует образцу, на заданную строку: $str =~ s/pattern/replacement/; Например, следующая инструкция заменит слово на: $str =~ s/\bcolour\b/color/; Небольшая модификация позволяет заменить все слова на: $str =~ s/\bcolour\b/color/g; В данном случае g в конце выражения указывает языку Perl на необходимость глобальной подстановки. Используя суффикс i, можно задать выполнение поиска с учё- том регистра. В противоположность простой проверке на соответ- ствие образцу, следующее выражение осуществляет также и замену: $str =~ tr/SearchList/ReplacementList/; Например, замена всех символов нижнего регистра теми же сим- волами верхнею регистра может быть осуществлена таким образом: $str =~ tr/a-z/A-Z/; # меняет регистр, с нижнего на верхний Проанализируйте сами следующий пример: $letters = "abcde"; print "$letters\n" # Выведет abcde $letters =~ tr/a-z/A-Z/; print "$letters\n" # Выведет ABCDE РЕЗЮМЕ В этой главе рассмотрено введение в программирование на язы- ке Perl. Используя рассмотренные здесь концепции, можно писать сложные скрипты CGI на языке Perl. Следующая глава окажет вам по- мощь в получении навыков в создании CGI-скриптов на языке Perl, которые можно запустить на собственном сервере. Прежде чем следо- вать далее, удостоверьтесь, что вы понимаете следующие ключевые концепции: ? Perl представляет собой интерпретируемый язык программирования, который используется программистами для написания скриптов для Web и Internet. ? Конструкции языка Perl во многих отношениях напоминают анало- гичные конструкции языка С, однако Perl предлагает много дополни- тельных возможностей, в особенности для обработки строк и файлов, которые трудно отыскать в языке С. ? Perl является основным языком для написания CGI-программ для Web и Internet, в первую очередь благодаря своей гибкости, ком- пактному стилю и обеспечению высокой безопасности. ВАЖНЕЙШИЕ WEB-УЗЛЫ С ИНФОРМАЦИЕЙ О PERL Следующие узлы Web помогут отыскать информацию об интересую- щих вас деталях относительно языка Pcrl, скриптов на нем, а так- же специальной информации о ресурсах языка Perl 5 и его библиоте- ках. Используйте эти Web-узлы в качестве отправной точки вашего поиска. http://www.shareware.com/top/Source-Code-table.html - SHAREWARE.COM - самые популярные файлы с кодами программ. http://www.inxpress.net:80/~moewes/computer/perl95.html - Perl для Windows 95. http://www.mit.edu:8001/perl/perlapi.html - PERLAPI http://www.genome.wi.mit.edu:80/ftp/pub/software/WWW/cgi_docs.html - CGI.pm - Библиотека Perl5.CGI http://www.metronet.com/0/perlinfo/perl5/manual/perl.html - PERL http://www.teleport.com/~rootbeer/perl.html - Ссылки для изучающих Perl

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

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

Для чтения из файла используется операция "ромб" о, которой в качестве операнда передается дескриптор файла. В этой главе мы не будем обсуждать ввод из файла через его дескриптор, отнеся рассмотрение этого вопроса в следующую главу, полностью посвященную работе с файлами. Здесь мы расскажем о том, как работает операция "ромб" в случае отсутствия операнда, представляющего дескриптор файла. В этом случае эта операция может читать записи из стандартного файла ввода STDIN или получать информацию, передаваемую программе через командную строку.

Для отображения в стандартный файл вывода STDOUT используется уже знакомая нам функция print, которая, однако, может выводить информацию и в файл, определенный своим дескриптором.

6.1. Операция ввода команды

Заключенная в обратные кавычки " " строка символов является всего лишь удобной формой записи операции ввода команды операционной системы qx{}, с которой мы уже знакомы (см. главу 4).

Когда интерпретатор Perl встречает строковый литерал в обратных кавычках, он осуществляет подстановку в нее значений скалярных переменных и переменных массивов и передает получившуюся строку, как команду, на выполнение операционной системе. Последняя выполняет ее и возвращает в строковый литерал результаты вывода команды на стандартное устройство вывода, которым обычно является экран монитора. В связи с таким "поведением" строкового литерала в обратных кавычках его иногда называют псевдолитералом.

Операция ввода команды различает скалярный и списковый контексты, в которых она может выполняться. В скалярном контексте возвращается одна строка, содержащая весь вывод на экран монитора выполненной команды, включая символы новой строки в случае многострочного вывода. В списковом контексте возвращается список значений, каждое из которых содержит одну строку вывода. Пример 6.1 демонстрирует использование операции ввода команды в соответствующих контекстах.

#! peri -w

$command = "dir";

$scalar = ~$command"; # Скалярный контекст. ,

@list = "$command~; # Списковый контекст.

Print $scalar;

Print $list, $list, $list;

Теперь, в отличие от примера 6.1, элемент массива $iist содержит вывод команды до следующей встретившейся последовательности СИМВОЛОВ "<КАТАЛОГ>" И Т. Д.

Команда, содержащаяся в псевдолитерале, выполняется всякий раз, как вычисляется этот псевдолитерал. Встроенная переменная $? содержит числовое значение состояния выполненной команды.

(Об интерпретации значений встроенной переменной $ ? см. главу 14.)

Хотим обратить внимание читателя еще раз на тот факт, что операция ввода команды возвращает вывод на стандартное устройство вывода операционной системы. При выполнении команды можно направить ее вывод на другое устройство, например, в файл. Для этого в строке после имени команды и всех необходимых для ее выполнения параметров следует задать символ ">", после которого ввести имя файла. В этом случае на экран монитора ничего выводиться не будет, а следовательно и ничего не будет возвращаться в псевдолитерал, т. е. после выполнения такой команды псевдолитерал будет содержать неопределенное значение (пример 6.3).

#! peri -w - $/ = "<КАТАЛОГ>";

$list = "dir >file.dat~; # Вывод осуществляется в файл file.dat print $list; # Оператор ничего не напечатает!

6.2. Операция о

Для нашего читателя эта операция не является совсем уж новой. Несколько слов о ней было сказано в главе 4; в некоторых примерах мы ее использовали для ввода пользователем данных в программу Perl. Основное ее назначение - прочитать строку из файла, дескриптор которого является операндом этой операции. (Операнд операции о расположен внутри угловых скобок.) Мы не будем сейчас объяснять, что такое дескриптор файла, зачем он нужен и какую функцию выполняет в программах Perl. Эти вопросы будут нами подробно рассмотрены в следующей главе, посвященной работе с файлами. Здесь же мы остановимся на специальном случае использования этой операции - операции с пустым операндом о. В этом случае ввод осуществляется или из стандартного файла ввода, или из каждого файла, перечисленного в командной строке при запуске программы Perl. Но прежде чем перейти к описанию функционирования операции ввода с пустым операндом, остановимся на некоторых понятиях, необходимых для понимания дальнейшего.

Для обеспечения единообразия работы программ Perl в разных операционных системах при их запуске открывается несколько стандартных файлов. Один из них предназначен для ввода данных в программу и связан со стандартным устройством ввода - клавиатурой. Этот файл и называется стандартным файлом ввода и имеет дескриптор STDIN. Для вывода информации из программы создается стандартный файл вывода, также связанный со стандартным устройством вывода операционной системы, которым является экран монитора компьютера. Этому стандартному файлу назначается дескриптор STDOUT. Для отображения разнообразных сообщений о возникающих в процессе выполнения программы ошибках создается стандартный файл ошибок, который связан со стандартным устройством вывода. Этот файл имеет дескриптор STDERR. Эти файлы не надо создавать и открывать - они уже существуют, когда наша программа начинает выполняться. Иногда их называют предопределенными файлами ввода/вывода. Таким образом, если мы, например, говорим о том, что ввод осуществляется из стандартного файла (или стандартного устройства), мы имеем в виду стандартный файл ввода с дескриптором STDIN.

При запуске программы Perl в системе UNIX или из командной строки Windows ей можно передать параметры. Эти параметры задаются после имени файла, содержащего программу Perl, и отделяются от него и друг от друга пробелами:

peri program.pl parl par2 рагЗ

Параметрами могут быть ключи (обычно символ с лидирующим дефисом, например, -v), которые устанавливают определенные режимы работы программы, или имена файлов, содержимое которых должна обработать программа. Все передаваемые в программу параметры сохраняются в специальном встроенном массиве @ARGV. Если не передается ни одного параметра, то этот массив пуст.

Операция о без операнда, употребленная в циклах while и for, при первом своем вычислении проверяет, пуст ли массив @ARGV. Если он пуст, то в первый элемент этого массива $ARGV[O] заносится символ "-" и операция ожидает ввода пользователя из стандартного файла ввода STDIN. Если массив @ARGV не пуст, то он содержит параметры, переданные программе при ее запуске. Операция о трактует каждый из них как имя файла и в цикле передает в программу последовательно все строки всех файлов, указанных в командной строке. Рассмотрим простейшую программу (пример 6.4), состоящую из одного цикла while с операцией <>, и рассмотрим ее поведение при разных способах запуска.

i! peri -w

While ($line = <>) {

print $line; }

При ее запуске без параметров она будет ожидать ввода пользователя с клавиатуры и в цикле распечатывать вводимые им строки, пока пользователь не завершит ввод комбинацией клавиш +, интерпретируемой как конец файла.

Если при запуске передать ей имя файла, например, файла, содержащего саму же программу,

peri examp6_4.pi examp6_4.pi

То программа примера 6.4 распечатает его содержимое:

F! peri -w

While ($line = <>) {

Print $line; }

Если эту же программу запустить, задав в командной строке дважды имя файла программы,

peri examp6_4.pl examp6_4.pl examp6_4.pl

То программа дважды распечатает свой собственный текст.

При выполнении операции ввода из файла встроенная переменная $. на каждом шаге цикла хранит номер прочитанной строки файла. В случае задания нескольких имен файлов в командной строке при последовательном вводе их строк операцией о эта переменная продолжает увеличивать свое значение при переходе на чтение строк очередного файла, т. е. она рассматривает содержимое всех файлов как один-единственный файл.

Операцию о и массив @ARGV можно совместно использовать для ввода в программу содержимого нескольких файлов, не связывая их с заданием имен файлов в командной строке. В любом месте программы перед первым использованием в цикле операции ввода <> можно в массив SARGV занести имена файлов, содержимое которых необходимо обработать:

@ARGV = ("filel.dat", "file2.dat", "file3.dat"); for (;<>;) {

Операторы обработки строк файлов }

Этот фрагмент программы в цикле for последовательно обработает строки трех файлов filel.dat, file2.dat и file3.dat. Здесь же продемонстрирована еще одна интересная особенность операции ввода о. Обычно прочитанная этой операцией строка присваивается скалярной переменной, как это происходило в примере 6.4, но если эта операция одна представляет выражение условия цикла, то результат ее выполнения сохраняется в специальной встроенной переменной $_. Цикл while программы примера 6.4 можно записать и так:

While (<>) (print;

}

Здесь также используется то обстоятельство, что функция print без параметров по умолчанию выводит содержимое переменной $_.

Если мы хотим передать в программу некоторые ключи, устанавливающие режим ее работы, то в начале программы следует поместить цикл, который проверяет содержимое массива @ARGV на наличие ключей в командной строке вызова программы. Один из способов подобной проверки приводится в примере 6.5, где предполагается, что программе могут быть переданы ключи

D, -s И -е.

f! peri -w

while ($_ = $ARGV, / A -/) {

if(/ A -d/) { print $ARGV,"\n";}

if(/ A -s/) { print $ARGV-, "\n"; }

1£(/ Л -е/) { print $ARGV,"\n"; }

shift; }

При вычислении выражения условия цикла while осуществляется присваивание переменной $_ значения первого элемента массива @ARGV и проверка присутствия дефиса "-" в качестве первого символа содержимого этой переменной (операция / Л -/). Операторы if проверяют содержимое переменной $_ на соответствие известным ключам и отображают их. (В реальных программах в этих операторах обычно определяют некоторые переменные, которые в дальнейшем используются для выполнения действий, присущих соответствующим ключам.) Функция shift удаляет из массива @ARGV первое значение, сдвигая оставшиеся в нем элементы на одну позицию влево: второй становится первым, третий вторым и т. д. Цикл повторяется до тех пор, пока переданные через командную строку параметры начинаются с дефиса. Еще одно применение операции <> связано с получением в программе имен файлов определенного каталога, удовлетворяющих заданному шаблону. Если в качестве операнда этой операции используется шаблон имен файлов, то в скалярном контексте она возвращает первое найденное имя файла в текущем каталоге, в списковом контексте - список имен файлов, удовлетворяющих заданному шаблону. (В шаблоне можно использовать метасимволы: * для произвольной цепочки символов, ? для произвольного одиночного символа.) Если в каталоге не найдены файлы с именами, удовлетворяющими шаблону, то операция возвращает неопределенное значение. Например, выполнение следующей операции

$first = <*.pl>;

Приведет к сохранению в переменной $ first имени первого файла из списка всех файлов текущего каталога с расширением pi, если таковые файлы в каталоге есть, иначе эта переменная будет иметь неопределенное значение. В списке файлы упорядочены в алфавитном порядке. Эта же операция в списковом контексте

Gfiles = <*.pl>;

Возвращает список всех файлов с расширением pi. После выполнения этой операции элементы массива @files содержат имена всех файлов с расширением pi.

Если при задании шаблона файла явно указать каталог, то эта операция возвратит список файлов из указанного каталога, имена которых удовлетворяют заданному шаблону. Например, операция

@files = ;

Сохранит в массиве @flies имена всех файлов каталога /peri с расширением pi.

При использовании этой операции в выражении условия цикла while или for она последовательно на каждом шаге цикла возвращает очередное имя файла, удовлетворяющее заданному шаблону:

While ($file = <*.pl>) (

print "$file\n"; }

Употребленная в выражении условия самостоятельно, эта операция возвращает очередное имя файла в переменной $_. Например, предыдущий фрагмент можно переписать следующим образом:

While (<*.pl>) {

Print $_, "\п"; }

Операция получения имен файлов, соответствующих заданному шаблону, реализуется с помощью внутренней функции glob, единственным параметром которой является шаблон имен файлов. Эту функцию можно использовать самостоятельно для получения соответствующих имен файлов:

@scripts = glob "*.pl";

В скалярном контексте она возвращает имя первого файла, удовлетворяющего заданному шаблону, в списковом - список имен всех файлов. Употребленная без параметра, она использует в качестве параметра специальную переменную $_.

6.3. Функция print

Функция print наиболее часто используемая функция для вывода информации из сценария Perl. Ее синтаксис имеет следующий вид:

print ДЕСКРИПТОР СПИСОК;

Здесь ДЕСКРИПТОР представляет дескриптор файла, в который функция выводит строковые данные, представленные списком вывода список. Он может состоять из переменных, элементов массивов и выражений, вычисляемых как строковые данные. Дескриптор файла создается функцией open 0,0 которой мы поговорим в следующей главе. Он может быть опущен, и в этом случае вывод осуществляется в стандартный файл вывода STDOUT, если только функцией select о не выбран другой файл вывода по умолчанию. Как уже отмечалось ранее, обычно стандартное устройство вывода - экран монитора компьютера.

Функция print при выводе своего списка не завершает его символом новой строки "\п". Это означает, что следующая функция print начнет вывод на экран непосредственно после последнего выведенного предыдущей функцией print символа. Если такое поведение не желательно, то следует список вывода каждой функции print явно завершать строкой, содержащей символ новой строки, или включать его последним символом последнего элемента списка вывода. Пример 6.6 демонстрирует вывод с помощью функции print.

#! peri -w

print "String 1:";

Print "String 2:\n";

Print "String 3:", "\n";

print STDOUT "String 4:\n";

print FILEOUT "String 4:\n";

Вывод первых четырех функций print примера 6.6 представлен ниже:

String I:String 2: String 3: String 4:

Вторая функция print начинает свой вывод на той же строке, на которой завершила вывод первая функция, в которой в списке вывода нет символа перехода на новую строку. В четвертой функции явно указан дескриптор стандартного файла вывода STDOUT. Относительно пятой функции скажем, что она ничего ни в какой файл, определенный дескриптором FILEOUT, не выведет, так как с этим дескриптором не связан никакой файл. Для этого следовало бы до выполнения последней функции print открыть файл функцией open о и связать с ним дескриптор FILEOUT. Мы отложим эти вопросы до следующей главы.

Функция print, как и большинство других функций, определенных в языке Perl, является списковой операцией, и все элементы списка вывода вычисляются в списковом контексте. Это обстоятельство следует учитывать при использовании в качестве элементов списка вывода выражений с вызовами подпрограмм.

Все, что было сказано относительно списковых операций и их использования в качестве термов выражений в главе 4, относится, естественно, и к функции print. Если ее параметры, включая дескриптор файла, заключены в круглые скобки, то такая синтаксическая конструкция считается термом и в выражении имеет наивысший приоритет вычисления. Например, следующий оператор

Print ($m + $n) ** 2;

Напечатает сумму значений переменных $т и $п, а не их сумму, возведенную в квадрат. Компилятор peri, обнаружив после лексемы print левую круглую скобку, найдет правую круглую скобку и будет рассматривать их содержимое как список параметров функции print. А так как такая конструкция есть терм, то сначала будет выполнена операция печати суммы значений переменных, а потом результат этой операции (Истина =1) будет возведен в квадрат. Добавление необязательного дескриптора стандартного файла вывода STDOUT исправит подобную ошибку:

print STDOUT ($m + $n) ** 2; # Выведет ($m + $n) ** 2

Если в функции печати print не задан список вывода, то она по умолчанию выводит содержимое специальной переменной $_ в файл, определенный параметром ДЕСКРИПТОР:

print; # Выводится содержимое переменной $_ на экран монитора, print STDOUT; # Эквивалентен предыдущему оператору, print FILEOUT; # Выводится содержимое переменной $_ в файл # с дескриптором FILEOUT

* * *

В этой главе мы познакомились с основными возможностями ввода/вывода, предоставляемыми языком Perl. Узнали, как легко и просто можно выполнить команду операционной системы и получить результаты ее вывода на экран монитора непосредственно в программу Perl. Операция <> позволяет не только считывать записи внешних файлов, но и автоматически обрабатывать содержимое нескольких файлов, заданных в командной строке при запуске сценария Perl. Эта же операция позволяет осуществлять поиск файлов, чьи имена удовлетворяют заданному шаблону. Для вывода информации из программы используется функция print о, которая может выводить ее не только на экран монитора (стандартное устройство вывода), но также и во внешний файл.

Вопросы для самоконтроля

1. Каким образом можно получить результаты выполнения команды операционной системы в программе Perl?

3. Где хранятся имена параметров, переданных сценарию Perl через командную строку?

4. Можно ли получить в программе Perl имена файлов определенного каталога, удовлетворяющих заданному шаблону?

5. Какая списковая операция осуществляет вывод на экран монитора?

6. Какая списковая операция осуществляет вывод во внешний файл?

Упражнения

1. Напишите программу, которая копирует один файл в другой. Имена файлов передаются в программу при ее запуске как параметры командной строки. (Подсказка: используйте системную команду сору.)

2. Напишите программу, которая отображает на экране содержимое файлов, имена которых задаются в командной строке. Отображение содержимого каждого файла должно предваряться строкой, содержащей имя файла. (Подсказка: использовать операцию <>.)

3. Напишите программу Perl, которая удаляет файлы определенного каталога. Имена файлов задаются шаблоном, который вместе с именем каталога передается в программу через командную строку при ее запуске.



В Perl реализовано два набора функций для осуществления операций чтения/записи в файл. Функции одного набора используют буфер - некоторую область памяти - для накопления читаемой/записываемой в файл информации, после заполнения которого или закрытия файла осуществляется физическая запись данных буфера на диск или их пересылка в программу. Эти функции, к которым относятся print , readline, <>, read , getc , seek и tell , по существу, представляют собой интерфейсные функции к процедурам буферизованной библиотеки ввода-вывода stdio языка С. Использование буферизованных операций ввода-вывода ускоряет чтение/запись данных в файлы. Функции второго набора, к которым относятся sysread, syswrite и sysseek, обращаются непосредственно к функциям ввода-вывода операционной системы, осуществляя прямые физические операции чтения/записи данных без накопления их в промежуточном буфере. Открывать файл для доступа как буферизованными, так и небуферизованными функциями можно любой из двух функций - open () или sysopen() - при открытии файла не регламентируется, каким набором функций следует обрабатывать содержащуюся в нем информацию. Единственное требование заключается в том, что не рекомендуется смешивать эти два подхода для одного файла в одном сеансе открытия, так как это может привести к непредсказуемым результатам.

ВНИМАНИЕ При работе с одним и тем же файлом не следует смешивать вызовы буферизованных и небуферизованных функций ввода-вывода. Подобная практика может приводить к непредсказуемым коллизиям. Если требуется, например, использовать небуферизованных функции чтения/записи, а информация из файла уже читалась буферизованной операцией о, то следует закрыть файл, снова его открыть и использовать для работы с ним небуферизованные функции.

Буферизованный ввод-вывод

Чаще всего в программе осуществляется обработка текстовых файлов. Операция <>, операндом которой является дескриптор файла, читает информацию из файла целыми «записями», которые обычно представляют строки текстового файла. Примененная в скалярном контексте, она читает текущую запись файла, увеличивая на единицу специальную переменную $., отслеживающую количество прочитанных записей. В списковом контексте эта же операция прочитает все записи файла, если она выполняется для этого файла первой, или оставшиеся записи, если до нее были выполнены другие операции чтения из файла, и возвращает список, элементами которого являются прочитанные записи файла. Более точно можно сказать, что операция <> в списковом контексте читает оставшиеся записи файла, начиная с текущей его позиции, которая неявно изменяется при каждой операции чтения, а может быть изменена и явным способом с помощью функции seek (), c которой мы познакомимся чуть позже. Забегая вперед, скажем, что все файлы в Perl не имеют никакой структуры, а представляются, как и в С, потоком байтов. Разделитель записей хранится в специальной переменной $/, и по умолчанию им является символ новой строки \n. Таким образом, если пользователь не устанавливал собственный разделитель записей, то под записью файла в операции <> подразумевается строка текстового файла. Задание другого разделителя записей осуществляется обычной операцией присваивания переменной $/ нового символа или последовательности символов разделителя записей. В листинге 6.9 демонстрируются некоторые приемы чтения из файла операцией <>. Листинг 6.9. Чтение из файла операцией <> #! perl -w open (F1, "in.dat") or die "Ошибка открытия файла: $!"; open(F2, "out.dat") or die "Ошибка открытия файла: $!"; $line1 = ; # Первая запись файла in.dat $line2 = ; # Вторая запись файла in.dat @rest = ; # Оставшиеся записи файла in.flat $/ = ">"; # Задание другого разделителя записей @f2 = ; # Печать прочитанных записей файла out.dat for($i=0; $i<=$#f2; $i++) { print "Запись ".($i+1).": $f2[$i]\n"; } $/ = "\n"; # Восстановление разделителя по умолчании close (Fl) or die $!; close(F2) or die $!; open(F3, "out.dat") or die "Ошибка открытия файла: $!"; print ; # Печать всего файла close(F3) or die $!; Несколько комментариев к программе из листинга 6.9. В переменные $line1 и $line2 читаются соответственно первая и вторая строки файла in.dat, так как используется умалчиваемый разделитель записей \n. Элементы массива @rest хранят строки с третьей по последнюю этого же файла, так как в операторе присваивания операция чтения выполняется в списковом контексте. Перед чтением записей файла out.dat устанавливается новый разделитель записей - символ >. Если файл out.dat, например, содержит только одну строку Иванов> Петров> Сидоров> Конец то элементы массива @f2 будут содержать следующие значения: $f2 = "Иванов>" $f2[l] = "Петров>" $f2 = "Сидоров>" $f2 = "Конец"
ПРИМЕЧАНИЕ Если при создании файла out.dat его единственная строка завершена переходом на новую строку (нажата клавиша Enter), то $f2 будет содержать строку "Конец\n".
В последнем операторе печати программы из листинга 6.9 операция выполняется в списковом контексте, так как функция print() является списковой операцией и ей требуется список значений. Если же мы захотели бы при выводе на печать предварить каждую запись каким-либо текстом и предполагали бы для этого использовать следующий оператор: print "Запись:".; то получили бы распечатку только первой строки файла, так как в этом случае операция будет выполняться в скалярном контексте, создаваемом операцией конкатенации строк (.). Чтобы получить требуемый результат, следовало бы воспользоваться циклом while: while() { print "Запись: ".$_; # Печать очередной строки связанного # с дескриптором F3 файла } Напомним, что если результат операции <> не присваивается никакой переменной, то результат ее выполнения сохраняется в специальной переменной S_. Фактически операция чтения записей <> в Perl реализуется встроенной функцией readline(), которую можно вызывать и непосредственно. Единственным ее параметром является ссылка на дескриптор файла, а так как дескриптор не принадлежит ни к одному из допустимых типов данных (скаляр, массив скаляров или хэш-массив), то следует использовать ссылку на специальный внутренний тип данных Perl typeglob. Для этого достаточно поставить перед дескриптором файла префикс *: readline *ДЕСКРИПТОР; Например, следующие два оператора полностью эквивалентны: $line = ; $line = readline *STDIN; Естественно, все, что говорилось о символах разделения записей, хранящихся в специальной переменной $/, относится и к операции readline(). Запись в файл, открытый в режиме записи или добавления, осуществляется функцией print() с первым параметром, являющимся дескриптором файла: print ДЕСКРИПТОР СПИСОК_ВЫВОДА; Эта операция записывает содержимое элементов списка в том порядке, в котором они определены в вызове функции, и не добавляет в конец списка никакого разделителя записей или символа перехода на новую строку. Об этом должен позаботиться сам программист, либо явно добавляя в конец списка вывода символ разделителя записей или новой строки, либо воспользовавшись возможностью, предоставляемой специальной переменной $\. Функция print добавляет в конец своего списка вывода содержимое этой специальной переменной, которая по умолчанию содержит пустую строку: # Явное задание разделителя записей print F1 @recl1, "\n"; $\ = "\n"; # Установка разделителя записей # Теперь разделитель записей будет выводиться неявно # каждой следующей функцией print print F1 @rесl2;
ВНИМАНИЕ Между дескриптором файла и первым элементом списка вывода не должно быть запятой. Если такое случится, то интерпретатор perl выдаст ошибку: No comma allowed after filehandle.
При записи информации в файл функцией print() можно воспользоваться еще одной полезной возможностью. Если задано значение специальной переменной $, то оно вставляется между элементами списка вывода. Например, если мы хотим, чтобы значения элементов списка выводились не сплошным потоком символов, а были разделены пробелом, то следует установить значение этой специальной переменной равным пробелу: $var1 = "11111"; Svar2 = "22222"; print $var1, $var2, "\n"; $, = " "; print $var1, $var2, "\n"; Первый оператор print напечатает: 1111122222 Тогда как при выполнении второго оператора print мы получим строку: 11111 22222
ВНИМАНИЕ При установке значений специальных переменных $\ и $, их действие распространяется на все последующие вызовы функции print().
Если в функции print не указан дескриптор файла, то по умолчанию вывод осуществляется в стандартный файл вывода с дескриптором STDOUT, а если не задан список вывода, то выводится содержимое специальной переменной $_. Установку дескриптора функции print() по умолчанию можно изменить стандартной функцией select (). Вызванная без параметров, она возвращает текущий дескриптор файла вывода по умолчанию для функций print() и write(). Если ей передается единственный параметр, то этот параметр должен быть дескриптором файла. В этом случае она также возвращает текущий дескриптор по умолчанию и меняет его на дескриптор, определенный переданным ей параметром. Пример использования функции select() приведен ниже. # Сохранение текущего дескриптора по умолчанию и назначение # F1 новым умалчиваемым дескриптором $oldfilehandle = select (Fl); # Вывод в файл, ассоциированный с дескриптором Fl print $line; # Восстановление старого дескриптора по умолчанию select($oldfilehandle); # Вывод в файл, ассоциированный со старым дескриптором print $line; Как уже отмечалось, файлы в Perl интерпретируются как неструктурированные потоки байтов. То, что с помощью операции <> и функции print() мы соответственно читаем или записываем целую последовательность байтов, которую мы называем «записью», ни в коем случае не связано с какой-то определенной структурой файла. Просто эти операции так организованы, что одна читает, а вторая записывает последовательности байтов. В действительности мы можем читать и записывать информацию в файл побайтно. Для каждого открытого файла создается системная переменная, которая отслеживает его текущую позицию, то есть место в файле, начиная с которого функции чтения читают, а функции записи записывают информацию. Следовательно, мы можем говорить, что операции чтения/записи выполняются с текущей позиции файла. При выполнении любой операции чтения/записи указатель текущей позиции файла перемещается на количество прочитанных или записанных байтов. Если, например, была прочитана запись длиной 80 байт с самого начала файла, то следующая операция чтения или записи начнется с позиции 81 байта файла. Для определения текущей позиции в файле используется функция tell(), единственным параметром которой может быть дескриптор файла. Она возвращает текущую позицию в ассоциированном с заданным дескриптором файле. Эта же функция без параметра возвращает текущую позицию в файле, для которого была в программе выполнена последняя операция чтения. Текущая позиция в файле автоматически изменяется в соответствии с выполненными операциями чтения/записи, но ее можно менять и явным образом с помощью функции seek(), которой передаются в качестве параметров дескриптор файла, смещение и точка отсчета. Для связанного с дескриптором файла устанавливается новая текущая позиция, смещенная на заданное параметром СМЕЩЕНИЕ число байтов относительно точки отсчета: seek ДЕСКРИПТОР, СМЕЩЕНИЕ, ТОЧКА__ОТСЧЁТА; Параметр ТОЧКА_ОТСЧЕТА может принимать одно из трех значений: 0 - начало файла, 1 - текущая позиция, 2 - конец файла. Смещение может быть как положительным, так и отрицательным. Оно отрицательно относительно конца файла, положительно относительно начала файла и может быть как положительным, так и отрицательным относительно текущей позиции. Задать точки отсчета можно так же с помощью именованных констант SEEK_SET, SEEK_CUR и SEEK_END, определенных в поставляемом с Perl пакете IO::Seekable, что делает программу лучше читаемой. Эти константы в том порядке, как мы их перечислили, соответствуют началу файла, текущей позиции и концу файла. Для использования указанных именованных констант, естественно, необходимо подключить в программе этот модуль с помощью ключевого слова use . Например, следующие операторы устанавливают одинаковые текущие позиции в файлах: use IO::Seekable: seek FILE1, 5, 0; seek FILE2, 5, SEEK_SET; В языке нет специальных функций перехода в начало или конец файла. Если необходимо позиционировать файл в начало или конец, следует использовать нулевое смещение относительно соответствующих точек отсчета при вызове функции seek(): seek FILE1, 0, 0; # Переход в начало файла seek FILE1, 0, 2; # Переход в конец файла Кроме уже знакомых нам операций чтения записей файла <> и readline(), Perl предоставляет еще две функции чтения содержимого файлов - getc() и read(). Первая читает один байт из файла, тогда как вторая читает записи заданной длины, то есть последовательность байтов определенной длины. Функция getc() читает и возвращает символ в текущей позиции файла, дескриптор которого передан ей в качестве параметра, или неопределенное значение в случае достижения конца файла либо возникновения ошибки. Если функция вызывается без параметра, то она читает символ из стандартного файла ввода STDIN. getc; # Чтение символа из STDIN getc F1; # Чтение символа в текущей позиции файла с # дескриптором F1 Функция read() читает определенное число байтов, начиная с его текущей позиции. Она может вызываться с тремя или четырьмя параметрами, и ее вызов имеет вид: read ДЕСКРИПТОР, ПЕРЕМЕННАЯ, ДЛИНА [,СМЕЩЕНИЕ]; Эта функция читает количество байтов, определенное целым значением параметра ДЛИНА, в скалярную переменную, определяемую параметром ПЕРЕМЕННАЯ, из файла с дескриптором, заданным первым параметром ДЕСКРИПТОР. Возвращаемое значение - действительное количество прочитанных байтов, 0 при попытке чтения в позиции конца файла и неопределенное значение в случае возникновения других ошибок. Необязательный параметр СМЕЩЕНИЕ определяет, после какого байта содержимого переменной ПЕРЕМЕННАЯ будет сохранена прочитанная из файла запись. Он может иметь и отрицательное значение смещения -n (n - целое число). Это означает, что из содержимого переменной ПЕРЕМЕННАЯ отбрасываются последние n байтов и к оставшейся строке добавляется запись, прочитанная из файла. Листинг 6.10 демонстрирует чтение записей определенной длины из файла in.dat, содержащего три строки данных: ******** * PERL * ******** Листинг 6.10. Чтение записей определенной длины #! perl -w open(F1, "in.dat") or die "Ошибка открытия файла: $!"; $str = "1234567890"; read F1, $str, 9; # Чтение девяти байтов в # переменную $str без смещения print $str,"\n"; # $str = "********\n" read F1, $str, 8, length ($str); print $str,"\n"; # $str - "*******\n* PERL *" В программе из листинга 6.10 функция length() используется для определения количества символов (байтов), содержащихся в скалярной переменной. После выполнения первой операции чтения содержимое переменной $str было уничтожено, так как эта функция read () вызывалась без смешения. При втором чтении хранившиеся данные в переменной $str были полностью сохранены. Обратите внимание, что символ перехода на новую строку, содержащийся в первой строке файла in.dat, также учитывается при чтении функцией read() записей определенной длины. Следует не забывать об этом обстоятельстве при чтении информации из «многострочного» файла функцией read().

Небуферизованный ввод-вывод

Функции чтения из файла sysread(), записи в файл syswrite() и установки указателя текущей позиции файла sysseek() являются аналогами рассмотренных нами функций read(),print() и seek(), но, в отличие от последних, они напрямую обращаются к соответствующим функциям операций системы, а не к функциям стандартной библиотеки ввода-вывода С, минуя тем самым создаваемый этими функциями буфер для выполнения операций чтения и записи в файл. Заметим, что аналога буферизованной функции tell () не существует, ее функциональность реализуется функцией sysseek(). При вызове функций небуферизованного чтения и записи им передается одинаковый набор параметров, полностью соответствующий параметрам функции read: sysread ДЕСКРИПТОР, ПЕРЕМЕННАЯ, ДЛИНА [,СМЕЩЕНИЕ]; syswrite ДЕСКРИПТОР, ПЕРЕМЕННАЯ, ДЛИНА [,СМЕЩЕНИЕ]; Возвращаемым значением этих функций является истинное количество соответственно прочитанных или записанных в файл байтов, 0 в случае достижения конца файла или undef при возникновении ошибки. Соответственно набор параметров функции sysseek() полностью соответствует передаваемым параметрам в функцию seek(): sysseek ДЕСКРИПТОР, СМЕЩЕНИЕ, ТОЧКА_ОТСЧЕТА; Все сказанное относительно использования функции seek() полностью переносится и на ее небуферизованный аналог. Функциональность буферизованной операции tell() реализуется следующим вызовом функции sysseek(): $position = sysseek Fl, 0, 1; # Текущая позиция указателя файла Программа демонстрирует использование небуферизованных функций ввода-вывода для обработки содержимого файла. #! perl -w use Fcntl; # Открытие файла в режиме чтение/запись sysopen F1, "in.dat", O_RDWR; # Чтение блока в 14 байтов $read = sysread F1, $string, 14; warn "Прочитано $read байтов вместо 14\n" if $read != 14; # Установка текущей позиции (на 15 байтов) $position = sysseek Fl, 0, 1; die "Ошибка позиционирования: $!\n" unless defined $position; # Запись строки в текущей позиции $string = "Новое Значение"; $written = syswrite F1, $string, length ($string); die "Ошибка записи: $!\n" if $written != length($string); # Закрытие файла close F1 or die $!; При работе с небуферизованными функциями ввода-вывода следует всегда проверять завершение операции чтения, записи или позиционирования. Стандартная система ввода-вывода, через которую реализуется буферизованный ввод-вывод, сама проверяет завершение указанных операций и отвечает за него, если процесс по каким-то причинам был прерван на середине записи. При небуферизованном вводе-выводе об этом должен позаботиться программист.

Каждый раз, когда необходимо получить доступ к файлу на диске, необходимо создать новый дескриптор и открыть его. Для открытия дескрипторов используется функция open:
open (дескриптор_файла, путь)
Путь указывает, какой файл необходимо открыть, поэтому, если не указан полный путь, например с:/windows/system/, функция open попытается открыть файл в текущем каталоге. При успешном выполнении функция open возвращает ненулевое значение (Истина), при неудачном - возвращается undef (Ложь), например:
if (open(MYFILE, "mydatafile")) {
#Выполняется при успешной открытии
} else {
print "Ошибка при открытии файла mydatafile\n";
exit 1;
}

Во многих программах Perl подобный синтаксис "открыть или сообщить об ошибке" может быть реализован с помощью функции die . Функция die останавливает выполнение программы и выводит сообщение об ошибке:
Died at имя_сценария line xxx
Здесь имя сценария - название программы на Perl, xxx - номер строки, в которой встретилась функция die. Функции die и open часто используются вместе следующим образом:
open(MYTEXT, "novel.txt") || die;
Программа или открывает файл, или прекращает свое выполнение. Если open завершилась неудачно, возвратив ложное значение, вступает в действие логический оператор ИЛИ (| |) . В результате будет вычисляться аргумент, находящийся в правой части оператора (в данном случае - функция die). При удачном выполнении функции open правая часть логического выражения не вычисляется. Иногда используют другой вид логического ИЛИ - or.

Для закрытия дескриптора используется функция close :
close(MYTEXT);
Если попытаться открыть файл, указав в качестве параметров функции open один из уже открытых дескрипторов, то вначале этот дескриптор закрывается, а затем повторно открывается.

Функции die может передаваться список аргументов, которые будут выводиться вместо стандартного сообщения. Если сообщение не содержит символа перевода строки, то в его конец добавляется текст at имя_сценария line xxx, например:
die "Ошибка при открытия файла";
# Выводится сообщение " Ошибка при открытии файла at имя_сцевария line xxx"
die "Ошибка при открытии файла\п" ; # Выводится " Ошибка при открытии файла"

В Perl предусмотрена специальная переменная $! , содержащая сообщение об ошибке, возникшей при выполнении последней системной операции (например, операции дискового ввода-вы вода). В числовом контексте конструкция $! возвращает мало что говорящий номер ошибки. В строковом контексте переменная $! возвращает сообщение операционной системы об ошибке:
open(MYFILE, "myfile") || die "Ошибка при открытии myfile: $!\n";
Если эта функция не сможет открыть файл из-за его отсутствия, будет выведено сообщение:
Ошибка при открытии myfile: a file or directory in the path does not exist.

Не используйте переменную $! для проверки успешности выполнения системной функции. Значение этой переменной определено непосредственно после выполнения системной операции (например, ввода-вывода) и только при неудачном завершении этой операции. Во всех остальных случаях переменная $! может иметь совершенно произвольное бессмысленное значение.

Иногда нужно вывести в программе предупредительное сообщение и продолжить ее выполнение. Для генерации предупреждений служит функция warn , аналогичная die, за исключением того, что выполнение программы продолжается:
if {! open{MYFILE("output")) {
warn "Ошибка при открытии файла output: $!";
} else {
# Читаются данные файпа...
}

Perl существует несколько способов чтения файлов, определенных дескрипторами. Самый распространенный заключается в использовании оператора файлового ввода, называемого еще угловым оператором <> . Для чтения информации из файла достаточно поместить его дескриптор в угловые скобки и присвоить это значение переменной, например:
open(MYFILE, "myfile") || die "Ошибка при открытии myfile: $!";
$line=; #Чтение файла

Угловой оператор в скалярном контексте читает одну строку из файла. Если файл заканчивается, угловой оператор возвращает значение undef. Строкой в текстовом файле называется последовательность символов, ограниченная специальным признаком конца строки. В UNIX таким признаком является символ перевода строки (ASCII-код 10), в DOS и Windows- последовательность символов возврата каретки и перевода строки (ASCII-коды: 13 и 10). Указатель стандартного признака конца строки может быть изменен в Perl, что позволяет добиться некоторых интересных эффектов.

Для чтения и вывода содержимого целого файла можно использовать следующий код (в примере предполагается, что MYFILE - открытый дескриптор файла):
while (defined($a=)) {
print $a;
}

Для чтения информации из файла удобно использовать цикл while. Если в цикле while вместо условного выражения используется угловой оператор, Perl автоматически присваивает введенную строку специальной переменной $_ и повторяет цикл, пока файл не закончится:
while() {
print $_;
}

При этом на оператор while возлагается присваивание введенной строки переменной $_ и проверка признака достижения конца файла. Такое интересное поведение случается только в цикле while и лишь тогда, когда условное выражение состоит из углового оператора. Не забывайте, что в прочитанных из файла данных, кроме самого текста, содержатся также символы конца строки. Если вам нужен только текст, используйте функцию chomp, позволяющую избавиться от символов конца строки.

В контексте списка угловой оператор читает файл целиком и присваивает его списку. Каждая строка файла присваивается соответствующему элементу списка или массива, как показано ниже:
open(MYFILE, "novel.txt") || die "$!";
@contents=;
close(MYFILE);

В этом примере через дескриптор MYFILE считываются все данные из файла novel.txt и присваиваются массиву @contents. При этом первая строка файла novel.txt присваивается первому элементу массива @contents: $contents . Вторая строка присваивается $contents и т.д.

Для записи данных в файл сначала нужно открыть сам файл для записи. Синтаксис открытия файла для записи почти такой же, как и для чтения:
open (дескриптор, ">путь")
ореn (дескрвптор, ">>путь")

Синтаксис первой строки уже знаком нам, за исключением символа > перед путем. Этот символ говорит Perl, что в файл, путь которого указан следом, должны быть записаны новые данные. При этом уже имеющиеся данные в файле стираются и указанный дескриптор открывается для записи. Во втором примере символы >> говорят Perl, что файл открывается для записи, но если файл уже существует, новые данные дописываются после имеющихся. Вот примеры:
# Новые данные записываются поверх старых, если таковые есть
open(NEWFH, ">output.txt") || die "Ошибка при открытии output.txt: $!";
# Новые данные дописываются к уже существующим.
open(APPFH, ">>logfile.txt") || die " Ошибка при открытии logfile.txt: $!";

После окончания работы с файлом, открытым для записи, вопросом первостепенной важности становится закрытие этого файла и освобождение его дескриптора. Функция close сообщает операционной системе, что запись в файл завершена и данные должны быть помещены в место своего постоянного хранения на диске, например:
close(NEWFH);
close(APPFH);

После того как файл открыт для записи, поместить в него данные очень просто. Это делается с помощью хорошо вам знакомой функции print . Пока функция print использовалась нами лишь для отображения данных на экране. В принципе эта функция может быть использована для записи данных в любой файл. Синтаксис оператора print, предназначенного для вывода данных в файл, очень прост:
print дескриптор СПИСОК

open (LOGF, ">>logfile") || die "$!";
if (! Print LOGF "Запись сделана ", scalar(localtime), "\n") {
warn "Ошибка при записи в файл logfile: $!";
}
close(LOGF);

Одновременно можно открыть сразу несколько файлов для чтения или записи, как показано в следующем примере:
open(SOURCE, "soursefile") || die "$!";
open(DEST, ">destination") || die "$!";
@contents=; # "Проглотим" исходный файл
print DEST $contents; # Запишем его в другой файл
close(DEST);
close(SOURCE);

В этом примере выполняется простое копирование файлов. Кстати, можно несколько сократить код, объединив в одном операторе операции чтения и записи:
print DEST ;
Так как функция print в качестве параметра ожидает передачи списка, оператор находится в контексте списка. Угловой оператор в контексте списка считывает весь файл, а оператор print выводит его в дескриптор DEST.

При записи двоичных данных, таких как файлы GIF, EXE, документы MS Word и т.п., преобразование данных не требуется. Поэтому, чтобы ни Perl, ни операционная система не делали подобных преобразований, перед записью двоичных данных в файл необходимо использовать функцию binmode . Она помечает дескриптор файла как дво-
ичный. Функция binmode вызывается после открытия файла, но до того, как будет выполнен ввод или вывод данных:
open(FH, "camel.gif") || die " $! " ;
binmode(FH); # Дескриптор становится двоичный.

После открытия файла функцию binmode к его дескриптору можно применить только один раз. При закрытии и повторном открытии двоичного файла придется заново вызвать функцию binmode. Использование binmode в системах, где не различаются текстовые и двоичные файлы (например, в UNIX), не вызывает никаких действий.

Перед тем как открыть файл, неплохо убедиться, что он действительно существует, проверить, не является ли он каталогом, и не приведет ли это к появлению сообщения об ошибке permission denied. В Perl имеются специальные операторы тестирования файлов . Все они имеют похожий синтаксис:
-X дескриптор_файла
-X путь

Здесь X - конкретная операция тестирования, а дескриптор_файла - тестируемый дескриптор. Файл можно протестировать и без открытия дескриптора.

Часто используемые операторы тестирования файлов
-r
-г "file"
Истинное значение, если разрешено чтение "файла"
-w
-w $a
Истинное значение, если разрешена запись в файл, имя которого содержится в переменной $а
-e
-e "file"
Истинное значение, если "file" существует
-z
-z "file"
Истинное значение, если "file" существует, но он пустой
-s
-s "file"
Возвращает размер "file" в байтах, если тот существует
-f
-f "file"
Истинное значение, если "file" является обычным файлом (не каталогом)
-d
-d "catalog"
Истинное значение, если параметр "catalog" задает каталог
-T
-T "file"
Истинное значение, если параметр "файл" определяет текстовый файл
-B
-B "file"
Истинное значение, если параметр "файл" определяет двоичный файл
-M
-M "file"
Возвращает количество прошедших дней с момента последней модификации "файла"

print "Где будем сохранять данные?";
$filename=;
chomp $filename;
if (-s $filename) {
warn "Содержимое файла $file будет потеряно!\п";
warn "Он был модифицирован ",
-М $filename, "дней тому назад.\п";
}