Подборка полезных однострочников на Perl

20 февраля 2013

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

Затянувшееся введение про синтаксис и CPAN

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

Ниже я буду использовать некоторые CPAN-модули. Если вы не знаете или вдруг подзабыли, устанавливаются эти модули с помощью утилиты cpan. Например, чтобы установить модуль URI::Escape в Debian или Ubuntu, нужно выполнить команду:

sudo cpan -i URI::Escape

Также существует более удобная утилита cpanm. Используется она точно так же, как cpan, только без флага -i. Если у вас в системе нет cpanm, просто поставьте модуль App::cpanminus.

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

Если вы не знаете имя нужного вам модуля, воспользуйтесь сайтом metacpan.org. Например, если вы ищите модуль для работы с JSON, просто введите в строке поиска «json». В ответ вы получите большой список модулей. При клике на имени модуля вы увидите исчерпывающую документацию по нему. В именах некоторых модулей могут использоваться сокращения «PP» или «XS». Первое означает, что модуль написан на чистом Perl (pure perl) и прекрасно переносим между различными операционными системами. Во втором случае модуль написан на Си. Такие модули очень быстры, но плохо переносимы.

Основные сведения об однострочниках на Perl

Хорошо, теперь перейдем к однострочникам. Однострочник — это небольшая (в одну строку) команда, которая пишется прямо в оболочке и часто объединяет утилиты cut, grep и другие с помощью пайпов. Типичный однострочник выглядит как-то так:

cat file.csv | cut -d ';' -f 2 | grep -v afiskon

Здесь мы читаем csv-файл, удаляем из прочитанного все столбцы, кроме второго, а затем из всех строк оставляем только те, что не содержат подстроку «afiskon». Однострочники очень удобны при решении не самых тривиальных мелких задач, но их возможности сильно ограничены. Если только вы не владеете Perl!

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

perl -e 'printf "%x\n", 1234'

Также предусмотрен флаг -l, предназначенный для автоматического добавления символа новой строки к выводимым строкам. Другими словами, «обратный» однострочник, преобразующий число из шестнадцатеричной системы в десятичную, можно написать так:

perl -le 'print 0x4da'

Рассмотрим чуть более сложный пример:

perl -le 'while(glob "cgi-bin/*"){ print if -f }'

Здесь мы смотрим содержимое каталога cgi-bin и выводим все найденные в нем файлы (то есть, не каталоги и тп). Тут как нельзя кстати оказываются такие особенности Perl, как переменная $_ и функции, работающие с ней, возможность не писать точку с запятой в конце выражения, и другие.

Рассмотрим еще один пример. Обычно для поиска файлов я использую find или grep с ключем -r, но иногда их возможностей недостаточно. Например, как-то раз мне понадобилось найти все текстовые файлы, содержащие строку «test» и не содержащих строку «debug». Обе строки — нечувствительны к регистру. Вот возможное решение:

find ./ | perl -lne 'if(-T $_){ $d = `cat "$_"`; print if ($d =~ /test/i) && ($d !~ /debug/i) }'

Здесь мы воспользовались новым флагом -n. Он означает «брать каждую строку из stdin и вызывать для нее программу, передав эту строку в переменной $_». При одновременном использовании флагов -l и -n, из входных строк удаляются символы новой строки. Что же делает сама программа?

Она проверяет, является ли файл с именем, хранимом в переменной $_, текстовым (оператор -T). Если это так, содержимое файла считывается в переменную $d. Если содержимое файла удовлетворяет требуемому условию, что мы проверяем при помощи регулярных выражений, его имя выводится, иначе — не выводится.

Весьма полезен флаг -p. Он аналогичен -n за тем исключением, что после выполнения программы автоматически выводится содержимое переменной $_. Это позволяет писать вещи вроде:

echo 'hello, world!' | perl -pe 's/hello/goodbye/i'

… что, бесспорно, очень удобно.

Собственно обещанная подборка

Для того, чтобы подгрузить сторонний модуль, интерпретатор Perl имеет флаг -M. Например, так можно производить кодирование и декодирование в/из base64:

perl -MMIME::Base64 -le 'print encode_base64("password")'
perl -MMIME::Base64 -le 'print decode_base64("cGFzc3dvcmQ=")'

Аналогично с urlencode:

perl -MURI::Escape -le 'print uri_escape("привет")'
perl -MURI::Escape -le 'print uri_unescape("%D0%BF%D1%80%D0%B8%D0%B2")'

Некоторые модули требует, чтобы импорт функций в текущее пространство имен происходил явно. Например, так работает Digest::MD5:

perl -MDigest::MD5=md5_hex -le 'print md5_hex("kitty")'

Кстати, с помощью Perl вы можете взламывать md5-хэши прямо в консоли:

time perl -MDigest::MD5=md5_hex -le 'for("a".."zzzzz") { if(md5_hex($_) eq "cd880b726e0a0dbd4237f10d15da46f4") {print; last} }'

Для работы с другими алгоритмами хэширования на CPAN есть модули Digest::SHA1, Digest::SHA2, Digest::CRC (или String::CRC32, который делает то же самое) и прочие Digest::*.

С помощью Perl вы можете с легкостью пропарсить JSON. Следующий однострочник находит десять последних твитов, в которых упоминается Perl:

wget 'http://search.twitter.com/search.json?q=Perl' -O - | \
  perl -MJSON::XS -lne '$j=decode_json($_); print join("\n", map { $_->{text} } @{$j->{results}})'

Парсить XML в однострочниках несколько сложнее, но если перед вами встанет такая задача, посмотрите в сторону модуля XML::Parser.

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

perl -le 'print time()'

Декодировать время в понятное человеку представление можно командой:

perl -le 'print scalar localtime(1345006368)'

Чтобы получить представление заданного времени в unix timestamp, воспользуйтесь следующим однострочником:

perl -MDate::Manip -le 'print UnixDate("2012/08/05", "%s")'

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

perl -e 'while(1) { system("cat worker.log | grep Failed | wc -l"); sleep 60 }'

Есть такой известный среди питонистов способ быстро расшарить заданный каталог по HTTP:

python -m SimpleHTTPServer

На Perl то же самое можно сделать с помощью модуля HTTP::Server::Brick:

perl -MHTTP::Server::Brick -e '$s = HTTP::Server::Brick->new; $s->mount("/" => { path => "./" }); $s->start()'

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

Сделать checkout ветки dev в дюжине различных каталогов:

perl -le 'for(<./deps/*>) { chdir($_); print "=== $_==="; system("git checkout dev 2>/dev/null && git pull origin dev"); chdir("../.."); }'

Дернуть большое количество урлов с заданными аргументами:

cat id_list.txt | perl -lne 'chomp; system("wget \"http://example.ru/api/v1.0/items?item_id=$_\"")'

Преобразовать результат выполнения select-запроса в CSV:

cat /tmp/rslt.txt | perl -e 'while(<>){ next if ++$n == 2; s/^\s+//; s/\s+$//; s/\|\s+/|/g; s/\s+\|/|/g; s/\|/;/g; print "$_\n"}'

И последний однострочник:

perl -e '@f = <*.*>; system "mpg123 ".$f[rand(@f)]'

Как вы, конечно же, поняли, он проигрывает случайный mp3-файл.

Заключение

В целом, основные правила при написании однострочников следующие:

  • В однострочниках ни к чему использовать use strict и всякие там my;
  • Пользуйтесь возможностью опускать скобочки, точки с запятой и тп;
  • Держите на вооружении флаги -l, -n и -p;
  • В сочетании с флагами -n и -p удобно использовать блоки BEGIN и END;
  • Операторы || и //, а также постфиксные if, unless и for — ваши друзья;
  • Переменные $_, $1..$9 и прочие идеально подходят для однострочников;

Следует отметить, что некоторые вещи, например, кодирование/декодирование в/из base64, удобнее делать в REPL. В качестве REPL можно использовать отладчик Perl, или скрипт re.pl, который входит в состав модуля Devel::REPL. К сожалению, этот модуль зависит от Moose, однако это действительно очень удобная REPL-среда для Perl.

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

А для каких задач вы используете однострочники?

Дополнение: Еще часто возникает задача пройтись по всем файлам и заменить в них одну строчку на другую. Решение с помощью однострочника на Perl:

find ./ -type f -iname '*.go' | \
  xargs perl -pi -e 's/relace-from/replace-to/'

Отличие от ранее рассмотренных однострочников заключается в использовании флага -i, означающего замену текста в файлах на месте (in place).

Метки: .


Вы можете прислать свой комментарий мне на почту, или воспользоваться комментариями в Telegram-группе.