Операции ввода-вывода

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

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

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

my $file;
open $file, '<', 'MyFile.txt';	открытие для чтения

Мы обожаем совмещать объявление переменной для дескриптора с вызовом процедуры open:

open my $file, '<', 'MyFile.txt';

Значение '<', переданное как второй параметр, символизирует чтение. Знак «меньше», повернувшийся спиной к имени файла, как бы намекает нам, что готовится извлечение информации из файла. Угадайте, как открыть файл для записи? Конечно же так:

open my $file, '>', 'MyFile.txt';	открытие для записи

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

chdir '/';

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

open my $passwd, '<', '/etc/passwd';

В некоторых операционных системах, в полных именах файлов применяется другой разделитель директорий. Например, в Microsoft DOS и Microsoft Windows вместо слэша / применяется разделитель бэкслэш \. Тем не менее, задавая полное имя в программе на Perl при открытии, следует использовать слэш: '/c:/dos/autoexec.bat'.

Не стоит ожидать, что открытие файла всегда будет успешным. Есть много причин, которые могут помешать. Среди них:

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

if(open )
{
	работа с файлом и последующее закрытие
}
else
{
	die "Невозможно открыть файл: $!\n";
}

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

Гораздо изящней выглядит следующая идиома:

open  or die "Невозможно открыть файл: $!\n";

В этом логическом выражении два операнда. Если первый (то, что возвращает open) принимает истинное значение, то в вычислении второго нет нужды, так как всё выражение уже заведомо истинно. Если же open возвратит ложное значение, то значение всего выражения определяется по второму операнду, который в этом случае должен быть вычислен. Для вычисления будет вызвана процедура die со всеми вытекающими последствиями.

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

for(@fileNames)
{
	open my $file, '<', $_ or warn "Невозможно открыть файл $_: $!" and next;
	работа с очередным файлом и последующее его закрытие
}

Имеется два способа чтения из дескриптора: побайтное/посимвольное и построчное.

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

read $file, $buffer, 16;

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

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

Что же будет, если запросить при чтении больше байт, чем размер непрочитанной части файла? Ничего страшного, компьютер не сломается. Просто в переменную-буфер отправится меньше байт, чем было запрошено. Контролировать это явление удобно, пользуясь возвращаемым значением процедуры read — это количество байт, которое удалось прочесть. Широко распространена при программировании на Perl такая идиома:

while(read $file, $buffer, $size)
{
	сделать что-то с $buffer
}

Здесь значение, возвращённое из read, используется в качестве условия цикла. Рано или поздно файл будет прочитан до конца, и следующий вызов read возвратит ноль (ложное значение). Это прервёт цикл, что нам, собственно, и нужно.

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

Построчное чтение осуществляется оператором <…>. Код <$file> приводит к считыванию очередной строки из дескриптора $file в переменную по умолчанию $_.

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

open my $file, '<', 'file.txt' or die "Невозможно открыть файл: $!\n";

while(<$file>)
{
	print;
}

Здесь читатель справедливо задаётся вопросом: что именно печатает процедура print в теле цикла? Переменную по умолчанию, конечно. Можно было бы написать print $_, но вряд ли это добавит ясности.

Другой пример. В уже открытом файле записаны числа, по одному в строке. Требуется вывести на экран их сумму.

my $sum=0;

while(<$file>)
{
	chomp;
	$sum+=$_;
}

print "$sum\n";

Команда chomp необходима вот по какой причине. Оператор <…> вместе со строкой считывает и завершающий строку символ, который создаст проблему, если считанная строка впоследствии будет участвовать в арифметическом выражении. Встроенная процедура chomp удаляет этот символ, если строка заканчивается им. Если же последний символ другой, процедура ничего не делает. такая предосторожность нужна на тот случай, если, к несчастью, файл не заканчивается символом конца строки. Тогда и последняя прочитанная из файла строка закончится чем-то другим. Имеется также процедура chop, которая удаляет и возвращает последний символ строки независимо от того, какой он. Обе процедуры, chop и chomp, работают со строкой, переданной как параметр, но в отсутствие параметра — с переменной $_.

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

$s=<$file>;

Самое время сообщить об одной особенности оператора построчного чтения. Его смысл немного меняется, если оператор поместить в списочный контекст, то есть туда, где должен быть список. Например, если присвоить массиву:

@s=<$file>;

В этом случае все строки, прочитанный из файла, заполнят массив. Ещё можно организовать переборный цикл:

print for <$file>;

Этот код, как и приведённый выше цикл while, печатает строки файла на экран. К тому же результату приведёт код

print <$file>;

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

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

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

Для записи в дескриптор применяется давно знакомая нам процедура print, но не совсем так, как мы привыкли:

print $file $string;

Здесь содержимое строки $string записывается в открытый дескриптор $file. Обратите особое внимание на отсутствие запятой после первого параметра $file. С запятой смысл команды будет другим: в дескриптор ничего не запишется, а вместо этого программа выведет на экран строковое представление значений обеих переменных $file и $string:

(шестнадцатеричное число в скобках, скорее всего, будет другим, ну и вместо слова Привет! может оказаться другой текст). Число это нам ни о чём не говорит, как и загадочное слово GLOB вместе со скобками. Итак, если все параметры процедуры print разделены запятыми, все они печатаются на экран. Если после первого параметра нет запятой, этот параметр должен быть дескриптором, открытым для записи, а остальные параметры записываются в него:

print $file 'Hello, ', $user;

Можно все параметры после дескриптора заключить в скобки:

print $file('Hello, ', $user);

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

print {$file} 'Hello, ', $user;

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

print "Уважаемый $name! Вы задолжали $duty рублей. Уплатите до $date, иначе $punishment.\n";

Таким способом можно вставлять в шаблон и строки, и числа. Что касается чисел, в каких-то ситуациях может потребоваться их особое форматирование. Числа перед включением в шаблон может потребоваться округлить до нужного количества десятичных цифр после точки, дополнить слева нужным количеством нулей или пробелов, чтобы число хорошо смотрелось в таблице. Может потребоваться вставить число в двоичном или шестнадцатеричном формате.

В таких ситуациях на помощь приходят встроенные процедуры printf и sprintf. Первый параметр в обеих процедурах — так называемая форматная строка. Это шаблонный текст, в который с определённые места должны быть вставлены остальные параметры. Места вставки параметров и желательный формат специальным образом помечаются. Метка представляет собой знак процента, за которым следует обозначение формата. Остальные параметры процедуры вставляются на места форматных меток в соответствующем формате. Процедура printf выводит результат в дескриптор подобно процедуре print, а sprintf возвращает полученную после всех вставок строку для какого-то иного использования. В принципе, если бы не было printf, её можно было бы легко запрограммировать: вызов printf(…) равносилен print(sprintf(…)).

Теперь подробнее о форматных строках и форматных метках.

Если вставляемое число нужно вывести как целое (то есть с отбрасыванием дробной части), метка имеет вид %d:

printf "Целая часть π равна %d\n", 3.1415926;

Вот так можно выводить целые числа с выравниванием:

printf "%4d\n", $_**3 for 1..10;

Для выравнивания числа можно дополнять слева не пробелами, а нулями:

printf "%04d\n", $_**3 for 1..10;

Шестнадцатеричный формат с дополнением нулями до трёх цифр:

printf "%03X\n", $_**3 for 1..10

Двоичный формат с дополнением пробелами до четырёх знаков:

printf "%4B\n", $_ for 1..10;

Округление с двумя цифрами после десятичной точки:

printf "%.2f\n", rand for 1..10;

Приведённые примеры не охватывают все возможные форматы, понятные процедурам printf и sprintf, но для наших задач будет достаточно и этого.

Использование знака процента как признака метки лишает нас возможности использовать его в форматной строке как таковой. Эта трудность не новая для нас, и разрешается она уже хорошо знакомым способом. Комбинация из двух знаков процента означает одиночный знак процента (подобно тому, как \\ внутри ""-строк означает один бэкслэш):

$p=38; $x=43;
printf "%d%% от %d равно %d\n", $p, $x, $p/100*$x;

Точно так же, как и print, процедура printf может осуществлять вывод не только на экран, но и дескриптор файла, открытого для записи или для добавления:

printf {$file} ;

Информатика-54© А. Н. Швец