#!/usr/bin/perl -c
Please, listen to, my $words;
seek love, joy, happiness for everything;
study hard and sleep longer if able;
# Perl Poetry, The Sentiment
# (c) http://www.perlmonks.org/?node_id=882536
Пустыня ...
Гордый и величавый стоит у пустыни Perl. Свой путь он начал в далеком 1987 году, обойдя тысячи пустынь за многие лета, он видел как тут и там возникают чудные, ласкающие взгляд оазисы полные водой, как начинались сезоны дождей чередуемые с продолжительной засухой. Пустыни бывают разные, не раз с ним случалась беда, когда неосмотрительный наездник уводил его все глубже и глубже в засушливую пустыню где нет места даже надежде. Напротив бывали случаи, когда посреди пустыни возникали целые мегаполисы полные жизни и людей, казалось что это мираж, но нет — это была реальность. Perl ни когда не переставал удивляться творческому таланту людей. Он не навязывал путь человеку, доверяясь его воле следовал в глубь пустыни к неизвестности. Его разочаровывали1 «дикие» наездники, — люди из больших городов привыкшие передвигаться на сложных машинах. Здесь, в пустыне, даже самая привлекательная колючка не может заставить их сдвинутся вперед хоть на метр. По привычке они берут с собой много хламья, и постоянно подгоняют, заставляя бежать все быстрее и быстрее. Красота пустыни, её философия, проносится мимо них, лишь не многие из них доходят до цели, оставляя за собой всю ту же безжизненную пустыню. Напротив, встречались и другие люди, воспринимавшие пустыню как нечто новое, свободное, безграничное пространство для творчества. Держать путь с такими спутниками всегда было большим удовольствием. Тропа вела их сквозь причудливые, полные жизни островки пустыни. Иногда группа таких людей могла собирать целые караваны верблюдов, не спешно бредущие по пустыне. Пустыня пьянила, и как-то по-особому, магически действовала на не окрепшие молодые умы наездников. Perl часто видел, как бедняги сходили с ума начиная строить песчаные замки, которым не суждено было стать чем-то большим. Другие рисовали на песке не понятные даже ему каракули. Губили их и миражи, которые ни когда не существовали в действительности. К сожалению Perl не мог помочь этим беднягам, как и не мог им отказать в путешествии, ведь в путешествии Perl чувствовал себя прекрасно, для этого он и был рожден.
И вот он перед вами, радостно бьет ногой предлагая совершить увлекательную прогулку сквозь эту пока еще безжизненную пустыню, туда в даль, куда как вам кажется следует идти.
… верблюд2
(*) Философия Perl в том, что бы легко решать простые задачи, сохраняя возможность решать трудные. Это те задачи, которые мы решаем изо дня в день. Не нужно произносить множество предварительных слов, прежде чем сказать то, что вы собираетесь сделать. Perl традиционно включен в поставку практически всех *nix дистрибутивов, что делает старт особенно легким. Фактически уже после установки операционной системы Perl готов к работе (конечно мы говорим о *nix).
(*) При разработке каждой новой версии Perl особое внимание уделяется сохранению обратной совместимости. Что очень важно, учитывая сколько всего написано на Perl.
(*) CPAN. Сеть распространения модулей для Perl, что-то вроде (maven, ivy)/Java, RubyGems, npm/Node.JS. Скатерть-самобранка, кладезь готовых решений на все случаи жизни. На данный момент одна из крупнейших среди языков (более 121 000 модулей). Благодаря CPAN многие сложные задачи решаются тривиальным поиском подходящего модуля. Впрочем, сравнение с maven несколько натянуто, CPAN гораздо проще в плане управления зависимостями. Он не требует предварительной магий по настройке, это больше похоже на apt из Debian/Ubuntu.
(*) Perl компилирует текстовые программы в промежуточное представление перед их интерпретацией. Что позволяет, пусть и не часто, обнаружить ошибки в программе еще на этапе компиляции. Сюда, прежде всего, относится проверка корректности синтаксических конструкций, проверка корректности имен и аргументов подпрограмм (только при наличии прототипа), проверка корректности обращений к полям класса. Конечно это не компилятор C++, но для динамического языка совсем не плохо. Занятно, но кое в чем может оставить позади даже компилятор C++, так компилятор Perl может определять некорректные вызовы математических операции (например: корень из отрицательного числа вызывает ошибку компиляции). Вот несколько примеров:
#!/usr/bin/perl -w
use strict;
my Dog $aDog = new Dog; # Ошибка: нет класса Dog
$aDog->{wings} = '?'; # Ошибка (см. ООП): обычно собаки без крыльев
feed $aDog; # Ошибка: прототип функции не объявлен
sqrt -2; # Ошибка: корень из отрицательного числа
Perl всегда следует своему принципу, и ничего не стоит отключить большинство этих проверок, впрочем об этом далее.
(*) Прототипы функции позволяют заранее объявлять подпрограммы и список их аргументов, что-то на подобии forward declaration в С++, при наличии прототипа компилятор проверяет правильность вызова подпрограмм, и кроме того, не требует парных скобок при вызове подпрограммы.
sub feed($$);
feed 'Sharik', 'Fruit'; # Ok
feed 'Sharik'; # Ошибка: не достаточно аргументов
feed 'Sharik', 'Apple', 'Fruit'; #Ошибка: много аргументов
sub feed($$) {
print shift, ' eat ', shift, "n";
}
(*) $@%. Perl разделяет переменные по назначению: '$' скаляр (число, строка, ссылка), '@' массив (последовательность скаляров), '%' хэш (ассоциативный массив скаляров). Как видно классические примитивные типы представлены скаляром. Однако, они строго отделены от контейнеров, что позволяет компилятору выполнять дополнительные проверки, спасая нас от хлопот. Свои типы Perl вводить не позволяет (для ООП введены поблажки — любой из встроенных типов можно благословить в класс). Благодаря столь четкому разделению, в одной и той же области можно иметь не конфликтующие переменные всех типов:
#!/usr/bin/perl -w
use strict;
my $solarSystem = 'Mercury Venus Earth Mars Jupiter Saturn Uranus Neptune';
my @solarSystem = qw(Mercury Venus Earth Mars Jupiter Saturn Uranus Neptune);
my %solarSystem = (haveLife => 1, havePerl => 1);
sub solarSystem() {
print 'Mercury Venus Earth Mars Jupiter Saturn Uranus Neptune';
}
Каждая операция в Perl не разделима связана с контекстом выполнения. Perl понимает три типа контекста: пустой (без контекста), скалярный и списковый (например, присвоение значения скалярной переменной образует скалярный контекст, а присвоение массиву или хэшу — списковый контекст). Подпрограммы Perl способны определять контекст выполнения и соответственно могут вести себя по разному в разных контекстах, что дает простор для творчества.
В языке отсутствует арифметика указателей, однако присутствуют ссылки. Аргументы в подпрограммы передаются по ссылке, но оператор присваивания выполняет копирование самого значения переменной (копируются не только скаляры, но и контейнеры). Таким образом, легко реализуется передача аргументов в подпрограмму по значению. Более того, классическая запись подпрограммы на Perl подразумевает передачу по значению, т.е. не вносит побочных эффектов для своих аргументов.
#!/usr/bin/perl -w
use strict;
sub upper {
my $value = $_[0]; #копирование значения в переменную $value
$value = uc $value; #снаружи ничего не изменилось
$_[0] = $value; #теперь снаружи строка в верхнем регистре
}
my $bigLetters = 'big-letters';
upper $bigLetters;
print $bigLetters, "n"; # печатает BIG-LETTERS
(*) Перегрузка операторов (для редких случаев, когда это бывает нужно). Перегружать можно практически все, кроме пробелов, запятых и пожалуй, я не слышал что кто-то смог перегрузить комментарий. Единственный язык (известный мне), который позволяет перегружать константы (числа, буквы, слова). Это можно использовать, например, для того что бы заменить все числа введенные как константы на эквивалентные объекты представляющие комплексные числа. Или использовать числа неограниченной величины:
# Файл BigMath.pm
package BigMath;
use overload;
use Math::BigInt;
sub import {
overload::constant integer => sub {
Math::BigInt->new(shift)
};
}
1;
# Файл BigMath.pl
{
use BigMath;
my $bigInt = 2123; #создается объект BigInt
print $bigInt->as_bin(); #печатается 0b100001001011
}
{
my $int = 2123;
print $int->as_bin(); #ошибка нет метода as_bin()
}
Perl позволяет перегружать стандартные подпрограммы языка.
(*) Регулярные выражения глубоко интегрированы в Perl. Помимо привычных шаблонов регулярных выражений, Perl поддерживает свои расширения. Например Perl позволяет использовать в шаблонах поиска и шаблонах замены прямой код на Perl:
my $math = '100 + 200 = 300';
$math =~ s/(d+)/$1 * 2/ge; # регулярное выражения умножает все числа на 2
print $math, "n"; #печатает 200 + 400 = 600
Регулярные выражения поддерживают свойства Unicode и даже дозволено вводить свои собственные свойства символов. Позволяется вводить свои аббревиатуры для сложных шаблонов (вроде s, w и пр.) Естественно регулярные выражения в Perl поддерживают как жадный так и не жадный поиск.
(*) Perl при отсутствии ключевого слова return в подпрограмме возвращает последнее вычисленное выражение (похоже это нынче модно). Кроме того при вызове подпрограмм можно опускать скобки, если вы понимаете что делаете.
(*) Блоки в Perl. Блок это пара фигурных скобок {… }, такие конструкции как while, if, for принимают блок, хотя блок может существовать и сам по себе. Самое интересное, что любой блок поддерживает операторы next, last и redo. Т.е. вполне законна запись:
{
print "Yeahn";
last;
print "Unreachable coden";
}
{
print "Forever loopn";
redo;
}
Помимо этого, подпрограммы могут принимать блоки в качестве аргументов, что позволяет богато расширять язык своими синтаксическими конструкциями. Это дает возможность писать красивые самодокументированные программы на Perl, конечно наличие фантазии обязательно.
use strict;
#sub transaction(&$);
#sub update($$$);
#sub set(+);
#sub where(+);
#sub rollback(&);
#sub throw($$);
transaction {
update 'user', set { name => 'caiiiycuk' }, where { id => '1' };
} rollback {
throw 500, "rollback exception";
}
Perl не поддерживает блок try/catch/finally, однако он легко реализуется через подпрограммы принимающие блоки. Именно потому что if/else принимают блок, в Perl не может существовать синтаксическая конструкция if/else if/else, вместо нее используется if/elsif/else. Точно так же отсутствует switch/case, он реализуется тривиально.
(*) Цикл while может определять секцию continue, которая выполняется вне зависимости от того был вызван next, last или redo (т.е. выполняется всегда).
while (1) {
next;
} continue {
print "Yet another loop done n";
}
(*) Три области видимости для переменных: глобальная, лексическая и локальная. Локальная область видимости распространяет значение переменной на текущий блок и на все вызовы подпрограмм из этого блока, полезна для переопределения значений глобальных переменных в конкретном блоке. Локальная область видимости определяется во время выполнения, а лексическая на этапе компиляции (это самая обычная область видимости, внутри блока).
use strict;
our $a = 'global scope'; #глобальная область видимости
sub printLocal {
local $a = 'local scope'; #локальная область видимости (внутри блока и в подпрограммах)
printGlobal();
}
sub printGlobal {
print $a, "n";
}
printGlobal(); #печатает 'global scope'
printLocal(); #печатает 'local scope'
printGlobal(); #печатает 'global scope'
(*) Perl реализует замыкания, радует что синтаксис объявления анонимной подпрограммы не отличается от обычного объявления, как бывает в некоторых других языках.
(*) Аж с 2002 года в Perl поддерживается механизм вычисления константных подпрограмм, и замены мест их вызова на значения вычисленной константы (constexpr).
(*) Удобные операторы проверки файлов (-rwxoRWXOezsfdlpSbctugkTBMAC).
use strict;
if (-e '/tmp' and -d '/tmp') {
print '/tmp is directory', "n";
}
(*) Оператор <>. Выполняет несколько функции. Во-первых, его можно использовать для поиска файлов по шаблону в текущей директории (кстате способ поиска можно менять, по умолчанию используется glob).
print join ',', <*.pl>, "n"; #печатает имена файлов *.pl
Во-вторых, это оператор чтения данных из файлового дескриптора. Причем в скалярном контексте он считывает данные построчно, а в списковом — полностью считывает строки файла в массив.
open F, '<somefile' or die;
my @random = <F>; # считать все строки из файла
close F;
И наконец, пустой оператор <> выполняет чтение из файлов имена которых были переданы в аргументах запуска программы. Делает это не явно, как будто вы работаете с одним большим файлом. Если же программа была запущена без аргументов — выполняется чтение из STDIN. Это очень удобно для разработки программ конвейеров, которые будут объединятся через |, поскольку не нужно заботится об открытии и закрытии файлов. Применив этот оператор, аналог команды cat реализуется в одну строчку.
print while (<>);
(*) Полезная умолчательная переменная $_. Большинство стандартных подпрограмм и операторов Perl умеют работать с ней, предполагают её использование в случае если входной аргумент функции опущен. Иногда это позволяет писать более лаконичный код.
use strict;
while (<>) {
print if (s/^prints*(.*)/$1/);
}
# эквивалентно
while ($_ = <>) {
print $_ if ($_ =~ s/^prints*(.*)/$1/);
}
(*) В Perl результатом присваивания является lvalue. Поэтому в Perl возможно менять значение во время присваивания.
($new = $old) =~ s/bad/good/g;
##
chomp($answer = <STDIN>);
##
if ( ($k, $v) = $string =~ m/(w+)=(w+)/ ) {
print "key: $k value: $v", "n";
}
(*) Оператор инкремента примененный к строке возвращает следующею перестановку из набора символов.
my $a = 'aa';
print ++$a, "n"; # печатает ab
(*) Автоматическое расширение хэшей и массивов на произвольную глубину. Т.е. при обращении к хэшу по ключу который еще не был добавлен, автоматически создается этот элемент. Причем Perl угадывает тип элемента который нужно создать, например он может создать анонимный хэш, анонимный массив или скаляр.
my $hash = {};
$hash->{first}->{second}->{third} = 5;
print $hash->{first}->{second}->{third}, "n"; # 5
Интересной особенностью является поведение отрицательных индексов в массивах. Отрицательный индекс выбирает элементы с конца массива.
(*) Строки и кавычки. Приятно что Perl поддерживает ввод многострочных документов в одном присваивании:
my $doc1 =
'There’s More Than One Way To Do It
--
2012';
my $doc2 =<<"_DOC2_";
There’s More Than One Way To Do It
--
2012
_DOC2_
print<<"_DOC3_";
There’s More Than One Way To Do It
--
2012
_DOC3_
Применение оператора =<< позволяет, удобно выполнять операции с многострочным документом в той же строке где выполняется присваивание. Так же этот оператор удобно использовать для передачи документов в функции.
use strict;
sub printAll(@) {
print join '', @_;
}
(my $quote =<<'__QUOTE__')=~s/^s+//gm;
The Road goes ever on and on,
down from the door where it began.
__QUOTE__
printAll(<<'__GOOD__',<<'__BAD__', $quote);
There’s More Than One Way To Do It
__GOOD__
TIMTOWTDI
__BAD__
Perl различает два способа ввода строк — с интерпретацией и без. Если использовать одинарные кавычки ', то строка присваивается переменной как есть (без изменений) даже символы n не преобразуются в переносы строк. С другой стороны если использовать двойные кавычки ", то строка интерпретируются — Perl обнаруживает и заменяет имена переменных на их значения, причем делает это весьма искусно, можно даже осуществить вызов функции прямо из строки.
use strict;
my $car = {
brend => 'Lada',
name => 'Kalina',
drive => sub {
"WEEEEEE!n";
}
};
# Печатает Do you want to drive on $car->{brend} $car->{name}?n
print 'Do you want to drive on $car->{brend} $car->{name}?n', "n";
# Печатает Do you want to drive on Lada Kalina?
print "Do you want to drive on $car->{brend} $car->{name}?n";
# Печатает WEEEEEE!
print "@{[ $car->{drive}->() ]}";
# Выходим
print "@{[ exit ]}";
Впрочем, Perl дает вам право выбора символа для экранирования строк и регулярных выражений.
print q {
Мама мыла раму!
};
(my $text = "a b c") =~ s{s}{}g;
Еще одной очень полезной особенностью Perl является поведение строк при использовании ` в качестве символа экранирования строки. В этом случае Perl выполняет эту строку в оболочке и возвращает результат выполнения команды.
print `uptime`; # печатает время работы
print << `EOC`; # выполнить команды
pwd
ls -l
EOC
(*) Perl поддерживает интересный механизм атрибутов подпрограмм. Можно провести аналогию с аннотациями из Java. Атрибуты указываются в объявлении подпрограммы через двоеточие. Можно объявлять сколько угодно многопользовательских атрибутов. Так же есть встроенные атрибуты method и lvalue. Замечательный атрибут lvalue позволяет подпрограмме учавствовать в операциях как lvalue. Так, например, можно реализовать методы get и set в одной подпрограмме.
my $_name = '?';
sub name(;$): lvalue {
$_name = shift || $_name
}
name = 'caiiiycuk';
print name(), "n";
(*) Замечательный механизм AUTOLOAD позволяет обрабатывать вызовы не объявленных подпрограмм, что часто используется для динамической загрузки программы по частям. Имеется ввиду, что если Perl не находит объявление подпрограммы, то обращается к вызову AUTOLOAD с сигнатурой функции и только если он окажется не удачаным выкинет ошибку. С помощью AUTOLOAD, например, можно добиться боле ясного вызова системных функций, как будто они сами являются операторами Perl (как это делает модуль Shell).
use strict;
sub AUTOLOAD {
my $program = our $AUTOLOAD;
$program =~ s/.*:://;
system($program, @_);
}
sub uptime();
sub echo(@);
uptime;
echo "AUTOLOAD";
(*) tie — механизм подмены внутренней реализации скаляров, массивов и хэшей. С помощью этого механизма можно связать массив или хэш напрямую с базой данных таким образом, что любое чтение и запись из/в него будет автоматически отображаться на базу данных. Другим возможным применением данной технологии может быть подсчет выделенных ресурсов.
(*) Perl поддерживает две модели многопоточности: модель потоков и модель процессов. Модель процессов подразумевает создание нового системного процесса с полной копией данных родительского процесса для параллельной обработки данных. Делается это командой fork, героическими усилиями модель процессов полностью поддерживается в Windows, хотя эта модель распараллеливания больше свойственна для *nix. Голую функцию fork использовать не вполне удобно, в CPAN полно модулей для более легкого использования fork, например код мог бы выглядеть так:
use strict;
use Proc::Fork;
run_fork {
child {
print "Process 1n" while 1;
}
parent {
print "Process 2n" while 1;
}
}
В противоположность модели процессов выступает модель потоков, эти потоки имеют совместный доступ ко всем ресурсам данных, такие потоки больше похоже на потоки Java. Модуль поддержки потоков называется Thread, он предоставляет очень удобную функцию async, которая помогает писать понятный код.
use Thread 'async';
my $thread1 = async {
sleep 5;
return 1;
};
my $thread2 = async {
sleep 2;
return 2;
};
print $thread1->join(), "n";
print $thread2->join(), "n";
Поддерживаются механизмы синхронизации потоков: мьютексы, семафоры, засыпание и пробуждение потоков по условию.
(*) Мощнейшие функции pack/unpack позволяют преобразовывать структуры Perl в массив байт и обратно. Очень полезная вещь при чтении бинарных данных и для их произвольной конвертации.
(*) ООП и Perl, пожалуй это единственная вещь которая вызывает у меня смешанные чувства. Это очень похоже на то что мы имеем в JavaScript нет как таковой специальной поддержки возможностей ООП в языке, тем не менее все парадигмы ООП реализуемы и вполне жизнеспособны. Однако, иногда приходит ощущение какой-то костыльности. Объектом может быть любая структура данных скаляр, массив или хэш. С помощью специальной команды ссылка на структуру данных благословляется в объект (bless), после этого действия Perl знает что данная ссылка это объект. Методы этого объекта следует искать в пакете в который благословилась ссылка. Соответственно не существует классического понятия конструктора, конструктором может быть любая подпрограмма которая возвращает благословленные структуры данных. Perl поддерживает косвенную форму вызова методов объекта, в этой форме на первом месте идет метод затем ссылка на объект и лишь потом аргументы метода. Благодаря этому вызов метода new может выглядеть так как вы ожидаете, однако при вызове обычных методов такая форма записи может показаться странной.
{
package Dog;
sub new {
my $class = shift;
return bless { name => shift }, $class;
}
sub gav {
my $self = shift;
print "$self->{name}: gavn";
}
}
{
my $dog = Dog->new('Mona');
$dog->gav();
}
{
my $dog = new Dog 'Sharik';
gav $dog;
}
ООП в Perl поддерживает множественное наследование. При использовании прагмы use fields существует возможность проверки обращений к полям класса на этапе компиляции с помощью псевдо хэшей, а так же реализации приватных данных класса.
{
package Foo;
use fields qw(foo);
sub new {
my Foo $self = shift;
$self = fields::new($self);
$self->{foo} = 10;
return $self;
}
}
my Foo $var = Foo->new;
$var->{foo} = 42;
# Ошибка: обращение к несуществующему полю
$var->{zap} = 42;
К сожалению методы классов не могут иметь прототипов, поэтому мы теряем возможность проверки корректности вызова методов и передачи аргументов, это прискорбно. Perl поддерживает деструкторы объектов.
(*) Специальная метка __DATA__ указывает на конец скрипта, весь текст после этой метки доступен программе через специальный файловый дескриптор DATA. Это очень удобно для написания небольших тестов, когда данные и сам тест не отделимы друг от друга. Впрочем вся система тестирования в Perl более чем удобна, что присуще всем динамическим языкам.
use strict;
my @content = <DATA>;
print join '', @content;
__DATA__
A
B
C
(*) Огромное число командных флагов Perl позволяют писать максимально короткие однострочники для обработки файлов. Например, флаг -n заключает скрипт в цикл while (<>), который мы рассматривали ранее. Т. е. аналог cat можно написать так:
perl -ne "print;" file1 file2 ... fileN
(*) Очень интересная возможность написания генераторов кода. После компиляции кода программа на Perl представляет из себя граф операторов. Этот граф при нормальном выполнении передается интерпретатору Perl, однако можно изменить это поведение выполнив свои действия с этим графом. Например, можно просто сериализовать весь граф в файл, а затем считать его на другом компьютере и передать на интерпретацию Perl опустив фазу компиляции. Более сложное применение этой технологии — генерация кода на C или C++ из Perl программы. Генераторы B::C и B::CC направлены именно на это, при их использовании Perl программа преобразуется в исходные коды на целевом языке, после этого они компилируются в машинные коды и могут быть запущены без Perl интерпретатора. Помимо генераторов кода в Perl есть так же фильтры кода, они позволяют произвольно преобразовывать исходный код программы перед компиляцией и интерпретацией, вы можете написать полностью свой язык если хотите.
(*) Режим меченных данных. Находясь в этом режиме, Perl предпринимает особые меры предосторожности что бы избежать очевидных и скрытых дыр в безопасности. Принцип прост: нельзя использовать данные полученные из-за пределов выполняемой программы для воздействие на что-либо еще, находящееся за ее пределами. Любые данные, поступающие в программу извне, становятся «помеченными» как данные сомнительного происхождения. Меченные данные нельзя использовать прямо или косвенно в любой операции, которая вызывает «оболочку», а также в любых операциях модифицирующих файлы, каталоги или процессы. Поэтому удается избежать множество проблем с безопасностью. Применение режима меченных данных является хорошим тоном при запуске скриптов на сервере. Есть модули которые идут еще дальше в повышении безопасности, например, модуль Safe.
#!/usr/bin/perl -T
use strict;
print "Enter file/directory name to delete: n";
my $file = <STDIN>;
# Ошибка: режим меченных данных, в оболочку передаются
# не безопасные аргументы
`rm -rf $file`;
(*) PSGI/Plack — это спецификация, предназначенная для отделения среды веб-сервера от кода веб-фреймворка. Говоря проще, это что-то вроде сервлет контейнера из мира Java. Пишим веб приложение с использованием этой спецификации и запускаем его на любом из серверов поддерживающих PSGI, коих великое множество. Подробнее.
(*) А еще Perl это море приятных эмоций, чего стоит только набор модулей ACME. Например, модуль ACME::EyeDrops позволяет преобразовывать Perl программы в ASCII арт который работает. Есть модуль фанатов баффи, при использовании которого программы могут быть написаны только с использованием слова buffy. Ну а модуль ACME::ChuckNorris говорит сам за себя. Кроме того существует поэзия Perl — это валидная синтаксическая программа сточки зрения Perl, и поэзия с точки зрения человека одна из таких в заглавии этой статьи.
Счастливого пути!
1 Так думает мой Perl, ваш может думать иначе TIMTOWTDI
2 Интересные на мой взгляд возможности Perl
Автор: Caiiiycuk