Как, думаю, многим известно, Ruby создавался под влиянием Perl, поэтому нет ничего удивительного в том, что Ruby может подменить Perl в его нише "практического извлечения данных и составления отчетов". В данном небольшом посте речь пойдет об использовании ruby для мелкой обработки текста в консоли.
TL;DR
Запоминаем две конструкции:
... | ruby -lne <CODE>
ruby -lne <CODE> file1, file2, ...
В первом случае входными данными будет вывод предыдущей команды, а во второй — конкатенация содержимого файлов.
CODE будет выполняться для каждой строки входных данных, которая будет записываться в переменную $_
. Важно: строки будут без завершающего \n
(за это отвечает флаг -l
).
Примеры
Отраженный cat
:
ruby -lne 'puts $_.reverse' file-to-show.txt
Вывести только первые 10 символов имени файлов в текущей директории:
ls | ruby -lne 'puts $_[0..9]'
Немного подробностей
Предопределенные переменные
В Ruby есть множество предопределенных переменных, о существовании многих из которых мы с вами можем даже не знать. Среди них есть и перекочевавшие из Perl, например, $_
и $<
(на самом деле, в Perl нет такой переменной, но есть конструкция оператора ввода <>
, читающая из файлов/stdin и устанавливающая $_
).
С их списком можно ознакомиться здесь.
В рамках темы поста советую обратить внимание на вышеупомянутые $_
и $<
, а помимо них на: $~
, $1
, $stdin
, ARGV
, $stdout
, STDIN
и STDOUT
.
Страшный секрет gets
Возможно, многие использовали gets
для того, чтобы запросить ввод с клавиатуры. Однако далеко не все знают, что он проверяет содержимое ARGV
— аргументов командой строки.
Когда ARGV
непустой, наш gets
считает, что там указан список файлов, берет первый из них и пытается прочитать строку оттуда.
Возьмем скрипт:
# myscript.rb
puts gets
И запустим его с помощью ruby myscript.rb
. Как и ожидалось, программа ждет нашего ввода.
А теперь запустим его вот так:
ruby myscript.rb no-such-file
Произойдет ошибка: файла "no-such-file" не существует. А теперь изменим скрипт:
# myscript.rb
ARGV.pop
puts gets
Теперь интерпретатор опять ожидает нашего ввода, т.к. с помощью pop мы выкинули наш "no-such-file".
Опции интерпретатора
Вот так выглядит мой шаблон однострочника:
ruby -lne <CODE> [file1, file2, ...]
(зачастую без -l
)
Описание используемых и некоторых потенциально полезных опций:
-e <CODE>
— вынуждает интерпретатор не пытаться искать скрипт среди аргументов и выполнитьCODE
.-n
— оборачивает скрипт (или код, переданный с помощью-e
) в циклwhile gets
. Методgets
неявно устанавливает возвращаемое значение переменной$_
(привет, Perl!), которую мы и должны использовать. Важно: строка будет заканчиваться\n
.-l
— для простоты можно считать, что она просто удаляет символ новой строки\n
из$_
.-p
— работает точно так же, как и-n
, но после каждой итерации выводит содержимое$_
. Т.е. подразумевается, что вы будете его менять. Например:ls | ruby -pe '$_.upcase!'
-C <DIR>
— меняет рабочую директорию.-a
(от auto-split) — используется совместно с-n
или-p
и устанавливает переменную$F
, равной$_.split
.-F
— устанавливает стандартный разделитель дляString#split
($;
). Теоретически, может быть полезно совместно с-a
. Например, так можно обрабатывать простенькие .csv
Заключение
Впервые использовать ruby -e
меня натолкнула команда cut
. Мне ее приходилось пару раз использовать, и каждый раз мне нужно было открывать мануал, который моя тугая голова не хотела быстро понимать.
В конечном итоге я подумал: "Да, я не умею пользоваться cut
/awk
/sed
. Но зато я неплохо знаю Ruby, почему я не могу для всяких мелочей использовать его?".
Собственно, в конечном итоге вместо запоминания множества команд мне достачно было запомнить только конструкцию, полученную опытным путем:
ruby -lne CODE
И все. Все мои проблемы с построчной обработкой текста решены.
Ничего новаторского здесь нет, насколько мне известно, бородатые сисадмины давно используют в таком ключе perl. Да и среди вас, готов поспорить, есть множество людей, которые знают об этом, но при этом не используют, хотя стоило бы. Наверняка, у питонистов тоже есть что-то подобное.
P.S. Напоследок вот пример использования:
Скачал сезон сериала, а там имена файлов некрасивые, вроде "s01e01 — super release by super-mega-macho.mkv", хочу их переименовать, чтобы в медиатеке все было красиво. Пожалуйста:
ls | ruby -lne 'File.rename($_, "#{$_[0..5]}.mkv")'
Комментарии (10)
g4xd
09.10.2017 01:00-1или можно просто научиться awk и sed, и особо много времени на это не нужно
Nondv Автор
09.10.2017 19:39+1А если Вам потребуется что-то мелкое, но выходящее за рамки знаний? Вы пойдете гуглить нужный инструмент и потом будете читать мануал к нему?
Вы тогда изучать инструмент будете дольше, чем решать задачу (в единственном числе), для которой он Вам нужен.
В случае с ruby/perl/etc Вы, запомнив по сути всего парочку фактов, получаете швейцарский нож, с помощью которого можете сделать, что угодно.
Это хорошо, если человек знает awk+sed. Он решит задачу, скорее всего, красивее, чем с помощью руби/перл. Но это при условии, что он знает. Мне вот это пригождается не так часто, чтобы я постигал прелести awk (которым я раньше временами пользовался) и пытался держать их в голове.
scizor
Просто подумал, что, возможно, не всегда удобно использовать _$ и -n. С тем же xargs например
ls | ruby -lne 'puts $_[0..9]'
можно переписать какls | xargs ruby -e 'ARGV.each { |file| puts file[0..9] }'
. Хотя конкретно для этого кейса ваш код выглядит поприятнееNondv Автор
Ну, в посте разбирается построчная обработка. Я сомневаюсь, что использование ARGV когда-нибудь будет удобнее в этом плане.
Хотя все возможно:)
Nondv Автор
Преимущество ARGV, как мне кажется, только в том, что в скрипте имеешь доступ ко всем аргументам сразу (в итерации можно, например, использовать предыдущий)
scizor
ага, именно) разве что так сразу пример не подобрать, когда может понадобиться.
Nondv Автор
В любом случае я бы использовал
$<
вместоARGV
:scizor
$< выглядит как прекрасная альтернатива, да, xargs + ARGV будет полезен когда нужны все аргументы сразу, типа для случая ARGV.join или ARGV[0] + ARGV[-1], скорее не для построчной обработки, что вы в общем и сказали, конечно зависит от количества входных данных и всегда можно заюзать ARGF если захотеть, я бы сказал, что по ситуации надо выбирать
michaelkl
Можно распараллелить обработку:
Nondv Автор
Спасибо за идею!
можно написать
-r parallel
, чтобы не перенасыщать строку кода: