Интересные примеры на Perl

Около месяца собирал разные «хаки» на языке программирования Perl. Эта подборка наглядно демонстрирует, как в Perl одна-две строчки кода могут сделать больше, чем десять строк в каком-нибудь другом языке программирования.

Дополнение: См также Основы программирования на Perl.

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

sub inarr {
  grep {$_ == $_[0]} @{$_[1]}; # важно: для строк использовать eq
}

2. Удалить из массива @arr элементы, которые есть в массиве @skip.

my @rslt = grep {
  my $t = $_;
  ! grep { $_ == $t } @skip; # важно: для строк использовать eq
} @arr;

3. Скрипт замены строк в тексте.

1
2
3
4
5
6
7
8
9
10
11
#!/usr/bin/perl

while(<>) {
  chomp;
  last if($_ eq "==end==");
  /^([^=]*)=(.*)$/ and $r{$1} = $2;
}

$d = join '', <>;
$d =~ s/$_/$r{$_}/g for (keys %r);
print $d;

4. Вывести список имен файлов и каталогов в заданной директории, отсортированный по дате последнего доступа. Обычно глобы сортируют список по имени файлов и каталогов. Для сортировки по дате последнего изменения, заменить цифру 8 на 9.

print join "\n", sort {(stat $a)[8] <=> (stat $b)[8]} glob "./*";

5. Удалить повторяющиеся элементы в массиве.

my %cnt; # будет содержать число повторений элементов
@links = grep { ! $cnt{$_}++; } @links;

То же самое с помощью List::MoreUtils.

use List::MoreUtils qw/uniq/;
# ...
@links = uniq @links;

6. Перемешать элементы массива.

for(0..$#links) {
  my $j = rand(@links);
  @links[$_,$j] = @links[$j,$_];
}

Сделать то же самое с помощью List::Util.

use List::Util qw/shuffle/;
# ...
@links = shuffle @links;

7. Выбрать случайный элемент в массиве можно как минимум двумя способами. Можно перемешать элементы, как в предыдущем примере, и выбрать нулевой, а можно в одну строчку:

$rand = $links[rand(@links)];

8. Аналог PHP функции urlencode.

$url =~ s/([^a-zA-Z0-9\%\&\?\:\;\/\=\.\,\#\-\_]{1})/sprintf("%%%02x",ord($1))/eg;

Но лучше использовать URI::Escape.

use URI::Escape;

# uri_escape, uri_unescape

9. Получить все строки файла.

my @lines = split "\n", `cat $0`;

10. Простой многопоточный обработчик.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#!/usr/bin/perl

use strict;
use threads;
use threads::shared;
use List::Util qw/shuffle/;
use constant THREADS_NUM => 8;

my $cnt :shared = 0;
my @threads;
my @lines;

while(<>) { chomp; push @lines, $_; }
@lines = shuffle @lines;
push @threads, threads->create(\&thread_func, $_) for(1..THREADS_NUM);
$_->join for(@threads);

sub thread_func {
  my ($thid) = @_;
  my $my_cnt;
  while(1) {
    { lock($cnt); $my_cnt = $cnt++; }
    last if($my_cnt >= @lines);
    parse_line($thid, $lines[$my_cnt]);
  }
}

sub parse_line {
  my ($thid, $line) = @_;
  print "thid = $thid, line = $line\n";
  sleep(rand(3));
}

Для выполнения этого кода Perl должен быть собран с поддержкой потоков. У меня под FreeBSD этой поддержки не оказалось, а после пересборки перла потребовалось также обновить некоторые CPAN-модули. Будьте внимательны.

11. Работа с временными файлами:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#!/usr/bin/perl

# работа с временными файлами в Perl
# (c) Alexandr A Alexeev 2011 | http://eax.me/
# подробности см в 'man File::Temp'

use strict;
use File::Temp;

# сгенерировать имя временного файла в заданном каталоге
# с указанным префиксом
my $tmp_fname = File::Temp::tempnam("./tmp", "myprfx");
print "tmp_name = $tmp_fname\n";

# получить имя временного файла в каталоге /tmp
my $tmp_fname2 = tmpnam();
print "tmp_name = $tmp_fname2\n";

# получить хэндл временного файла в каталоге /tmp
my $fh = tmpfile();
close $fh;

# получить имя и хэндл временного файла
my ($th, $tname) = tmpnam();
print "tname = $tname\n";
close $th;

12. Вырезаем HTML теги + пример работы с юникодом:

use strict;
use utf8;
use HTML::Entities;

# ...
utf8::decode($_);
s/<[^>]*>//g;
decode_entities($_);
utf8::encode($_);

См также HTML::Strip.

13. Кроссплатформенное считывание файла в одну строку или в массив строк.

use File::Slurp;
# ...
my $data = read_file($filename);
my @lines = read_file($filename);

# удаляем символы новой строки
@lines = map { chomp; $_; } @lines;

14, 15, 16 … Coming soon?

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

  • maard

    брр
    1. зачем inarr, если он полностью дублирует grep?
    4. а чем не подходит ls -t ? (disclaimer: под windows установлен MSYS/MinGW, поэтому имею всё те же unix comman line tools).
    5. мы строим промужуточный список, что затратно. ++$cnt{$_} for @links — и короче и экономичнее.

  • maard

    брр
    1. зачем inarr, если он полностью дублирует grep?
    4. а чем не подходит ls -t ? (disclaimer: под windows установлен MSYS/MinGW, поэтому имею всё те же unix comman line tools).
    5. мы строим промужуточный список, что затратно. ++$cnt{$_} for @links — и короче и экономичнее.

  • http://eax.me/ Eax

    Спасибо за ваш комментарий.
    1. для многократного использования
    4. а если нужно отсортировать по другому критерию?
    5. в perl, как всегда, есть много способов сделать одно и то же :)

  • Саня

    Почитал Ваш пост http://habrahabr.ru/blogs/perl/80328/
    Главный недостаток Perl в том, что это wtite-only язык:
    http://en.wikipedia.org/wiki/Write-only_language

    Просто ради интереса сделал те же задачки на Python.

    #!/usr/bin/env python
    # coding=utf-8

    # 1. Проверить, существует ли элемент в массиве
    el = 10
    arr = [10, 20, 30, 40]
    print el in arr

    # 2. Удалить из массива arr элементы, которые есть в массиве skip
    skip = [30, 40, 50, 60]
    arr = [i for i in (set(arr) - set(skip))]
    print arr

    # 3. Скрипт замены строк в тексте
    import re
    p = re.compile(r'd+')
    print re.sub(p, 'a number', '2 * 2 = 4')

    # 4. Вывести список имен файлов и каталогов в заданной директории,
    # отсортированный по дате последнего доступа.
    import os
    from stat import ST_ATIME
    names = [(name, os.stat(name)[ST_ATIME]) for name in os.listdir('.')]
    names.sort(key=lambda x: x[1])
    print names

    # 5. Удалить повторяющиеся элементы в массиве.
    arr1 = [1, 2, 3, 1, 3]
    arr2 = [i for i in set(arr1)]
    print arr2

    # 6. Перемешать элементы массива
    import random
    a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    random.shuffle(a)

    # 7. Выбрать случайный элемент в массиве
    print random.choice(a)

    # 8. Аналог PHP функции urlencode
    # http://docs.python.org/library/urllib.html#urll

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

    Является ли Perl write-only языком — это большой вопрос. Писать плохие программы можно на любом языке (с). В частности, обфускация php или html или javascript-кода — достаточно частое явление, что не делает эти языки write-only.

    За примеры на Python спасибо. Сам я на нем не пишу, но подозревал, что аналогичные задачи на Python решаются не хуже, чем на Perl. Рад, что он оправдал мои ожидания :)

  • http://sveshnikov.ru/ Alexey

    На самом деле очень неоднозначные примеры.

    4. Вызывать stat внутри sort{} — это не комильфо. Питоновское решение из комментария выше красивей.

    5. Удалить повторяющиеся элементы в массиве.
    Тут есть побочный эффект — еще и меняется порядок элементов.

    6. Перемешать элементы массива. Долой велосипеды:
    use List::Util;
    @list = shuffle @list

    7. «$rand = $links[int rand() * scalar @links];»
    Ужасный код. Я давно программирую на perl, но мой взгляд споткнулся на этом выражении и вместо того, чтобы думать над общей идеей я начинаю вспоминать приоритеты операций и думать, как это варажение будет восприматься компилятором. В общем, лакончиность есть, красота не появилась (имхо).

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

    Нет предела совершенству. Как говориться, за код, написанный пол года назад, хочется самому себе руки оторвать. Вернусь к этому посту в апреле :)

    Маленькая поправочка:
    # use List::Util;
    use List::Util qw/shuffle/;