Читаем INI-файл: что может быть проще?

  • BASH
Как ни странно, INI-файлы читаются на BASH не сложнее, чем на любом другом языке, а по мне — так даже элегантнее.

Вот вам пример, который можно сделать ещё короче, если немного покумекать:


#!/bin/bash
read_ini () {
  local file="$1"
  local arr_name="$2"
  local -A ini_conf
  while read l; do
    if [[ $l =~ ^\[([^]]+)\] ]]; then
            section=${BASH_REMATCH[1]}
    elif [[ $l =~ ^([^=[:space:]]+)[[:space:]]*=[[:space:]]*(.+)$ ]]; then
            source <(cat <<CODE
            ini_conf["${section:+${section}.}${BASH_REMATCH[1]}"]=${BASH_REMATCH[2]}
CODE
)   
    fi
  done < <(sed -r -e '/^\s*([#;].*)$/d' -e 's%^\s+%%; s%\s+$%%' "$file")
  local t=$(declare -p ini_conf)
  [[ $arr_name ]] && echo ${t/declare -A ini_conf=/declare -A ${arr_name}=} || echo "$t"
}
 
read_ini "$@"


Дерзайте и дерзновенны будете! К чему бы это я? А к тому, что source в BASH — обалденно мощная и удобная штука, намного лучше eval, вынуждающего экранировать кавычки и далеко не всегда дающего адекватные результаты. Не стесняйтесь чаще и больше пользоваться source'ом!

Собственно, именно source'ом и нужно «подобрать» вывод функции read_ini, дабы получить ассоциативный массив, имя которого передаётся вторым параметром.

Возникли вопросы? Пишите в комментариях — буду рад ответить.
За сим до новых встреч!

Как получить строку из функции на Си?

Почему-то эту тему совсем не осветили в документации Crystal, упомянув только то, что передавать и получать строки (при взаимодействии с кодом на Си) можно только как указатели на UInt8. Ни одного примера на эту тему не нашёл, поэтому на всякий случай «восполню пробел»:

lib LibC
  fun ctermid : UInt8*
end
puts String.new(LibC.ctermid)

Функция ctermid возвращает указатель на строку, которая *char с точки зрения языка Си, и она же UInt8* (т.е. Pointer(UInt8)) в языке Crystal. После выполнения функции ctermid в jбласть памяти, адресуемую полученным из функции указателем UInt8*, будет записана последовательность байт, заканчивающаяся байтом со значением 0, что является обычным для Си способом представления строк. И хотя без проблем можно было бы получить результирующую строку, попросту пройдясь в цикле до того самого «нулевого» байта, даже от этой необходимости нас избавил рантайм языка: ведь в Crystal можно создать строку напрямую из UInt8*, что, собственно, и показано в примере выше!

Вот такой простой пример. Для домашней же проработки оставляю вам статью, в которой описаны куда более интересные трюки, допустимые при работе с внешними функциями на языке Си.

За сим до свидания — и до встречи в русскоязычной телеграмм-группе, посвящённой языку Crystal:
telegram.me/crystal_ru

hex_dump строки

Время не стоит на месте, и в Crystal версии 0.26 уже есть прекрасный встроенный метод hexbytes для декодирования строки, содержащей шестнадцатеричный дамп, — в последовательность байт.

А как сделать прямое преобразование? То есть если у нас есть строка — и нужно получить её шестнадцатеричный дамп, какой метод можно использовать?

Ответ с одной стороны пока неутешителен: нет такого метода. С другой стороны, ответ слишком сложен, потому что на архитектуре Intel делать такое преобразование быстрее всего MMX-инструкциями, а значит — использовать встроенный asm-препроцессор (что, разумеется, не будет сделано разработчиками Crystal, поскольку они поддерживают «много разных архитектур» и стараются до последнего всё-таки обходиться собственными средствами языка).

Ну а с третьей стороны — как раз собственными средствами языка всё делается достаточно просто:

def hex_dump (s : String)
  s.to_slice.each_with_object(IO::Memory.new) {|b,s| s << b.to_s(16) }.to_s
end

