Основы Perl — ввод/вывод, файлы, каталоги и глобы

С момента публикации моего предыдущего урока программирования на Perl прошло уже более двух месяцев. И вот она — долгожданная последняя часть!

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

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

Ввод и вывод

Как всегда, давайте начнем с примера:

1
2
3
4
5
6
7
8
#!/usr/bin/perl
use strict;

while(my $line = <STDIN>) { # читаем из файлового дескриптора STDIN
  chomp($line); # отсекаем символ \n
  last if($line eq "exit");
  print "Вы ввели '$line'. Для выхода из цикла введите 'exit'\n";
}

Рассмотрим четвертую строку. STDIN — это файловый дескриптор, связанный со стандартным потоком ввода. Чтение из файла производится с помощью оператора «треугольные скобки». Да, это не так привычно, как readln, но такая запись тоже имеет право на жизнь, тем более, что она короче.

Функция chomp проверяет, находится ли в конце строки-аргумента символ новой строки (\n) и если он присутствует, отсекает его. Если такого символа нет, строка остается без изменений.

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

print STDOUT "Вывод строки в стандартный поток вывода\n";
print STDERR "Вывод строки в стандартный поток ошибок\n";

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

Приведенный выше скрипт можно немного «ужать» за счет использования переменной $_. Напоминаю, что впервые мы столкнулись с ней во втором уроке.

1
2
3
4
5
6
7
8
#!/usr/bin/perl
use strict;

while(<STDIN>) { # результат чтения сохраняется в переменной $_
  chomp; # отсекаем символ \n в переменной $_
  last if($_ eq "exit");
  printf "Вы ввели '%s'. Для выхода из цикла введите 'exit'\n", $_;
}

Заметьте также, что функция print была заменена на printf, которая должна быть хорошо знакома программистам на Си. Я полагаю, что вы уже знакомы с форматированным выводом. Если это не так — ничего страшного, всю необходимую информацию можно получить, выполнив команды «perldoc -f printf» и «perldoc -f sprintf».

Файлы — чтение и запись

Чтение и запись в случае с файлами производится точно так же, как чтение из STDIN и запись в STDOUT или STDERR. Единственное, о чем следует позаботится — это создать дескриптор, связанный с заданным файлом.

1
2
3
4
5
6
7
8
#!/usr/bin/perl
use strict;
# создаем файловый дескриптор
open FID, "input.txt"
  or die "Failed to open input.txt: $!\n";
my $i = 0;
print "line ".++$i.": $_" while(<FID>);
close FID; # закрываем файловый дескриптор

Собственно создание файлового дескриптора происходит на четвертой строчке. Функция open принимает два аргумента — дескриптор и имя файла. В случае ошибки, например, если файла с именем input.txt не существует, функция вернет false, а сообщение об ошибке будет записано в переменную $!.

Но что делает оператор «или» после вызова open? Конструкция вида «функция1 or функция2″ встречается в perl-скриптах довольно часто, так что давайте разберемся, как она работает.

Будем считать, что функция1 и функция2 возвращают ложь в случае ошибки и истину иначе. Сначала выполняется функция1. Если она завершается успешно, будет возвращено истинное значение и интерпретатору perl следует вычислить «истина или (что-то, что вернет функция2)». Но «истина или ложь» = «истина или истина» = «истина». Другими словами, в этом случае результат не зависит от того, что вернет функция2, а значит не следует тратить время на ее выполнение.

Если же функция1 завершится с ошибкой и вернет «ложь», то результат «ложь или функция2″ напрямую зависит от того, что вернет функция2, значит в этом случае интерпретатор будет обязан ее вызвать. Таким образом конструкция «функция1 or функция2″ означает «вызвать функцию1 и если она вернет ложь (и только в этом случае!) вызвать функцию2″.

Значит, если вызов open завершится неудачно, будет вызвана функция die, которая делает что? Как несложно догадаться по ее названию — выводит сообщение об ошибке и завершает работу скрипта. Остальная часть примера настолько тривиальна, что я не вижу смысла ее описывать.

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

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/usr/bin/perl
use strict;
# пример 1
open FID, ">output1.txt"; # один знак "больше"
  or die "Failed to open output1.txt: $!\n";
print FID "111\n"; # обратите внимание - запятой нет
close FID;

# пример 2
open FID, ">>output2.txt"; # два знака "больше"
  or die "Failed to open output2.txt: $!\n";
print FID "222\n";
close FID;

В первом случае, если файл output1.txt уже существует, он будет очищен, иначе — файл output1.txt будет создан. Во втором случае, если файл output2.txt уже существует, запись будет производится в его конец, иначе также будет создан новый файл. Чтобы почувствовать разницу, можете несколько раз запустить этот скрипт и посмотреть, что будет записано в каждом из файлов.

Здесь мы рассмотрели лишь самые распространенные способы вызова функции open. Вывод команды «perdoc -f open» занимает 19 килобайт. Это не случайно, ведь функцию open можно вызывать с тремя аргументами, с ее помощью можно сохранять файловые дескрипторы в скалярных переменных и даже перенаправлять данные в уже открытых дескрипторах (см строки 38-40 в этом примере). Разумеется, в рамках одной заметки я не могу рассказать о всех возможностях этой функции (более того — скорее всего я их всех и не знаю), так что можете считать чтение соответствующей документации домашним заданием.

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

1
2
#!/usr/bin/perl
print while(<>); # аналогично print $_ while($_ = <>);

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

Работа с каталогами

Самый простой способ получить имена всего, что есть в каталоге — это воспользоваться функциями opendir и readdir:

1
2
3
4
5
6
7
#!/usr/bin/perl
use strict;
opendir DIR, "/home/afiskon" or die $!;
while(my $fname = readdir DIR) {
  print "$fname\n";
}
closedir DIR;

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

1
2
3
4
5
#!/usr/bin/perl
use strict;

my @flist = glob "~/*.txt";
print "$_\n" for(@flist);

Функция glob возвращает список файлов, как это сделала бы оболочка csh. Тильда означает домашний каталог, звездочка — любую последовательность символов. Также можно использовать знак вопроса, фигурные и квадратные скобки.

Существует и более короткая запись:

my @flist = <~/*.txt>; # полностью эквивалентно glob "~/*.txt"

А еще можно так:

print "$_\n" while(<~/*.txt>);

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

Другие операции над файлами и каталогами

Эта заметка была бы не полной, если бы я не рассказал, как в Perl создать/удалить каталог или определить размер файла. Но не беспокойтесь, этот раздел очень простой. Фактически сейчас моя задача — привести список функций и операторов, чтобы в случае необходимости вы знали, в какой мануал заглянуть (вы ведь уже запомнили команду «perldoc -f имя_функции», верно?) :

# удаление одного или нескольких файлов
# функция возвращает число успешно удаленных файлов
unlink $file1, $file2, $file3;

# а еще можно так:
unlink glob "~/*.core";

# переименовать или переместить файл
rename $old, $new;

# создать каталог, второй аргумент представляет
# собой права доступа и является необязательным
mkdir $dir_name, 0755;

# удалить пустой каталог
rmdir dir;

# сменить права доступа к файлу или файлам
chmod 0755, $file1, $file2, $file3;

# сменить владельцев файлов
chown $user, $group, glob "*.txt"

# по моему опыту работать с ссылками приходится не часто
# но на всякий случай вот список имен функций:
# link, symlink, readlink

Также в Perl имеется несколько довольно полезных операторов:

if(-e $fname) { # если файл или каталог существует
  if(-f $fname) { # является файлом
    my $fsize = -s $fname; # получить размер файла
    # сделать с ним еще что-то
  } elsif(-d $fname) { # является каталогом
    my $readable = -r $fname; # доступен на чтение?
    # ....
  } else {
    die "EPIC FAIL\n";
  }
}

С непривычки эти операторы могут показаться странными, но на самом деле они мало чем отличаются от банальных «больше» и «меньше». Полный список операторов этого типа можно посмотреть, выполнив команду «perldoc -f -X».

Заключение

Мои поздравления! Вы только что закончили читать последнюю часть «Основ Perl». Теперь вы знаете, что в этом языке нет ничего мистического — он не то, чтобы невероятно сложен, очень даже читаем, если знаешь матчасть, и хорошо документирован. К сожалению, за кадром остались еще множество вопросов, например взаимодействие процессов, объектно ориентированное программирование, регулярные выражения и многие другие. Если вы намерены продолжить изучение Perl, то я настоятельно рекомендую приобрести книги «Изучаем Perl» и «Perl: изучаем глубже», авторы — Шварц Р. и Феникс Т., пока их еще легко найти.

Я надеюсь, что «основы» помогут вам написать Ваши первые скрипты и разобраться в чужом коде. Со временем у вас будут возникать вопросы «а как мне сделать то-то» и «а что происходит в этом скрипте в строе N». Не ленитесь заглядывать в документацию и просить помощи у более опытных программистов на форумах, в чатах и в тематических сообществах (например, жж-сообществе ru_perl). И рано или поздно вы станете настоящим гуру языка Perl. Удачи!

Дополнение. Рекомендую также посмотреть презентацию «Мифы Perl» Алексея Капранова, представленную на DEVCONF::Perl() 2010.

Дополнение. Некто Тезка совершенно справедливо заметил в комментариях:

А про eval, system, etc не написали, как в прошлой главе обозначили сие намерение.

Верно, но урок и так получился объемный, так что оставляю на самостоятельное изучение. Самое главное, что я рассказал про perldoc.

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

Подпишитесь на блог с помощью RSS, E-Mail, Google+ или Twitter!
Также, пользуясь случаем, приглашаю вас посетить мой форум.

  • http://twitter.com/bacek bacek

    Если что, perlstyle весьма рекомендует пробелы вокруг операторов. Типа «if («, etc.

  • http://eax.me/ Безумный Программист

    А с чем это связано?

  • anonymous

    open FID, «>output1.txt» # один знак «больше»
    Во первых, не используем глобальные переменные, определите свои и используете, так вы не нарвётесь на очень интересные ошибки, которые будете искать дооооооолго.
    во вторых, давно рекомендуют использовать, ТРИ аргумента:
    open $FID,'>',»$output1.txt», почему так лучше уже тоже было описано не раз.
    ну и третье рекомендуют использовать не print STDERR а print {*STDERR} вообще для всякого дескриптора print {*$FID}.

  • http://eax.me/ Безумный Программист

    1 и 2 вы правильно говорите. Но учитывая, с какой целью писалась заметка, чем меньше таких тонкостей описывается, тем меньше шансы запутать читателя.

    На счет 3 первый раз слышу. С чем это связано?

  • anonymous

    Лучше сразу учить правильно.:) Ну а третье, это просто чтобы визуально отделить от остальной строки аргументов функции print

  • http://twitter.com/bacek bacek

    Ну что бы визуально отличать от вызова функций. В шестёрке пробел вообще сделали обязательным (в замену сделали скобки необязательными :).

  • http://eax.me/ Безумный Программист

    Я следую другому правилу — не даю функциям имен if, while и тд :) Но даже если бы и давал, вызов таких функций все равно производится с указанием амперсанда:
    —-8<—-8<—-
    #!/usr/bin/perl

    sub if {
    print join «,», @_;
    }

    &if(1, 2, 3);
    —-8<—-8<—-
    Вот и визуальное отличие оператора от функции. В общем, что-то я сильно сомневаюсь в ценности такого правила…

  • Тезка

    А про eval, system, etc не написали, как в прошлой главе обозначили сие намерение. :)

  • http://eax.me/ Безумный Программист

    Справедливое замечание. Но урок и так получился объемный, так что оставляю на самостоятельное изучение. Самое главное, что я рассказал про perldoc :)

  • Вячеслав

    Извиняюсь, если немного не по теме … Сам я немного занимался perl лет так 8 назад :) Потом перешел на php, лично мне он показался более перспективным и вроде сейчас это подтвердилось. Но смотрю, perl «жил, жив и будет жить». Мне просто очень любопытно, а где же он сейчас востребован так, что ему нет равных (помимо разных скриптов для unix)? Может быть пора вспомнить незаслуженно забытого старичка? ;)

    • Vasilich

      «Но смотрю, perl «жил, жив и будет жить». Мне просто очень любопытно, а где же он сейчас востребован так, что ему нет равных»

      Пишу и на Perl и на PHP. На Perl решаются абсолютно любые задачи, например, я занимаюсь обработкой больших объемов информации, аналитикой и т.п. Perl рулит. В том числе и под Windows где я создаю естественные графические интерфейсы с помощью Win32::Gui. А если бы я узнал про Perl-модуль Mojolicious лет 6 назад — я бы с PHP вообще связываться бы не стал.

  • http://eax.me/ Безумный Программист

    Используется много где. Насколько я знаю, на Perl написаны всякие Mail.ru, Яндекс, Рамблер, LiveJournal и другие проекты. Недавно я защитил диплом, так вот во время защиты студенты называли Perl в качестве используемого ими языка не реже Java и C++. В конце поста есть ссылка на презентацию «Мифы Perl» — там этот вопрос очень подробно раскрыт.

  • Вячеслав

    Да, в конечном итоге, важно чтоб конечный продукт исправно работал. А на чем он написан — личное дело программиста. Хотя приятно видеть что perl живет и количество модулей для него растет. php в области web конечно вылез вперед, но по количеству готовых библиотек ему до перла еще очень далеко.

    PS: А Ваш блог на чем кстати сделан? ;)

  • http://eax.me/ Безумный Программист

    На WordPress. Комментарии работают на системе Disqus.

  • http://twitter.com/the_antoo antoo

    print «$_n» for(@flist);Ну сделайте же это массивом без всяких $_, невозможно понять что за переменные такие потом :)

    • TheAthlete

      print «$_n» for(@flist);
      Кстати, вместо этой записи, можно написать более короче
      say for @list;
      Но эта запись требует версии Perl не ниже 5.10 (в этой версии появилась функция say)