Некоторые сведения о Perl 5/Ввод и вывод

← Операторы Глава Форматированный вывод →
Ввод и вывод


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

Язык Perl предоставляет весь необходимый функционал для чтения потоков данных и записи их в файлы. В этой главе мы рассмотрим как реализуется ввод/вывод в Perl.

Операции ввода править

Данные в Perl программу можно вводить следующими стандартными инструментами:

  • через стандартный поток ввода STDIN с помощью операции <>;
  • через файлы системы, открывая их и получая дескрипторы;
  • через операцию qx{} или наклонные кавычки ``.

В этом разделе мы рассмотрим различные подходы к вводу, кроме файлов (которые будут рассмотрены позже). Начнем с операции qx{}.

qx{} (или более короткая ее форма ``) позволяет исполнить строковые литералы как команды для командной оболочки операционной системы. После того как команды выполнятся, весь вывод можно присвоить переменной, причем команда различает скалярный и списковый контексты. В скалярном контексте весь вывод сохраняется одной строкой. В списковом контексте каждая строка вывода будет записываться отдельно.

# Скалярный контекст
$date = `date`;

$out = qx {
    ps
};

# Списковый контекст
@out = qx {
    ps
};

print $date;

print $out;

foreach (@out) {
    print "line: " . $_; 
}
 9 янв 2023 г. 11:12:33
      PID    PPID    PGID     WINPID   TTY         UID    STIME COMMAND      
     1495       1    1495      17984  cons0    1052188   Jan  5 /usr/bin/bash
     2475    1495    2475      16208  cons0    1052188 10:58:03 /usr/bin/perl
     2476    2475    2475      11076  cons0    1052188 10:58:03 /usr/bin/ps  
line:       PID    PPID    PGID     WINPID   TTY         UID    STIME COMMAND      
line:      2477    2475    2475      19128  cons0    1052188 10:58:03 /usr/bin/ps  
line:      1495       1    1495      17984  cons0    1052188   Jan  5 /usr/bin/bash
line:      2475    1495    2475      16208  cons0    1052188 10:58:03 /usr/bin/perl

В списковом контексте строки будут разбиваться по символу разделителя, записанному во встроенной переменной $/, в которой по умолчанию записан символ новой строки \n. Управляя этим значением, можно управлять формированием вывода. Разделителем не обязательно может быть один символ: им может быть целая строка.

Выражение qx{} вычисляется каждый раз когда встречается интерпретатором perl. Код возврата исполняемой команды будет записан в переменной $? (подобно командной оболочке Bourne Shell).

Второй операцией ввода является <> (угловые скобки). Обычно она используется, чтобы читать по дескриптору (когда он указан между скобками), но когда его нет, весь ввод осуществляется через дескриптор STDIN. Дескриптор STDIN может быть связан с различными устройствами ввода (все зависит от того, как была вызвана Perl программа), но обычно он связывается с клавиатурой, поэтому попытка чтения по этому дескриптору обычно всегда приостанавливает исполнение в ожидании, когда пользователь что-нибудь введет и нажмет ↵ Enter.

Чтобы прочитать что-нибудь из дескриптора, достаточно присвоить результат операции переменной. Операция <> понимает скалярный и списковый контексты.

# Файл: main.pl

# Простейшее чтение из STDIN. В данном примере программа
# работает как echo, печатая на устройстве вывода все, что
# ввел пользователь.
while ($line = <>) {
    print $line;
}

Операция <> читает до тех пор, пока не получит символ перевода строки или символ конца потока EOF. В программе выше мы зациклили ввод за счет того, что операция ввода будет возвращать ИСТИНУ до тех пор, пока не получит EOF. Чтобы послать этот символ с клавиатуры, нужно нажать сочетание Ctrl + d.

Если вызвать эту же программу следующим образом

$ perl main.pl <<END
hello
world
END
# то получим
hello
world

В данном случае командная оболочка связывает STDIN не с клавиатурой, а с некоторым файлом (предоставляемым оболочкой), в который она записывает две строки. Эти строки мы и прочитали в программе.

Операция <> по умолчанию пишет в переменную $_. Пользуясь этим, программу можно написать еще проще.

while(<>) {  # Пишет в $_
    print;   # Печатает $_
}

Массив аргументов @ARGV править

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

Обычно вам не следует парсить входящие аргументы самостоятельно: лучше всего использовать встроенный модуль Getopt::Long (что такое модули мы подробно разберем в отдельной главе). Данный модуль позволит вам легко парсить длинные и короткие опции командной оболочки.

Для примера мы просто покажем, как выглядит массив @ARGV.

#!/usr/bin/perl
# Файл: main.pl
foreach (@ARGV) {
    print $_ . "\n";
}

Вызовем программу например так:

$ ./main.pl --help -s 34 param1 param2
--help
-s    
34    
param1
param2

Лексемы __END__ и __DATA__ править

В язык Perl включены две специальные лексемы, которые позволяют хранить данные прямо в исходном файле программы.

Лексема __END__ определяет конец исходного кода программы и на этом слове интерпретация принудительно завершается. Разработчики Perl обычно размещают после этой лексемы документацию к исходному коду, обычно на языке разметки POD (Plain Old Documentation), которая может быть прочитана стандартными средствами чтения документации Perl.

#!/usr/bin/perl

print "Hello, World!";

__END__

Эти строки не будут интерпретированы perl.

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

Чтобы обратиться к этим данным, нужно использовать файловый дескриптор <имя-пакета>::DATA.

#!/usr/bin/perl

while (my $data = <DATA>) {
    print "$data";
}

__DATA__
line 1
line 2
line 3
line 4

Операции вывода править

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

print [<дескриптор>] <список>
print <дескриптор>

printf [<дескриптор>] <формат>, <список>

# Начиная с версии 5.10
say [<дескриптор>] <список>
say <дескриптор>

Всем функциям вывода можно указать куда печатать через дескриптор в первом аргументе. Если он не указан, то по умолчанию печать производится в дескриптор STDOUT, который обычно связывается с некоторым терминальным устройством TTY. Именно поэтому вы видите всю печать на экране.

Функции печатают переменные, передаваемые списками. Если они не указаны явно, то используется переменная $_.

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

Функции print и say просто печатают переменные, переданные списком, в указанном порядке. Разница между print и say состоит в том, что say сама подставляет перевод строки в конец (что очень удобно), тогда как print выводит строку в точности, как вы ей скажете.

Функция say появилась относительно недавно, поэтому, чтобы ее использовать, необходимо в начале включить возможность ее вызова:

use feature qw{ say }; # Включаем функцию say()
say "Hello, World!"

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

use v5.10; # С 5.10 и выше функция включается неявно
say "Hello, World!"

Все приведенные функции принимают на печать списки (т.е. являются списковыми операциями). Это может приводить к ошибочным ситуациям.

# НЕВЕРНО
print (2 + 2) ** 2;
# Напечатает 4, а не 16

В вышеприведенном примере на печать будет выведено 4, когда ожидалось 16. Это происходит потому, что выражение в скобках является термом. Далее функция print будет рассматривать выражение в скобках, как список своих параметров, а так как конструкция print (2 + 2) является термом, то сначала напечатается сумма; затем результат печати вернет 1, которая будет в конце возведена в квадрат.

Чтобы исправить эту ошибку, в подобных ситуациях нужно всегда указывать дескриптор явно:

# ПРАВИЛЬНО
print STDOUT (2 + 2) ** 2;
# Напечатает 16

Другими словами, когда список вычисляется прямо в вызове, всегда указывайте дескриптор явно.

В функциях печати вы можете быстро печатать массивы. Простой массив можно распечатать как минимум тремя способами:

@arr = (1,2,3);

print "@arr\n";  # Всегда используйте кавычки, чтобы элементы разделялись пробелами

# Перебором в цикле
foreach (@arr) {
  print "$_\n";
}

# Функцией join. Преимущество здесь в том, что можно менять разделитель.
print join(" ",@arr),"\n";

С хеш-массивами немного сложнее, так как стандартные функции печати не умеют их печатать:

%person = (name => "Larry", surname => "Wall");

while ( ($k,$v) = each %person ) {  # Функция each итеративно парами извлекает элементы
    print "$k => $v\n";
}

print map { "$_ => $person{$_}\n" } keys %person;  # Преобразуем хеш-массив в список и печатаем его

print "@{[ %person ]}\n";   # Строим новый массив из элементов хеш-массива

{
    my @temp = %person;
    print "@temp\n";
}

Ввод и вывод в файлы править

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