Здесь мы сначала говорим: «с тем, что было строкой, далее работай как с указателем на побайтовый вектор» (s.to_slice), затем для каждого из байт, на которые указывает наш «слайс», вставляем их шестнадцатеричное представление в объект типа IO::Memory, созданный на лету в конструкции each_with_object. Само преобразование из «байта» в шестнадцатеричное строковое представление ('1' => 31) осуществляется функцией с наименее очевидной в данном случае семантикой: b.to_s(16).

Ну и в конце полученный объект типа IO::Memory преобразуется в строку (.to_s), после чего эта строка и становится выходным значением hex_dump'а

Кстати, это не самый эффективный способ преобразования: hex_dump(io) был бы и проще, и быстрее в случаях наподобие puts hex_dump(s). Но эту задачку я оставлю пытливым умам на домашнюю проработку :)

Простой PPTP сервер для Centos 7

  • Linux
Проверенный много раз в бою скрипт, можно брать VDS и раскатывать сервер pptp.
Все шаги выполняйте как тут выложены блоки по очереди

1.
yum -y update


2.
yum install -y epel-release
yum install -y net-tools vim
yum install -y ppp pptp pptpd pptp-setup
chkconfig pptpd on

3.
mv /etc/pptpd.conf /etc/pptpd.conf.bak
vim  /etc/pptpd.conf

Содержимое
option /etc/ppp/options.pptpd
logwtmp
localip 10.0.10.1
remoteip 10.0.10.2-254

4.
mv /etc/ppp/options.pptpd /etc/ppp/options.pptpd.bak
vim /etc/ppp/options.pptpd

Содержимое
name pptpd
refuse-pap
refuse-chap
refuse-mschap
#require-chap
#require-mschap
#require-mschap-v2
require-mppe-128
require-mschap-v2
proxyarp
lock
nobsdcomp
novj
novjccomp
nologfd
ms-dns 8.8.8.8
ms-dns 8.8.4.4

5.
mv /etc/ppp/chap-secrets /etc/ppp/chap-secrets.bak
vim /etc/ppp/chap-secrets

Содержимое
# Secrets for authentication using CHAP
# client        server  secret                  IP addresses
test * test *

6.
vim /usr/lib/sysctl.d/50-default.conf

Добавить строку
net.ipv4.ip_forward=1

7.
vim /etc/selinux/config

Заменить опцию
SELINUX=disabled

8.
firewall-cmd --permanent --add-masquerade
firewall-cmd --zone=public --add-port=1723/tcp --permanent
firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 0 -p gre -j ACCEPT
firewall-cmd --permanent --direct --add-rule ipv6 filter INPUT 0 -p gre -j ACCEPT
firewall-cmd --reload

перегружаем и пробуем подключаться по логину паролю test/test

Макросы: как задать режим компиляции извне?

Положим, вы хотели бы компилировать тот или иной код в зависимости от значения переменной-триггера, задающей «режим компиляции».

В Crystal это делается элементарно:
  1. Задаём переменную окружения MODE_VAR локально для процесса-компилятора
    
    MODE_VAR=1 crystal src/mutating_code.cr
    

  2. Учитываем значение этой переменной во время компиляции:
    
    {% begin %}
      {% if env("MODE_VAR") != nil %}
        puts "Do something if MODE_VAR was triggered"
      {% else %}
        puts "Do anything else if MODE_VAR environment variable is absent "
      {% end %}
    {% end %}
    

Рецепт: Передача переменных простых типов по ссылке

Предположим, вы хотите написать функцию, модифицирующую значение переменной простого типа «на лету».

Если вы сделаете вот так:

def inc(cnt : Int32)
  cnt += 1
end
n = 0
inc(n)
puts n
# "0" will be printed

— ничего у вас не выйдет, поскольку в действительности значение cnt передано не по ссылке, а по значению. Если вдаваться в технические подробности, то значение переменной cnt будет скопировано на вершину стека вызова функции-«метода», а внутри этой функции будет модифицировано именно значение в стеке, а не исходная переменная cnt.

Как же правильно передать значение по ссылке?
Многословность спасёт мир:

def inc(cnt : Pointer(Int32))
  cnt.value += 1
end
n = 0
inc(pointerof(n))
puts n
# output: "1"

(Посмотреть, как работает код...)

Собственно, не сильно отличается от вызова аналогичной функции в Си, только «буков» местами больше, но зато и читается легче.

