Однострочники в Perl
blog

Однострочники в Perl

Изначально эта статья была опубликована в журнале Pragmatic Perl.

Самый стандартный и часто используемый вариант написания кода на Perl — это создание текстового файла с кодом программы. Например, можно создать текстовый файл "hello.pl" вот с таким содержанием:

#!/usr/bin/perl

print "Hello\n";

И можно выполнить код этой программы в консоли:

$ perl hello.pl
Hello

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

$ perl -e 'print "Hello\n"'
Hello

Ключ -e и -E

Собственно говоря, ключ -e — это самый важный ключ для написания однострочников. Все что угодно можно сделать только с помощью этого ключа. Все остальные ключи были добавлены исключительно для упрощения написания однострочников.

Ключ -e позволяет выполнить код, который находится сразу после этого ключа. Например, сложить 2 числа:

$ perl -e 'print 2+5 . "\n"'
7

Чтобы не писать каждый раз символ для перевода на новую строку "\n", очень удобно использовать вместо оператора "print" оператор "say" — оператор "say" сам делает переход на новую строку, а ещё "say" удобнее писать, так как это слово короче чем слово "print". Но для того, чтобы оператор "say" можно было использовать, нужно сказать:

$ perl -e 'use feature "say"; say 2+5'
7

Если не указать 'use feature "say";', то мы получим ошибку:

$ perl -e 'say 2+5'
Number found where operator expected at -e line 1, near "say 2"
        (Do you need to predeclare say?)
syntax error at -e line 1, near "say 2"
Execution of -e aborted due to compilation errors.

Но писать каждый раз 'use feature "say";' слишком утомительно, поэтому появился новый ключ '-E'. Его можно использовать вместо '-e', и он включит все новые штуки, которые есть в Perl:

$ perl -E 'say 2+5'
7

Ключ -M

Perl — замечательный язык, очень много в нем есть из коробки, но Perl очень силен тем что у него есть куча библиотек. Например, вот однострочник который скачивает веб-страницу, считает сколько на этой странице символов и выводит это число:

$ perl -E 'use LWP::Simple; say length get("http://ivan.bessarabov.ru")'
6096

Ключ -M позволяет упростить подключение библиотеки к однострочнику. Вот как выглядит эта же команда с ключем -M:

$ perl -MLWP::Simple -E 'say length get("http://ivan.bessarabov.ru")'
6096

Вот еще один пример. Хочу вывести десятый элемент последовательности Фибоначчи. Вот как выглядит однострочник без ключа '-M' (модулю Math::Fibonacci нужно явно указывать, какие функции надо добавить в пространство имен):

$ perl -E 'use Math::Fibonacci qw(term); say term 10'
55

А вот более простой вариант этого однострочника с ключом '-M':

$ perl -MMath::Fibonacci=term -E 'say term 10'
55

Ключ -n

Иногда хочется не просто что-то посчитать-вывести, а как-то обработать вывод других программ. И для этих целей однострочники великолепно подходят.

Например, у нас есть какая-то программа, которые создает некий вывод:

$ echo -e '1\n2\n3'
1
2
3

Мы хотим преобразовать этот вывод — возвести каждое число в квадрат. Для того чтобы прочитать STDIN в Perl используется бриллиантовый оператор (diamond operator) <>:

$ echo -e '1\n2\n3'| perl -E 'while (<>) { say $_ ** 2 }'
1
4
9

Для того чтобы упростить написание подобных однострочников, был сделан ключ '-n'. При использовании этого ключа код переданный в '-E' обрамляется:

LINE:
while (<>) {
    # your program goes here
}

И при использовании ключа '-n' однострочник выглядит так:

$ echo -e '1\n2\n3'| perl -nE 'say $_ ** 2'
1
4
9

Разница между -nE и -En

В предыдущих разделах я рассказал про ключи -n и -E. Они прекрасно работают вместе:

$ echo -e '1\n2\n3'| perl -nE 'say $_ ** 2'
1
4
9

Но почему-то если поменять местами эти ключи и вместо -nE написать -En, то ничего работать не будет. Вот эта комнда ничего не выводит.

$ echo -e '1\n2\n3'| perl -En 'say $_ ** 2'

Объяснение этому очень простое. После ключа -E обязательно должен идти Perl код. При использовании ключей -En perl считает что Perl код — это просто символ n и как может его интерпритирует. Резульат этой интерпритации — ничего видимого для пользователя не происходит.

