BASH Tips&Tricks #0010: И на BASH'е будут join'иться массивы!

  • BASH
Когда я пишу на Perl, у меня буквально всё под рукой. Кое-что даже мешается и заставляет постоянно проверять кусочки кода в командной строке (perl -e или perl -E), дабы сделать полёт моей фантазии более комфортным и безопасным для здоровья разрабатываемой софтины.

Когда я пишу на BASH, мне, конечно же, многого не хватает: необъятные возможности работы с текстом в Perl'е накладывают свой отпечаток на отношение к другим языкам. Кое-что приходится терпеть, но многое вполне можно исправить, предоставив самому себе привычное окружение, пусть и несколько… задумчивое («бытует мнение», что в случае с BASH скоростью работы обычно можно пренебречь).

Так случилось и с функцией join, которой мне всегда так не хватало в BASH. Я, конечно же, говорю не о той join, которая «join lines of two files on a common field», а об одноимённой функции Perl, «склеивающей» элементы массива (если быть точным, то всё-таки «вектора») в строку.

Я долго терпел сие неудобство и всячески его игнорировал, но… в какой-то момент терпение моё лопнуло, и я решился на сотворение мира в атомарных масштаба. По итогам краткого ознакомления с бесплодными исканиями на StackOverflow и захватывающе познавательного обсуждения join-вопросов с умными людьми © на linux.org.ru, мною был исторгнут приведённый ниже код:

join2 () {
 (( $# == 2 || $# == 3 )) || return 1
 local delim=$1
 local arrName=$2
 local join2var=$3
 local v
 for v in ${join2var:+join2var} arrName; do
  [[ ${!v} =~ ^[_0-9A-Za-z]+$ ]] || return 2
 done
 [[ $join2var ]] || join2var='v'
 source <(
        cat <<SOURCE
         printf -v $join2var "%s${delim}" "\${$arrName[@]}" || return \$?
         [[ \$delim ]] && $join2var=\${$join2var:0:-${#delim}}
SOURCE
 )
 (( $# == 2 )) && echo "$v"
 return 0
}


Бесспорно, он (код) как всегда великолепен и безупречен… пока мною же не будет доказано обратное :)

Квинтэссенция сути функции join2 (звучит как «Join To») заключается в том, что встроенная команда BASH printf понимает конструкцию ${arr[@]} не как уже интерполированную строку из элементов массива, объединённых абы чем (пробелами), но как именно набор элементов. Что эта белиберда значит? А то, что если какой-либо элемент массива уже содержит пробелы, а его объединят пробелом с элементом соседним, printf всё равно «увидит» каждый элемент в отдельности.

Пользоваться join2 можно двояко:
  1. 
    declare -a arr=('a b c' 'd e f' 'g h i')
    join2 '///' arr
    

    — и в этом случае результат конкатенации элементов массива будет «выведен на экран», то есть отправлен на STDOUT, где его легко подобрать так:
    str=$(join2  '///' arr)


    ИЛИ… (барабанная дробь, фанфары!)
  2. 
    declare -a arr=('a b c' 'd e f' 'g h i')
    join2 '///' arr str
    echo "$str"
    

    — а в этом случае название функции окажется весьма неслучайным, поскольку результат join'а запишется сразу в переменную str.

Cакральное знание об особенностях работы printf с массивами в BASH нагло сп скромно почёрпнуто из Yet Another BASH FAQ'а.

Пример преобразования форматов на BASH 4

  • BASH
Если кто-нибудь скажет Вам, что BASH — это какой-то недостойный внимания недоязык для написания циклов из командочек, киньте ему ссылку на данный пример.

Пример совсем несложный, я только что написал его для StackOverflow (где, к сожалению, люди всё ещё живут в криогенных камерах, не ведая о существовании BASH 4-й версии):

#!/bin/bash
inFile=$1
outFile=$2

join () {
 local del=$1
 shift
 IFS="$del"
 source <(
        cat <<SOURCE
 echo "\${$1[*]}"
SOURCE
 ) 
 unset IFS
}

declare -a CSV=('"Module Name","Module Group","Module Version"')
declare -a keysAccepted=('Name' 'Group' 'Version')

declare -i nMandatoryKeys=${#keysAccepted[@]}
declare -A KeyFilled
rxKeysAccepted='('$(join '|' keysAccepted)')'
while read line; do
        [[ $line =~ \<strong\>Module\ $rxKeysAccepted:\</strong\>[[:space:]]*([^<]+)\</p\> ]] || continue
        key=${BASH_REMATCH[1]}
        val=${BASH_REMATCH[2]}
        KeyFilled[$key]=$val
        if (( ${#KeyFilled[@]} == nMandatoryKeys )); then
                unset csvLine
                for k in ${keysAccepted[@]}; do
                        csvLine+=${csvLine:+,}${KeyFilled[$k]}
                done
                KeyFilled=()
                CSV+=($csvLine)
        fi
done <"$inFile"

(( ${#CSV[@]} > 1 )) || exit 1

join $'\x0a' CSV >"$outFile"


BASH-скрипт состоит из sed'ов, awk и grep'ов?
Давайте подсчитаем количество вхождений того, другого и третьего в данном примере!

Не спорю, тот же самый код на Perl, лучшем языке для работы с текстами, получился бы и короче намного, и быстрее.

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

Простейший hexdump, который делает именно то, что вам нужно

Хотите просто получить шестнадцатеричный дамп файла, при этом не занимаясь контрпродуктивным подбором опций командной строки для hexdump, который, как известно, вне зависимости от того, о чём вы его просите, всегда готов любезно сделать что-нибудь «совсем не то»?

Воспользуйтесь простейшим однострочником на Perl:

perl -e 'do { local $/; print unpack("H*",<>) }'  <FILE


Всё гениальное — это просто Perl!

P.S. То есть нет, простите,
«Всё гениальное — это просто Perl!» ©

Простенький пример использования Moo

Неожиданно выяснил, что документации по модулю Moo, делающему возможным «ООП с человеческим лицом» в Perl практически нет, а та, что есть — вообще почему-то платная (?! я сам был удивлён весьма).

Для того, чтобы хоть немного ликвидировать столь странный «пробел» я написал по итогам нескольких часов «изучения» Moo маленький пример, которым и хочу с вами, мои дорогие читатели, поделиться:

Читать дальше →

BASH Tips&Tricks #000F: Ваша перловка, сэр!

  • BASH
Я часто использую ассоциативные массивы BASH, и все, даже те, кому это было не очень интересно, уже успели познакомиться с этим истинным предметом моего обожания. Иногда я даже генерирую эти ассоциативные массивы, они же «хэши», и складываю их в отдельные include-файлики «на память». Разумеется, это совсем не связано с тем, что реализация «хэшей» в BASH очень медленная и сама по себе их генерация может отнимать столько же процессорного времени, сколько потребовалось бы для получения числа «пи» с точностью до 1000-ного знака после запятой.
Но случается иногда с BASH-программистами казус (не имеющий отношение к глубокоуважаемому Кукоцкому), когда в их наработанное годами упорного труда тёплое скриптовое счастье врывается злобный Perl и требует переписать всё с нуля. Почему? Ну хотя бы потому что гладиолус. Шутка. На самом деле BASH действительно безумно медлителен, так что в какой-то момент и у крутых серверных железок, и у вас может банально не хватить терпения, после чего и возникнет вот эта самая поистине революционная идея: а давайте перепишем всё на Perl!
Читать дальше →

Netflow-искания Часть 4.1 (Netflow v9 collector script)

В общем похоже некий этап преодолен и теперь можно продемонстрировать первую версию моего коллектора. И да, я расположил свой скриптец на sourceforge. Это было по крайней мере мне интересно сделать.

Возможности:
  1. Работа с протоколами NetFlow 5 и 9 версии;
  2. Работа со всеми устройствами через один или несколько портов;
  3. Хранение данных в базе MySQL;
  4. Работа с различными типами сенсоров;
  5. Работа на различных ОС.

Системные требования:
  1. PIII и выше;
  2. 512MB RAM;
  3. наличие сетевого адаптера.

Требования к ПО:
  1. OS windows/*nix/MacOS (да, эта фигня должна работать на всем подряд, вопрос в прямоте ваших рук);
  2. MySQL 5.5 версии и выше;
  3. Perl 5.14 и выше.

Читать дальше →

Netflow-искания Часть 4 (Netflow v9 collector)

Предисловие:

Опасное это дело, Фродо, — выходить за порог: стоит ступить на дорогу и, если дашь волю ногам, неизвестно куда тебя занесёт.

Задача: Получить и обработать данные с сенсора netflow v9.

Подготовка
Запускаем уже полюбившийся скрипт и захватываем первый пакет, переданный от маршрутизатора:
#!/usr/bin/perl
use IO::Socket::INET;
$| = 1;
my ($socket,$received_data);
my ($peeraddress,$peerport);
$socket = new IO::Socket::INET (
LocalPort => '9999',
Proto => 'udp',
) or die "ERROR in Socket Creation : $!\n";
while(!$recieved_data)
{
$socket->recv($recieved_data,4096);
}

open (MYFILE, '>data.txt');
print MYFILE $recieved_data;
close (MYFILE);

$socket->close();

Читать дальше →

Netflow-искания Часть 3.1 (изобретаем анализатор)

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

Преамбула: Есть такая классная штука, как сервер видеонаблюдения. Еще есть не менее классная штука как сервер ВКС. И они очень любят кушать канал связи. 10 мегабит для них — воронка, в которую они пытаются просунуть свой арбуз. Одним весенним утром наш Мега-Босс захотел воспользоваться конференц-связью дабы отыметь своих подчиненных пусть не физически, но хоть так. И что характерно — отымел, но совсем не тех кого ожидали, а наш отдел. Качество связи было отвратительным.
Естественно сам факт совершенного насилия над нами был лишь следствием. Захотелось узнать истинные причины этого. Одной из задач стояло составление очень сложного отчета по трафику в разрезах по сервисам. То есть грубо говоря необходимо было поставить на мониторинг и детально разобрать трафик для выяснения причин плохого качества ВКС.

Задача: Сформировать сложный, но читабельный для всех отчет по трафику по заданным периодам в разрезе по различным сервисам/источникам данных.

Исходные данные: Сырые данные, собранные Flow-Tools.

Читать дальше →

Netflow-искания Часть 3 (изобретаем коллектор)

Итак поигрались с готовыми решениями — красота. Что дальше? Лично меня очень беспокоила информация о потерянных пакетах в логах:
# /var/log/flow-capture.log:
ftpdu_seq_check(): src_ip=xxx.xxx.xxx.xxx dst_ip=0.0.0.0 d_version=5 expecting=1531521097 received=1531521307 lost=210
ftpdu_seq_check(): src_ip=xxx.xxx.xxx.xxx dst_ip=102.0.0.0 d_version=5 expecting=1730244036 received=1730300526 lost=56490
ftpdu_seq_check(): src_ip=xxx.xxx.xxx.xxx dst_ip=99.111.117.110 d_version=5 expecting=1730207586 received=1730300556 lost=92970
ftpdu_seq_check(): src_ip=xxx.xxx.xxx.xxx dst_ip=102.0.0.0 d_version=5 expecting=1730300556 received=1730300586 lost=30

Плюс ко всему какие то нелепые dst_ip.

Что бы не мешать основному процессу, добавляю на сенсоре строчку для дублирования инфы на другой порт:
# conf t
# ip flow-export destination xxx.xxx.xxx.xxx 9997

Циска ругнется, что мы дублируем инфу, но послушно выполнит.

Читать дальше →

BASH Tips&Tricks #0002: Форматируем вывод ldapsearch

  • BASH
Если вам случалось писать на BASH скрипты, работающие с LDAP, то вы наверное уже в курсе, что клиентская утилита ldapsearch, входящая в поставку OpenLDAP, а потому имеющаяся в комплекте большинства дистрибутивов Linux, из каких-то своих соображений (оказывается, это требование RFC 2045) выводит результирующий LDIF со строками, длина которых не превышает 78 символов. Соответственно, те строки, что оказываются длиннее 78-и символов, попросту разбиваются по правилам формата LDIF: в начале продолжения предыдущей строки ставится один пробельный символ.
Анализ такого LDIF на языке оболочки bash может быть затруднительным, гораздо удобнее, когда значение атрибута умещается в одной строке.
Читать дальше →