Во всех этих случаях мы должны открыть файл в некотором режиме и связать его с дескриптором. Дескриптор — это специальный программный объект, который предоставляет интерфейс ввода/вывода системы, через который мы пишем/читаем файл. Одна программа может открыть за раз много дескрипторов (все зависит от ограничений, накладываемых системой на процесс), причем один дескриптор в один момент может быть связан только с одним файлом. Тем не менее, допускается один и тот же дескриптор перепривязывать на другие файлы.

В этой главе мы рассмотрим основные вопросы записи и чтения в файлы. Эти вопросы являются базовыми для любых типов файлов.

Файловые дескрипторы править

С точки зрения Perl, дескриптор — это символическое имя в программе, представляющее простой файл, файл устройства, сокета или программного канала. В программе Perl дескриптор файла чаще всего открывается функцией open(), которой передается имя дескриптора, режим доступа и путь к файлу в файловой системе. Например

open(FILE, "> /tmp/file.data");

создает дескриптор FILE и присоединяет его к файлу /tmp/file.data. В данном случае файл открывается только на запись, о чем говорит символ >. Символ режима открытия файла не обязательно записывать во втором аргументе, как показано в примере: вообще говоря вы передаете функции список, а дальше он будет проверен на наличие нужных частей:

# Символ режима можно вынести в отдельный аргумент.
open(FILE, '<', "test.txt");
# или так
open FILE, '<', "test.txt"; # Потому что это именованная списковая операция.

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

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

$ref_file = *FILE;
print $ref_file "Hello\n"; # Печатаем в файл

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

Любая программа Perl неявно открывает три дескриптора:

  • STDIN — связывается со стандартным устройством ввода;
  • STDOUT — связывается со стандартным устройством вывода;
  • STDERR — связывается со стандартным устройством вывода (как правило вывод через него не буферизуется).

По умолчанию STDIN пользуется операция <>, простые функции вывода используют STDOUT, а STDERR, например, пользуется функция warn().

Стандартные дескрипторы допускается переоткрывать, но сделать это можно только один раз. При этом восстановить предыдущее состояние через open() будет невозможно. Чтобы восстанавливать дескрипторы, их можно дублировать, о чем будет рассказано ниже.

Открытие/закрытие дескриптора править

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

open <идентификатор-дескриптора>, <имя-файла>;
open <идентификатор-дескриптора>;

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

Вместо пути можно указать знак тире -, тогда открываемый файл соответствует STDIN, т.е. >- соответствует записи в STDIN. Если стандартный дескриптор до это уже был перенаправлен в некоторый файл, то такой вызов будет ассоциироваться с ним.

Если не указан путь к файлу, то предполагается путь в переменной $<идентификатор-дескриптора>, причем ее имя становится идентификатором дескриптора:

$FILE = "file.dat";
open FILE;  # Предполагается, что путь есть в переменной $FILE.

Любой файл может быть открыт в одном из режимов:

  • < (чтение);
  • > (запись);
  • >> (дозапись).

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

Чтобы открыть файл одновременно на чтение и на запись, перед знаком режима нужно поставить плюс +, т.е. +>, +>>, +<.

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

sysopen <идентификатор-дескриптора>, <имя-файла>, <флаги> [, <umask>];

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

  • O_RDONLY — только чтение;
  • O_WRONLY — только запись;
  • O_RDWR — чтение и запись;
  • O_CREAT — создать файл, если не существует;
  • O_EXCL — завершить вызов с ошибкой, если файл существует;
  • O_APPEND — режим дописывания;
  • O_TRUNC — очистить содержимое файла перед записью.

Читателю, программировавшему для *nix, данные флаги могут быть знакомы. Дело в том, что Perl не придумывает велосипед и практически в неизменном виде копирует стандартную библиотеку ввода/вывода Си и *nix. Эти флаги представлены двоичными числами, которые можно объединять поразрядным И (|).

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

Функции open() и sysopen() возвращают 0, если удалось открыть дескриптор, и неопределенное значение undef, в противном случае.

Чтобы закрыть дескриптор, следует вызвать функцию

close [<дескриптор>];

Функция возвращает ИСТИНУ, если дескриптор удалось закрыть и выполнить всю системную работу, связанную с ним. Если опустить аргумент, то функция закроет дескриптор, на который последний раз указывала функция select().

Следует отметить, что закрывать дескрипторы явно иногда не обязательно. Если вы пытаетесь открыть новый файл по дескриптору, который уже используется, то перед открытием нового файла close() для старого будет вызвана неявно. Также при завершении Perl-программы все открытые дескрипторы закрываются автоматически. С другой стороны, явное закрытие дескрипторов позволяют выявлять очень редкие системные ошибки, например, когда буферы не сбрасываются на диск, потому что на нем закончилось место. Явное закрытие очищает переменную $., которая хранит последнюю прочитанную запись, тогда как неявное закрытие ее не очищает.

Если функция close() завершается с ошибкой, то ее причина записывается в переменную $!.

Примеры

use Fcntl; # для sysopen

# Примеры с функцией open

open FILE, "< example.txt" or warn "Warning: $!";   # на чтение. Файл должен существовать.
open FILE, "> example.txt" or warn "Warning: $!";   # на запись. Файл будет создан, если его нет.
open FILE, ">> example.txt" or warn "Warning: $!";  # на дозапись. Файл будет создан, если его нет.

open FILE, "+< example.txt" or warn "Warning: $!";  # на чтение и запись. Файл должен существовать.
open FILE, "+> example.txt" or warn "Warning: $!";  # на чтение и запись. Файл очищается.
open FILE, "+>> example.txt" or warn "Warning: $!"; # на чтение и на дозапись.

# Примеры с функцией sysopen

sysopen FILE1, "example1.txt", O_RDONLY, 0666 or warn "Warning: $!";   # на чтение. Файл должен существовать.
sysopen FILE1, "example1.txt", O_WRONLY | O_CREAT | O_TRUNC or warn "Warning: $!"; # на запись
sysopen FILE1, "example1.txt", O_WRONLY | O_CREAT | O_APPEND or warn "Warning: $!"; # на дозапись
sysopen FILE1, "example1.txt", O_RDWR or warn "Warning: $!"; # на чтение и запись. Файл должен существовать
sysopen FILE1, "example1.txt", O_RDWR | O_CREAT | O_TRUNC or warn "Warning: $!"; # на чтение и запись. Файл очищается.

# Примеры с close

close FILE  or warn "Warning: $!";
close FILE1 or warn "Warning: $!";

Дескриптор-дубликат править

Файловый дескриптор можно продублировать, т.е. создать независимую копию. Оба дескриптора будут связаны с одним и тем же файлом, иметь общий файловый указатель, но разные буферы ввода/вывода. Закрытие одного дескриптора не будет влиять на работу другого.

Чтобы продублировать дескриптор, в аргументе с именем файла нужно указать его идентификатор с амперсандом &:

# Без пробелов !
open(NEWSTDOUT, ">&STDOUT"); # NEWSTDOUT - дубликат STDOUT

Дублирование дескрипторов зачастую используется, чтобы сохранить предыдущее состояние. Например так следует поступать со стандартными дескрипторами STDIN, STDOUT и STDERR.

open OLDSTDOUT, ">&STDOUT";
open STDOUT, "> example.txt" or die "$!";

# Временно весь вывод будет направляться в файл
print "Hello, World\n";

# Восстанавливаем дескриптор STDOUT
close STDOUT or warn "$!";
open STDOUT, ">&OLDSTDOUT" or die "$!";
close OLDSTDOUT or warn "$!";

В остальных ситуациях дублирование в общем то бесполезно.

Чтение/запись файлов править

Чтение и запись файлов очень напоминает то, как это делается в обычной программе на языке Си. Можно выделить два подхода:

  • буферизированное чтение/запись. Здесь операции осуществляются высокоуровневыми функциями, использующими буферы ввода/вывода. Использование буферов позволяет уменьшить нагрузку на систему, так как данные не сбрасываются на устройства хранения, пока буфер не заполнится. Кроме того, функции чтения/записи контролируют некоторые нюансы, например что данные действительно записались или прочитались, благодаря чему код становится компактнее;
  • не буферизированное чтение/запись позволяет полностью контролировать всю процедуру чтения/записи. Обычно такой подход используется для записи/чтения двоичных файлов.

Буферизированное чтение/запись править

Операция <> и следующие функции используют буферы ввода/вывода: print, read, seek.

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

Пусть у нас есть файл example.txt со следующим содержимым:

line 1
line 2
line 3
line 4
line 5

За пятой строкой нет перевода строки, а идет сразу конец файла.

open FILE, "example.txt" or die "$!";

$line = <FILE>; # Прочитана первая строка (вместе с символом перевода строки)
print "$line";  # line 1\n

$line = <FILE>; # Прочитана вторая строка
print "$line"; # line 2\n