В однострочники можно добавлять ключ -w — это соответствует use warnings в Perl коде. Если запустить эту команду с ключем -w — то Perl скажет что он введен в заблуждение:

$ echo -e '1\n2\n3' | perl -w -En 'say $_ ** 2'
Unquoted string "n" may clash with future reserved word at -e line 1.
Useless use of a constant ("n") in void context at -e line 1.

Так что использование -En — это неправильное использование ключей. Ключ -E всегда должен быть перед Perl кодом. После -E и до Perl кода не стоит использовать другие ключи.

То же самое и для ключа -e. После ключа -e обязательно должен идти именно Perl код.

butterfly operator

С ключом '-n' иногда используется специальный хакерский секретный оператор «бабочка» }{

Чуть изменим задачу из прошлого раздела. У нас есть скрипт, который создает вывод:

$ echo -e '1\n2\n3'
1
2
3

Нужно просуммировать все числа из вывода этого скрипта.

Если бы мы решали эту задачу с помощью обычного Perl-скрипта, то мы бы написали что-то вроде:

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

say $sum;

Мы хотим решить эту задачу с помощью однострочника. Ключ '-n' создает вот такой код:

LINE:
while (<>) {
    # your program goes here
}

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

LINE:
while (<>) {
    $sum += $_
}{
    say $sum
}

А однострочник выглядит вот так:

$ echo -e '1\n2\n3'| perl -nE '$sum += $_ }{ say $sum'
6

Ключ -a

Другая задача. Нужно обработать вывод команды которая печатает на экран данные в виде таблицы:

$ echo -e 'a\t1\nb\t2\nc\t3'
a       1
b       2
c       3

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

$ echo -e 'a\t1\nb\t2\nc\t3' | perl -E ...
a
c

Вот длинный однострочник, который решает эту задачу. Разрезаем строку по пробелам, и выводим первый элемент массива, если второй элемент массива — нечетное число:

$ echo -e 'a\t1\nb\t2\nc\t3' | perl -nE 'my @e = split /\s+/, $_; say $e[0] if $e[1] % 2'
a
c

Так писать, конечно, совсем не весело. Поэтому появился ключ '-a', при использовании которого строка из дефолтной переменной разрезается на отдельные элементы, которые попадают в специальный массив '@F'. Вот как можно решить задачу с помощью ключа '-a':

$ echo -e 'a\t1\nb\t2\nc\t3' | perl -naE 'say $F[0] if $F[1] % 2'
a
c

По дефолту '-a' разрезает строку по пробелам. Но с помощью ключа '-F' можно указать паттерн, по которому будет проводиться разрезание. Например, в файле '/etc/passwd' разделителями являются двоеточия:

$ cat /etc/passwd | head -n 2
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/bin/sh

Вот однострочник, который находит все 'User ID' (колонка 3 в файле '/etc/passwd') в которых есть цифра 1. Скрипт выводит логин (это колонка 1) и 'User ID'.

$ cat /etc/passwd | perl -F: -naE 'say "$F[0] $F[2]" if $F[2] =~ /1/'
daemon 1
uucp 10
proxy 13
gnats 41
libuuid 100
syslog 101
messagebus 102
ntp 103
sshd 104
vagrant 1000
statd 105
bessarabov 1001
mysql 106
redis 107
elasticsearch 108

Ну и, конечно, паттерн, который передаётся в '-F', может быть регулярным выражением. Например, есть скрипт который разделяет буквы всякой ерундой, нужно убрать ерунду и отобразить только буквы через пробел:

$ echo -e 'a---b\nc__d\ne=====f'
a---b
c__d
e=====f

Вот простое, понятное и очень аккуратное решение:

$ echo -e 'a---b\nc__d\ne=====f' | perl -F'/[=_-]+/' -naE 'print "$F[0] $F[1]"'
a b
c d
e f

Резюме

Однострочники в Perl — это удобный способ очень быстро решить небольшую задачу. Для работы в консоли часто используют команды 'sed' и 'awk'. Но если человек умеет работать с Perl, то стоит использовать именно его.

В этой статье описаны самые базовые способы использования perl в консоли. Полное описание всех возможных способов запуска perl есть в документации perlrun.

Дополнительная литература

Иван Бессарабов
ivan@bessarabov.ru

13 августа 2014

Edit this post on GitHub