Всего наилучшего и до новых встреч!

Макросы: Условная компиляция

Для чего нужна условная компиляция и что это вообще такое?

Условная компиляция — это механизм формирования/генерации различного кода в зависимости от внешних условий, заданных на период начала компиляции.

Для чего это нужно? Ну, например, вы сможете генерировать из одного и того же исходного кода разные программы в зависимости от дня недели или наблюдаемой частоты вспышек на Солнце. Шучу конечно, но суть в общем-то отражает верно: это нужно для того, чтобы вы могли собрать программу, адаптированную к тем или иным условиям её (многократного) запуска. Чаще всего в данном контексте вспоминают рихтовку программного кода под целевую платформу: подключаются одни библиотеки, не подключаются другие, в зависимости от разрядности целевой платформы используются одни типы данных и не используются другие (например, на моём древнем нетбуке было бы актуально использовать Int32 вместо Int64). Тем не менее есть и другие, не менее значимые области применения: например, вы можете собрать версию программы с урезанным функционалом, но потребляющую существенно меньше памяти и/или более быструю, вы можете собрать код, из которого будут выброшены за полной бесполезностью условные конструкции, обречённые проверять незыблемые вещи. Таким образом, выудив пару-тройку назойливых if'ов из цикла с большим количеством итераций, вы малой кровью добьётесь весьма ощутимого прироста производительности: ведь проверка условий и условные переходы легко сбивают с толку конвейерный механизм работы современного микропроцессора (префетчинг/предвыборку команд в конвейер) и как минимум загружают его блок предсказания ветвлений ненужной работой.

Ну и конечно условная компиляция помогает быть сухим и чистым, т.е. «не повторять себя» (см. принцип d.r.y) и писать один макрос, ветвящийся в зависимости от значений того или иного параметра — там, где в противном случае пришлось бы писать несколько однотипных макросов или, что ещё «оригинальнее», генерировать макросами другие макросы.

В языке Crystal вы можете даже, пользуясь проверкой flag?(:release), вырезать из финального кода всё, что в противном случае прямо в рантайме проверяло бы, нужно ли выводить какие-нибудь низкоуровневые отладочные сообщения или же делать это и вовсе не следует. «Классический» подход к логированию заключается в том, чтобы устанавливать для приложения уровень отладки в конфигурационном файле или же задавать его параметрами командной строки. Но что если в этом нет никакой объективной необходимости? Что если ваш заказчик не хочет видеть отладку, а вы не хотите тестировать и отлаживать приложение прямо на «живом» заказчике? И если никогда не меняющееся условие «текущий_уровень_отладки >= debug» можно проверить один раз — при компиляции, — то почему бы так и не поступить? И машинного кода в релизе будет поментше, и работать он будет быстрее… Профит? Ну вот то-то же :)

Как же выглядит разрекламированная мной возможность языка Crystal впечатывать один код и не впечатывать другой, называемая также условной компиляцией?

Памятуя об упомянутой выше адаптации под архитектуру, вот вам классический solution для классического же use case, а именно генерация типа-псевдонима для того типа целого, который наиболее актуален для целевой архитектуры:


{% begin %}
    {% long_bl=64; gen_bl=32 %}
    {% if flag?(:x86_64) %}
        {% long_bl=long_bl*2; gen_bl=gen_bl*2 %}
    {% else %}
    # do nothing here
    {% end %} 
    alias Integer = Int{{gen_bl}} | UInt{{gen_bl}}
    alias LongInt = Int{{long_bl}} | UInt{{long_bl}}
    x: Integer=11_u{{gen_bl}}
    y: LongInt=12_i{{long_bl}}
    {% for var in %w(x y) %}
        puts "run-time class of var {{var.id}} is #{{{var.id}}.class}"
    {% end %}
{% end %}


Помимо некоторых интересных особенностей весьма умного движка макросов в языке Crystal, в данном примере на всякий случай показаны и некоторые его (языка) весьма досадные «родовые пятна»: например, для для того, чтобы поместить целое число в переменную целого же типа (не совпадающего с «умолчательным», зависящим от того, под какую архитектуру собран сам Crystal) — компилятору почему-то необходима вот такая откровенно уродливая конструкция, использующая некие самопальные суффиксы, изобретённые разработчиками из Manas, главного спонсора создания Crystal, не иначе как в горячем латинском ночном бреду:

x : Int128 = 11_i128

Видите вот это отвратительное "_i128"? Так вот, скоро вы его не увидите, ну или, вполне возможно даже, что на момент прочтения вами настоящей заметки — уже и не увидите, если только не поставите себе устаревшую версию Crystal. Дело в том, что Crystal 0.24.2, которым я пользуюсь сейчас — это один из последних выпусков, в которых упомянутая выше суффиксная кривизна всё ещё присутствует. Разработчики в курсе проблемы — и находятся на пути к скорейшему её решению.

А за сим про условную компиляцию пока что всё, в следующий же раз я расскажу вам о том, какие классы переменных наиболее общеупотребительны при написании макросов и какие у этих классов и у порождённых по их «образу и подобию» объектов (англ. «инстансов») — есть интересные методы и свойства.

Не переключайтесь и до новых встреч в прямом эфире!

Макросы: оператор for

Случилось как-то раз мне заинтересоваться возможностями макросов в языке Crystal. Если честно, я даже как-то особо ни на что и не рассчитывал, да и нужны были самые базовые вещи. И какой же приятной неожиданностью стало для меня то, что макросы в Crystal оказались продуманным до мелочей простым и понятным встроенным языком с обалденным функционалом (по меркам макроязыков). При этом, пожалуй, макроязык в Crystal даже более элегантный и читабельный, чем основной, «рантаймовый».

Например, знаете ли вы что в Crystal нет оператора организации цикла for? Его нет ни в каком виде, разработчики принципиально отказались от старого доброго for в пользу итераторов «для всего».

Так вот, удивительное дело, но во встроенном языке макросов есть for (который по сути foreach), и он умеет:

# перебирать элементы списка
{% begin %}
    {% for k in ["a","b","c","d"] %}
    {% puts k.id %}
    puts {{k}}
    {% end %}
{% end %}

Первый puts «напечатает» имена элементов списка во время компиляции, а второй — станет частью сгенерированного реального кода программы и «сработает» в рантайме.

а ещё макро-for'ом можно:

# Перебирать пары ключ-значение в хешах
# ...и псевдохешах со статичными ключами (NamedTuple'ах)
{% begin %}

    {% for k,v in { "a"=>1, "b"=>2, "c"=>3, "d"=>4 } %}
    {% puts k.id + " => " + v.id.stringify %}
    puts %q({{k}} => {{v}})
    {% end %}
    
    {% for k,v in { type: "road", wheel_size: "700c", ett: "555mm", weight: 7.5 } %}
    {% puts k.id + " => " + v.stringify %}
    puts %q({{k}} => {{v}})
    {% end %}
    
{% end %}

Ну разве это не прекрасно? :)

А в следующий раз я расскажу вам немного об условной компиляции и о типе StringLiteral в макросах crystal'а

Держите руку на пульсе технологий с человечным интерфейсом и не переключайтесь!

Записи в FreePBX в формате MP3

  • Linux
По умолчанию в mp3 freepbx писать не умеет, поэтому сделал, чтобы записи конвертировались потом, в кроне. Запускаю конвертацию ночью и все что накопилось в wav конвертируется в mp3. Имя файла в базе меняется на mp3
Сначала поставим lame
yum install lame

Можете попровать запустить конвертацию на любом wav файле
lame -h -b 32 test.wav test.mp3

Если все ок, запускайте скрипт
#!/bin/sh
#convert wav to mp3 asterisk recordings
cdrdb="asteriskcdrdb"
user="freepbxuser" #пользователь субд для отчетов
pass="password" #пароль пользователя субд

for i in `find /var/asterisk/spool/monitor -type f -name "*.wav"`
do
 if [ -e "$i" ]; then
    file=`basename "$i" .wav`;
    dir=`dirname "$i"`;
    lame -h -b 32 "$i" "$dir/$file.mp3";
    rm -f "$dir/$file.wav";
    mysql -u $user -p$pass --execute='use asteriskcdrdb;UPDATE cdr SET recordingfile="'$file'.mp3" WHERE recordingfile="'$file'.wav";';
  fi
done