# Внутренний файловый указатель смещен на первый символ третьей строки

# Дочитываем файл
while (<FILE>) {
    print;
}
# line 3\n
# line 4\n
# line 5\0

# Вернет пустую строку, так как читать больше нечего: внутренний файловый указатель находиться в конце файла.
$line = <FILE>;
print "Empty: $line";

Теперь пусть файл имеет такое содержимое:

a:b:c:d:e:f

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

open FILE, "example.txt" or die "$!";

{  # Используем блок, чтобы поменять $/ в локальной области видимости.
    local $/ = ':';  # Меняем разделитель.
    @lines = <FILE>; # Читаем файл прямо в массив.
}

foreach (@lines) {
    print "$_\n";
}

Результат

a:
b:
c:
d:
e:
f

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

open FILE, "example.txt" or die "$!";
@chunks = split(/:/, <FILE>);
foreach (@chunks) {
    print "$_\n";
}
# a
# b
# c
# d
# e
# f

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

...
foreach (@{[<FILE>]}) {
    print "$_\n";
}

Эта конструкция может показаться на данный момент не понятной, так как конструирование анонимных массивов мы будем обсуждать в отдельной главе. Здесь в анонимный массив, конструируемый операцией [], помещается содержимое файла, читаемое операцией <>. Чтобы операции производились в списковом контексте, мы помещаем конструкцию в еще одни скобки @{}.

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

read <дескриптор>, <переменная>, <длина> [, <смещение>];

Функция read(), читает из файла ровно столько байт, сколько указано в параметре <длина>, и помещает прочитанное в <переменная>. В отличие от <>, эта функция возвращает действительное значение прочитанных байт; 0 — если чтение достигло конца файла, и неопределенное значение в случае ошибки. Параметр <смещение> указывает, сколько байт нужно отсчитать в <переменная> и начать вставлять прочитанные данные. Так, отрицательное смещение -n говорит, что в <переменная> нужно отсчитать последние n байтов и к оставшемуся результату добавить прочитанную строку. Положительное смещение, большее чем длина текущей строки в <переменная>, будет создавать пробел из символа \0, после которого будет вставляться прочитанная строка. По умолчанию смещение равно нулю, что означает затирание предыдущих данных после чтения.

На практике файлы с помощью read() читаются примерно так:

open FILE, "example.txt" or die "$!";

$buffer_size = 1024;

while (read (FILE, $data, $buffer_size) > 0) {
    print "$data";
}

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

open FILE, "example.txt" or die "$!";

while ($data = getc(FILE)) {
    print "$data"; # Читаем файл символ за символом
}

Мы уже не один раз упомянули о внутреннем указателе файла, который является подобием виртуальной каретки (как в печатной машинке). Любая операция чтения/записи над файлом приводит к смещению этого указателя на число прочитанных/записанных байт. Этим указателем можно управлять с помощью функции seek() из модуля IO::Seekable:

seek <дескриптор>, <смещение-указателя>, <точка-отсчета>;

# Под точкой отсчета понимается одна из предопределенных констант:
# SEEK_SET (0) — от начала файла.
# SEEK_CUR (1) — с текущей позиции.
# SEEK_END (2) — с конца файла.

В следующем примере мы читаем посимвольно один файл с единственной строкой "abcdef" справа налево и записываем прочитанные символы в другой файл.

use IO::Seekable; # Для seek

# Генерируем входящий файл таким нехитрым способом.
qx {
    echo -n "abcdef" > "in.txt"
};

open IN, "in.txt" and open OUT, ">out.txt" or die "$!"; # Открываем файлы

seek IN, -1, SEEK_END;  # Смещаем указатель файла на позицию перед последним символом.
while ($character = getc(IN) and tell(IN) > 1) {  # Читаем файл справа налево, пока не упремся в начало файла.
    print OUT $character;    # Печатаем в другой файл прочитанный символ.
    seek IN, -2, SEEK_CUR;   # Смещаем указатель на две позиции налево относительно текущей позиции.
}
print OUT $character;        # Печатаем первый символ, так как условие выхода в цикле составлено так.

Здесь впервые встречается функция tell(), которая возвращает текущую позицию файлового указателя, начиная с нуля.

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

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

Не буферизированное чтение/запись править

Следующий функции реализуют не буферизированное чтение/запись:

# Чтение
sysread <дескриптор>, <переменная>, <длина> [, <смещение>];

# Запись
syswrite <дескриптор>, <переменная>, <длина> [, <смещение>];

# Управление файловым указателем
sysseek <дескриптор>, <смещение>, <точка-отсчета>;
  • Значения всех аргументов аналогичны их буферизованным аналогам. Основной разницей является то, что для реализации tell(), нужно использовать sysseek():
    $position = sysseek <дескриптор>, 0, 1; # Вернет текущую позицию
    
  • Операции чтения/записи при не буферизованном чтении/записи не всегда могут завершаться успешно. Вы всегда должны сверяться с тем, что возвращает функция чтения/записи (напомним, что они возвращают число прочитанных/записанных байтов).
  • Нельзя смешивать буферизованное и не буферизованное чтение/запись для одного и того же файла. Подобная практика может приводить к непредсказуемым коллизиям.

Пример

$total_read = 0;
open FILE, "< example.txt";

$bufsize = 1024;

while (my $read = sysread(FILE, $buffer, $bufsize)) {
    print "$read\n";
    $total_read += $read;
}

print "Total read: $total_read\n";

Операции с файлами править

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

chmod <флаги-доступа>, <список-файлов>;
Аналогична *nix утилите chmod: выставляет флаги доступа для владельца файла, группы пользователей и всех остальных. Флаги доступа обычно задаются восьмеричным числом (именно числом, а не строкой) в первом аргументе, после чего нужно передать список файлов. Возвращает число, которое означает число файлов, для которых процедура прошла успешно.
chown <идентификатор-пользователя>, <идентификатор-группы>, <список-файлов>;
Аналогична *nix утилите chown: меняет владельца и группу файла. В отличие от системной утилиты, идентификаторы нужно указывать числом. Значение -1 интерпретируется многими системами, как ничего не менять. Возвращает число, которое означает число файлов, для которых процедура прошла успешно.
link <исходный-файл>, <новый-файл>;
symlink <исходный-файл>, <новый-файл>;
Создают соответственно жесткую и символическую файловую ссылку. В случае жесткой ссылки возвращается ИСТИНА или ЛОЖЬ, в зависимости от успеха операции. В случае символической ссылки, возвращается 1 в случае успеха и 0 — в случае провала. Удалить созданные ссылки на файлы можно функцией unlink(), которой нужно передавать список из файлов. Данная функция удаляет одну ссылку на каждый файл, переданный в списке. Если ссылки на файл не существует, то удаляется сам файл. Функция unlink() возвращает число, которое выражает для скольки файлов из переданного списка операция удалась успешно.
rename <старое-имя>, <новое-имя>;
Переименовывает файл, заданный первым аргументом, на имя, во-втором аргументе. В случае успеха функция возвращает 1 и 0 — в случае провала.
truncate <дескриптор>, <длина>;
truncate <имя-файла>, <длина>;
Усекает файл до заданной длины. Значение длины может быть как меньше текущего размера файла, так и больше. В случае успешного усечения возвращается ИСТИНА, иначе — неопределенное значение undef.
stat <дескриптор>;
Возвращает структуру индексного дескриптора (inode) для файла.
my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,
    $atime,$mtime,$ctime,$blksize,$blocks) = stat($filename);

# Здесь:
# dev — номер устройства в файловой системе.
# ino — номер индексного дескриптора.
# mode — тип и права доступа для файла.
# nlink — число жестких ссылок. Если нет, то равно 1.
# uid — числовой идентификатор владельца файла.
# gid — числовой идентификатор группы.
# rdev — идентификатор устройства (только для специальных файлов).
# size — размер файла в байтах.
# atime — время последнего обращения к файлу с начала эпохи.
# mtime — время последнего изменения файла с начала эпохи.
# ctime — время изменения индексного дескриптора с начала эпохи.
# blksize — предпочтительный размер блока для операций ввода/вывода.
# blocks — фактическое количество выделенных блоков для размещения файла.
Отметим, что не все поля структуры поддерживаются в разных операционных системах. В списковом контексте функция возвращает не пустой список в случае успеха. Если вместо дескриптора передать нижнее подчеркивание stat(_), то функция возвращает закэшированное значение последнего удачного вызова. Данную функцию можно использовать для файлов, ссылок и каталогов. Для получения информации о символических ссылках используется функция lstat(), для которой все аналогично. Если система не поддерживает символические ссылки, то вызов lstat() аналогичен вызову stat().

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

  • -r — файл может читаться эффективным uid/gid;
  • -w — в файл может записывать эффективный uid/gid;
  • -x — файл может быть исполнен эффективным uid/gid;
  • -o — эффективный uid/gid является владельцем файла;
  • -R — файл может читаться действительным uid/gid;
  • -W — в файл может писать действительный uid/gid;
  • -X — файл может быть исполнен действительным uid/gid;
  • -O — действительный uid/gid является владельцем файла;
  • -e — файл существует;
  • -z — размер файла равен нулю;
  • -s — размер файла не равен нулю (размер возвращается как результат);
  • -f — файл является обычным;
  • -d — файл является каталогом;
  • -l — файл является символической ссылкой;
  • -p — файл является именованным FIFO каналом или дескриптор связан с таким каналом;
  • -S — файл является сокетом;
  • -b — файл является блочным устройством;
  • -c — файл является символьным устройством;
  • -t — дескриптор связан с терминалом;
  • -u — у файла поднят флаг setuid;
  • -g — у файла поднят флаг setgid;
  • -k — у файла установлен sticky bit;
  • -T — файл является текстовым;
  • -B — файл является бинарным (двоичным);
  • -M — возраст файла в днях на момент выполнения программы;
  • -A — возраст файла в днях с последнего обращения к файлу;
  • -C — возраст файла в днях для времени последней модификации индексного дескриптора файла.

Унарные операции применяются к строке, содержащей имя файла; к выражению, которое вычисляет имя файла, или к файловому дескриптору. Если параметр не задан, то проверяется значение в переменной $_. В случае ИСТИНЫ возвращается 1. В случае ЛЖИ возвращается пустая строка, а в случае, если файла не существует, возвращается значение undef.

Операции -T и -B работают следующим образом. Если первый блок файла содержит более 30% необычных символов (управляющие последовательности или байты с установленными старшими битами), то файл признается двоичным, иначе он признается текстовым. Если эти операции применяются к дескриптору, то проверяется не файл, а буфер ввода/вывода. Обе операции возвращают ИСТИНА, если связанный с дескриптором файл пуст или внутренний указатель находится в конце файла.

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

Операции с каталогами править

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

Для работы с каталогами в Perl предусмотрены следующие функции.

opendir  <дескриптор>, <имя каталога>;
closedir <дескриптор>;
readdir <дескриптор>;
Соответственно открывает/закрывает/читает каталог. Допускается, чтобы дескрипторы каталогов и файлов имели одинаковые имена в одно и то же время (внутри Perl разберется что к чему), однако, вряд ли вы захотите так сделать, так как это запутает того, кто будет читать программу. Функция readdir() для открытого каталога в списковом контексте возвращает список имен всех файлов или пустой список, если имена уже были прочитаны. В скалярном контексте функция возвращает имя очередного файла в каталоге или неопределенное значение undef, если все имена уже прочитаны. Функцией rewinddir(<дескриптор>) можно установить текущую позицию каталога в начало, чтобы повторно запросить имена файлов в нем. Функция readdir() возвращает всегда относительное имя файла: абсолютное имя вы должны формировать самостоятельно.
mkdir <имя-каталога>, <флаги-доступа>;
Создаёт каталог с указанным именем и флагами доступа (в восьмеричной форме). Если задаётся не полное имя каталога, то новый каталог будет создан в текущем рабочем каталоге. Если каталог был создан, возвращает ИСТИНУ, иначе — ЛОЖЬ. В случае ошибки в специальной переменной $! будет записана причина.
chdir [<дескриптор>];
Делает указанный каталог рабочим. Если каталог опущен, то рабочем становится каталог указанный в переменной окружения под именем HOME или LOGDIR, когда HOME не определена.
rmdir [<имя-каталога>];
Удаляет указанный каталог. Если переменная не указана, то берется значение из переменной $_. В случае успеха возвращает ИСТИНУ и ЛОЖЬ в противном случае, записывая причину ошибки в переменную $!. Эта функция удаляет только пустые каталоги. Чтобы удалить не пустой каталог, нужно его сначала очистить ото всех файлов.

Пример работы с каталогом.

opendir DIR, "/bin" or die "$!";
# Печатает все файлы не являющиеся каталогами.
while ($name = readdir DIR) {
    next if -d $name;
    print "$name\n";
}
closedir DIR;



← Операторы Форматированный вывод →