Не далее как прошлой осенью я писал о том, как изнутри процесса определить, запущен ли он, используя инструмент grep. В комментариях мне посоветовали использовать pidof, но мне не удалось заставить его работать для руби, т.к. поиск происходит по имени запускаемого файла, а в случае руби-скрипта это всегда ruby. Но мне существенно удалось сократить получение списка запущенных процессов с таким же именем. Вместо:
Довольно часто мне необходимо отфильтровать вывод или содержимое файла хитрее, чем просто поиск по регулярному выражению. Поскольку мне очень нравится руби, и, как неоднократно писалось в этом блоге, я пытаюсь использовать его везде, где можно, то почему бы снова так не поступить?
Командная строка руби
Руби имеет умеренное количество ключей командной строки. Кратко они описаны в выводе:
ruby --help
Нас в большей степени интересуют ключи -n и -p, которые создают цикл вокруг чтения из пайпа. Ссылка на подробности в конце статьи.
Например, мы хотим посчитать, сколько всего виртуальной памяти занимают все процессы браузера гугл-хром. В качестве источника информации будем использовать вывод команды:
ps axo "%p %z %c"
В которой собраны только необходимые данные (занимаемая виртуальная память и имя процесса без аргументов) и пид (ну а вдруг?). А теперь этот вывод отправим не грепу, а нашему родному руби:
Что это означает? Ключ n означает, что вокруг нашего скрипта есть цикл вида:
while gets(); ... end
Ключ a означает, что вместо переменной $_, куда автоматически попадает результат gets, мы можем использовать $F, который есть суть $_.split. А END содержит блок, который выполняется после цикла.
Ту же магию можно использовать и внутри запускаемых руби-скриптов. Например, если мы хотим найти какое-то слово внутри файла, выделить его цветом и вывести строку с номером, где это слово нашлось, то наш скрипт будет выглядеть вот так (файл look_for):
#!/usr/bin/ruby -n
BEGIN {
unless ARGV.size == 2
puts "Usage: ./look_for <word> <path/to/file>"
exit
end
str = ARGV.shift
}
next unless $_ =~ /#{str}/
printf "%6s%s", $., $_.gsub($&, "\e[31m#{$&}\e[0m")
Теперь, если сделать этот файл запускаемым и запустить его:
./look_for word /in/some/file
То можно увидеть неземную красоту. Кстати, обратите внимание на shift. Без него программа не работает, т.к. gets, который тут за кадром правит бал, пытается воспринимать все аргументы как пути к файлам, из которых непременно нужно что-нибудь прочитать.
Прочие прекрасные применения параметров командной строки руби я предлагаю пытливому читателю подсмотреть в ссылках ниже или найти самостоятельно.
Что-то большие перерывы в написании статей входят в привычку. Способность некоторых коллег по цеху регулярно выдавать что-нибудь полезное и интересное вызывает уважение.
Введение
С тех самых пор, как я только узнал про регулярные выражения, я слышал об их несовершенстве и моральном устаревании. Регулярные выражения продолжали использоваться, а недовольные теоретики сетовать. Основной претензией было то, что регулярные выражения не позволяют исследовать вложенности паттернов в виду своей линейности. Действительно, соглашался я, невозможно проверить правильность открытия и закрытия тегов или получить выражение в самых внутренних скобках.
Однако, как оказалось, человечество шагнуло далеко вперёд в вопросе совершенствования регулярных выражений. Об одном из новшеств хочу сегодня рассказать.
Именованные группы
В регулярных выражениях руби 1.9 появились именованные группы. Вот, как выглядит их элементарное использование:
if /\A(?<first>[a-zA-Z]+)\s+(?<last>[a-zA-Z]+)\Z/ =~ "Vassily Poopkine"
puts [first, last].inspect
end
if md = /\A(?<first>[a-zA-Z]+)\s+(?<last>[a-zA-Z]+)\Z/.match("Vassily Poopkine")
puts [md[:first], md[:last]].inspect
end
То есть мы не только выделяем группу скобками, как обычно, назначая ей тем самым порядковый номер (по номеру открывающей скобки), но и даём имя. И использовать его можно не только в локальных переменных и объекте MatchData, но и в самом регулярном выражении.
Более того, обращение к объявленным группам внутри может быть рекурсивным. Мне сразу же захотелось написать давнишнюю мою задумку о функции, раскрывающей вложенные скобки. Вот так:
str = "1 + 2 * (3 - 4 / {5 + 6} + [7 - 8 * (9 + 10 * 11) + 12 * {13 - 14}] + 15) + 16 * (17 + 18)"
re = %r{
(?<fill>[0-9+\-*/\s]+){0}
(?<expression>\g<fill>*\g<brackets>\g<fill>*|\g<fill>){0}
(?<braces>\{\g<expression>+\}){0}
(?<squarebrackets>\[\g<expression>+\]){0}
(?<parentheses>\(\g<expression>+\)){0}
(?<brackets>\g<braces>|\g<squarebrackets>|\g<parentheses>)
}x
def calculator(str)
if str =~ /\A[0-9+\-*\/\s]+\Z/
eval str
else
raise "Invalid expression: #{str}"
end
end
f =-> s do
if $~[:expression] == $~[:fill]
calculator($~[:fill])
else
calculator($~[:brackets][1..-2].gsub(re, &f))
end
end
puts calculator(str.gsub(re, &f))
puts eval(str.gsub(/(?<left>\{|\[)|\}|\]/) { |s| $~[:left] ? "(" : ")" })
Итак, в регулярном выражении присутствует 6 именованных групп: fill (заполнения пространства между скобками), expression (выражение, содержащее одни или ни одних нераскрытых скобок), braces (фигурные скобки), squarebrackets (квадратные скобки), parentheses (круглые скобки), brackets (любые скобки). Как видите, выражение описывается через скобки, а скобки через выражение.
Для проверки правильности расчёта, используем обычный eval, заменив все скобки на круглые.
Сделав этот пример, я был доволен, как стадо слонов, но потом решил проверить, а что будет, если скобки расставлены неправильно?
И я не смог дождаться завершения работы оператора =~ для такого длинного выражения. Это, конечно, неприятно. В причины я вникал не особо, но похоже, это связано с поведением недетерминированной машины Тьюринга. По крайней мере вот ответ на похожую проблему. Для нас это всего лишь означает, что проверять правильность расстановки скобок нужно отдельно и другим способом. Чем я предлагаю заняться пытливому читателю самостоятельно.
Это уже давно известная тема, и я не претендую на открытие Америки, но для себя зафиксирую это знание.
Даже если вы используете капистрано для выкладывания проекта в сеть, логи приложения хранятся в одном и том же месте (папка shared/log и разрастаются до огромных размеров. Можно, конечно, запускать после каждого обновления файлов проекта комманду:
rake log:clear
Но есть более цивилизованные методы. Тем более, после определённого времени код проекта начинает обновляться всё реже и реже.
С помощью системы
Существует прекрасный системный инструмент, который назвается logrotate. С его помощью архивируются логи апача, баз данных и даже менеджера пакетов.
Чтобы организовать это удовольствие для своего проекта нужно создать файл /etc/logrotate.d/my_project:
Первый вариант осуществляет ротацию раз в неделю, а второй по достижении файлом размера в 1 мегабайт и хранит 10 старых томов. Только в данном случае архивацию, если она нужна, придётся организовывать отдельно.
Было бы интересно
Для логротейт можно написать такую маску, которая бы включала в себя все логи всех рельсовых проектов. Но мне неизвестен способ потом написать такой скрипт, который бы перезапускал именно те проекты, для которых была сделана ротация. Например, если логротэйт не нашёл нужного файла, то и скрипт не запустит. А если мы указываем путь типа /path/to/**/shared/*.log, то и скрипт должен перебирать все эти проекты и создавать или просто менять дату редактирования файлов restart.txt. Или можно просто перезапускать апач.
Ого! Уже три месяца я ничего не писал в этот блог! Лето выдалось жаркое не только на погоду. Поскольку летом погода лучше, а световой день длиннее, было много работы. Причём работы связанной с поддержкой того, что уже и так нормально функционировало в прошлом сезоне. Ничего серьёзно нового не писалось активно, а значит и захватывающих сюжетов для статей не находилось.
Но теперь у меня появилась возможность писать кое-что новое. Поэтому есть, что рассказать.
Введение
Если вы любите процессы-демоны, как люблю их я, то, возможно, перед вами уже возникала задача определить, запущен ли уже такой демон, перед тем как создавать дочерний процесс. Об этом и будет сегодняшняя статья.
Баш в помощь
Предположим, что у нас есть простейший демон. Хорошо бы имя у него было уникальное, чтобы можно его потом было отыскать. Файл uniq_name_simple_daemon:
#!/usr/bin/env ruby
pid = fork do
begin
running = true
Signal.trap("TERM") do
running = false
end
while running
sleep 0.01
end
rescue Exception => e
puts e.to_s
puts e.backtrace.join "\n"
ensure
exit!
end
end
Мы всегда можем запускать с помощью другого скрипта, например на баше (simple_daemon_runner.sh):
#!/bin/bash
if ps ax | grep uniq_name_simple_daemon | grep -vq grep
then
echo "uniq_name_simple_daemon is already running"
else
echo "starting uniq_name_simple_daemon"
./uniq_name_simple_daemon
fi
На подобной команде будут базироваться все наши последующие методы. Тут, если кто не понял, мы фильтруем вывод ps ax сначала ища там имя нашего скрипта, а затем исключая из списка сам процесс поиска (команду grep). Ключ q позволяет нам получить код выхода, не выводя ничего на экран. То есть если строчка найдена, то запускаем первый блок, если нет, то второй.
Можно сделать такой же скрипт для остановки процесса (simple_daemon_stopper.sh):
#!/bin/bash
pid=$(ps ax | grep uniq_name_simple_daemon | grep -v grep | awk '{ print $1; }')
if [[ -n $pid ]]
then
echo "stopping uniq_name_simple_daemon"
kill -TERM $pid
else
echo "nothing to stop"
fi
Конечно же, при таком раскладе всегда есть возможность запустить нашего демона без помощи скриптов. И тогда проверка делаться не будет. В таком случае полезно проверять, запущен ли процесс уже внутри самого руби, перед тем, как отпочковать дочерний процесс.
Сам себе хозяин
В данном случае задача сводится к проверке наличия в памяти ещё одного процесса с таким же именем кроме текущего. Так же нужно уметь останавливать процесс с помощью того же файла. Вот, какое решение получилось у меня (uniq_name_auto_daemon):
#!/usr/bin/env ruby
ps_ax = `ps ax | grep #{File.basename(__FILE__)} | grep -v grep`.split("\n").map{ |l| l.strip.split(/\s+/) }.reject{ |l| l[0].to_i == Process.pid }
if ps_ax.any?
case ARGV[0]
when /stop/i
ps_ax.each do |l|
system "kill -TERM #{l[0]}"
end
when /kill/i
ps_ax.each do |l|
system "kill -KILL #{l[0]}"
end
else
puts "#{File.basename(__FILE__)} is already running. If you want to stop it, run './#{File.basename(__FILE__)} stop|kill'"
end
else
pid = fork do
begin
running = true
Signal.trap("TERM") do
running = false
end
while running
sleep 0.01
end
rescue Exception => e
puts e.to_s
puts e.backtrace.join "\n"
ensure
exit!
end
end
end
Во-первых, обходимся одним файлом, который никак иначе не запустить. Во-вторых, нигде не нужно хардкодить его имя. По-моему, очень удобно.
Оффтопик
С одной стороны, когда я пишу текст, то мне удобнее писать все термины по-русски и склонять их: «демоны», «руби», «баш», но с другой стороны это не поможет тому, кто будет искать решение похожей задачи.
Внутри примеров кода наоборот, удобнее писать комментарии и тексты по-английски, чтобы не переключать раскладку, но как-то это не очень соответствует русскоязычном блогу.
Лето в этом году выдалось жаркое, но пока ни о каких отпусках речи не идёт. Пока солнечно, тепло и сухо самое время снимать панорамы улиц (или ГрадоГлядъ, как я это называю). Однако, не смотря на хорошую плотность работы, ничего принципиально нового, о чём интересно было бы рассказать, не произошло. Поэтому я покажу концепт, над которым работаю уже какое-то время, и который лежит в стороне от моих основных языков программирования.
Дистанционное управление
Итак, у меня уже давно есть конструктор LEGO Mindstorms, который довольно бодро взаимодействует через bluetooth с компьютером или с таким же конструктором.
А в апреле я обновил телефон на HTC Desire под управлением операционной системы Андроид, которая уже давно привлекла моё внимание, но всё руки не доходили.
В итоге появились повод, возможность и желание «пощупать» Андроид. И вот, что из этого получилось:
Секреты?
Для тех, кто соберётся заниматься чем-то подобным, могу поделиться небольшим секретом. Стандартный метод получения BluetoothSocket, рекомендованный командой Гугла, не работает, а нужен небольшой хак:
/* Не работает:
* socket = device.createRfcommSocketToServiceRecord(MY_UUID)
*/
Method m = device.getClass().getMethod("createRfcommSocket",
new Class[] { int.class });
socket = (BluetoothSocket)m.invoke(device, Integer.valueOf(1));
Вот и всё, что я могу рассказать :) Если я ещё буду писать про Андроид, то придётся сделать подсветку кода для него.