Песня Коронавируса (пародия на В. Цой — Прохожий)

Я гуляю по проспекту
Мне не надо ничего
Свои гены растопырил
Заражу я хоть кого

Эй прохожий проходи
Эх пока не заразил!

Эй прохожий проходи
Эх пока не заразил!

В помещеньях я хожу
Без билета
Осенью,весной хожу
Зимой и летом

Эй прохожий проходи
Эх пока не заразил!

Эй прохожий проходи
Эх пока не заразил!

Прихожу домой я ночью
И включаю интернет
А сосед за стенкой стонет
Кашляет, его уж нет

Эй прохожий проходи
Эх пока не заразил!

Эй прохожий проходи
Эх пока не заразил!

Эй прохожий проходи
Эх пока не заразил!

Эй прохожий проходи
Эх пока не заразил!

Linux. Есть ли слово в строке. Есть ли подстрока в строке.

Довольно частой задачей является узнать, встречается ли в строке какое-нибудь слово или же, в более общем случае, подстрока.
Для решения можно воспользоваться внутренними механизмами bash, но я покажу более башенезависимый способ, с помощью grep. Благо он есть практически везде, даже в каких-нибудь системах, основанных на BusyBox, а вот вместо bash может встретится и просто sh, и что-нибудь более экзотическое.

Есть ли слово в строке

Предположим, что в переменной STR имеется строка:

STR="cat lynx lion coguar"

В переменную SUBSTR запишем первый параметр командной строки скрипта ($1):

SUBSTR="$1"

Выводим содержимое переменной $STR, передаем вывод grep с нужными параметрами, а результат сохраняем в переменную-счетчик:

CNTR=`echo "$STR" | grep -w -c "$SUBSTR"`

В данной команде более всего интересны параметры grep:

-w — искать целое слово.

Примечание: Словом по умолчанию считается все, что отделено от других символов пробелом(-ами) табуляцией(-ами) или переводом(-ами) строки.

— подсчитать количество строк с нужным вхождением

Примечание: Ключ заставляет grep подсчитывать строки, а не сами вхождения, так что количество вхождений слова в строку, так посчитать не получится.

В итоге в переменной CNTR оказывается 0, если совпадений нет, и 1, если совпадение есть, остается только проверить:

if [ "$CNTR" -ne 0 ];then
    echo "Exist"
else
    echo "Not exist"
fi

Демо-скрипт на GitHub

Примеры работы:

./exist-word lion

Вывод:
Exist

./exist-word dog
./exist-word li

Вывод:

Not exist

Более общий случай: имеется ли в строке подстрока.

Под подстрокой имеется любой набор символов, идущих подряд.

Задача решается аналогично предыдущей, только из параметров grep удаляется ключ -w

Если из вышеуказанного скрипта удалить ключ -w grep‘а, то вывод будет таким:

./exist-word li

Вывод:

Exist

Т.е. теперь была найдена подстрока li (часть слова lion).

Тридцать три короны

В центре города Уханя,
Где травинки не растет,
Жил великий вирусолог,
Черный маг и руноплет.

Иногда ему Иные
Помогали, как могли
Даже, блин, грибы с Юггота
(Хоть им было неохота)
РНК ему дАли!

Тридцать три короны,
Тридцать три короны,
Тридцать три короны,
свежая строка

Тридцать три короны,
Вирь родился новый
Из куска Иного РНК!

В пять утра вставал он ровно,
Пентаграммочку чертил,
Заносил туда он колбы,
Чашки Петри заносил.

День за днём промчалось лето,
Звезды встали как должны.
Результат эксперимента
И побочные эффекты
Были всем нам явлены!

Тридцать три короны,
Тридцать три короны,
Тридцать три короны,
свежая строка

Тридцать три короны,
Вирь родился новый
Из куска Иного РНК!

Linux shell, pipes (конвейеры) и код завершения.

Преамбула

При работе с конвейерами (пайпами, «трубами», pipe), часто возникает вопрос, как отловить ошибку, произошедшую внутри конвейера.

Суть проблемы

Для примера возьмем код, где ход загрузки файла отображается dialog’овым прогрессбаром (копия):

#!/bin/bash

FADDR="http://tolik-punkoff.com/static/test.mp3"

wget --progress=dot -O "./test.mp3" "$FADDR" 2>&1 |\
stdbuf -o0 awk '/[.] +[0-9][0-9]?[0-9]?%/ { print substr($0,63,3) }' | \
dialog --gauge "Download file from $FADDR" 10 100

Как видите, конвейер (т.е. передача вывода от одной команды другой через |), тут налицо.

Если после данной команды просто дописать echo $?, то будет видно, что результат будет 0.

И когда все в порядке:

И когда все не в порядке (в данном случае, сделан обрыв связи):

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

Решение

В современных версиях bash перед использованием контейнера, необходимо добавить команду:

set -o pipefail

В старых bash и некоторых других оболочках придется извращаться, может как-нибудь вернусь к вопросу (если для какого утюга что писать буду).

Примечание: Следует иметь в виду “безобидные” команды, которые могут вернуть не ноль.
В больших скриптах со сложными конструкциями и длинными конвеерами можно упустить этот момент из виду, что может привести к некорректным результатам.

Проверка

После добавления вышеуказанной команды в скрипт делаем тест с обрывом связи:

Или указываем в переменной $FADDR некорректный адрес:

Теперь echo $? отображает корректный код завершения.

Модификация скрипта

Скрипт на GitHub

Источник

Linux pipes tips & tricks Копия в PDF

Скачивание файла и обработка ошибок в wget. Обратите внимание!

Преамбула

Хотел, конечно, назвать как-нибудь типа «гадское поведение wget» или «уничтожение файлов wget‘ом», но остановился на этом. Добавлю ссылку в пост о кодах ошибок (копия) на этот пост.

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

Суть проблемы

Оказывается, wget создает новый файл перед закачкой. Несмотря на то, появятся там какие-то ошибки или нет, и несмотря на то, какие ошибки это будут, хоть это ошибки сервера (например 404, файл не найден), или ошибки сети (файл не удалось докачать), файл все равно будет создан. Хотя по логике, например, ошибку сервера (пусть 404) можно было бы сначала и проверить. Но wget этого не делает, если не случилась ошибка с кодом 2 (ошибка параметров командной строки или конфигурационных файлов).

На самом деле, такой подход довольно правильный, ну и правда, какая разница, почему оно не скачалось (или не сохранилось на диск в случае I/O error). И для анализа ошибок проблем меньше.

Наибольшую проблему это представляет собой тогда, когда пользователь указывает в параметрах wget ключ -O <путь к файлу>, тогда wget придется его полюбому перезаписать.

Так-то у wget есть «защита от дурака», если файл, например, file уже существует, то при следующей закачке файл file (под тем же именем) будет сохранен, как file.1, и если что, старый файл останется в целости и сохранности. Но в скриптах ключ -O удобно использовать. Не надо следить за этими номерами файлов, и вообще в скрипте, wget без ключей, один сплошной геморрой. Так что ключи используют. Но как же правильно это сделать? Да выбирать место сохранения скачанного и проверять коды ошибок wget!

Решение

Если нужно скачать файл, а уж если скачался, заменить, не скачался, оставить старый

1. Скачиваем файл во временный:
wget -O "/tmp/test.tmp" "http://example.org/test.dat"
2. Проверяем коды ошибок (здесь самый простой вариант)

if [ "$?" -eq 0 ]; then
	#Пункт 3
fi


3. Копируем скачанный файл в целевой: cp /tmp/test.tmp /home/pi/test.dat

Естественно, все имена файлов и пути меняем на свои.

Надо скачать файл. Не скачался — вернуть сообщение об ошибке

1. Скачиваем файл:
wget -O "~.data/test.dat" "http://example.org/test.dat"

2. Проверяем коды ошибок (здесь самый простой вариант). Если ошибка — завершаем работу:

if [ "$?" -ne 0 ]; then
	rm "~.data/test.dat"
	echo "Data download error!"
	exit
fi

Посмотреть размер каталога в Linux. В консоли, но интерактивно и наглядно (ncdu).

И наглядно увидеть, где засрано 🙂

Для отображения размеров каталогов (с подкаталогами), есть стнадартная утилита du, но, если честно, она не очень удобная. Гораздо удобнее ее аналог, написанный с помощью ncursesncdu

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

ncdu

Наверное, ее единственный минус, что в каталог уровнем выше, чем тот, из которого ее вызвали, она не переходит. Так что если хотите посмотреть размеры каталогов сразу во всей файловой системе, надо вызывать ее из корня (/). Но причина такому поведению легко может быть найдена — после запуска утилита сканирует все подкаталоги, чтоб потом нам красиво показать. Чем с более верхнего уровня ее вызываешь, тем дольше процесс сканирования.


Сканирование

В подкаталоги входить можно по ENTER, возврат назад по стрелке влево, клавише h или < (обычно запятая с шифтом) или по нажатию ENTER на .., как в mc. Краткая справка по ? (не забывайте нажать SHIFT), выход по q.


Отображение каталогов и их размеров

В Slackware устанавливается штатным образом через sbopkg, как в других линуксах не знаю, в Убунте и Дебиане есть в репозиториях.

Linux wget, коды возврата/завершения. Коды ошибок wget.

Раз уж пошел разговор про wget (копия), решил я заодно дополнить добрым людям скрипт какими-нибудь осмысленными сообщениями об ошибках, а для того нужны коды завершения.

Кто-то, неизвестно почему, утверждал, что в man их нет. Есть. Но пусть уж будут и тут, и мне от склероза, и мало ли кому другому, кто не любит на буржуйском читать.

Коды завершения wget

0 — OK (ошибок нет)
1 — Иная / общая ошибка (generic error code)
2 — Ошибка в параметрах командной строки или файлах конфигурации (.wgetrc или .netrc)
3 — Ошибка файлового ввода/вывода (I/O error)
4 — Ошибка сети (например, при обрыве связи)
5 — Ошибка SSL
6 — Ошибка идентификации (неправильное имя пользователя или пароль)
7 — Ошибка протокола
8 — Ошибка сервера (например, нужный файл на сервере не найден, ошибка 404)

За исключением 0 и 1, коды выхода с меньшими номерами имеют приоритет над кодами с большими номерами, когда встречаются многочисленные типы ошибок.

Linux wget, тайм-аут и повторы. «Виснет» wget при разрыве связи.

Преамбула

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

Проблема

Оказалось, скрипт вис, если пропадало соединение с сетью.
Выгрузка производилась простой командой:

wget -O "~/buh01/data/kassa01.dat" "https://example.org/mag01/kassa.dat"

Решение

Оказывается, у wget не проставлен тайм-аут ожидания, а по умолчанию, если его не проставить, wget будет ждать аж 900 секунд (15 минут). Надо, соответственно, его напрямую указать в параметре ключа -T <секунды> (--timeout=<секунды>),
Например, можно поставить вменяемые полминуты (30 с.):

wget --timeout=30 -O "~/buh01/data/kassa01.dat" "https://example.org/mag01/kassa.dat"

Т.е. вис-то, собственно wget, хотя конечно же, не вис, а терпеливо ждал. Но тут и не бухгалтеру, а даже не сильно опытному пользователю ничего не стоит принять такое поведение за то, что wget виснет при разрыве соединения.

Заодно можно указать wget‘у количество попыток для скачивания файла: -t <число> (--tries=<число>), тем более, что указание параметра -O (имя выходного файла) сбрасывает этот параметр в 1.

wget --timeout=30 --tries=3 -O "~/buh01/data/kassa01.dat" "https://example.org/mag01/kassa.dat"

Полезные ссылки

Шпаргалка по Wget. Копия в PDF

Linux dialog —infobox с «мельницей», скрашивающей процесс ожидания.

Сделал гибрид старого скрипта waiter (копия) с dialog --infobox. В infobox‘е отображается псевдографическая «мельница»

https://www.youtube.com/watch?v=o9KempHy5To

Не стал уж совсем дублировать waiter, так что никаких параметров демо-скрипт не принимает, просто отсчитывает 10 секунд, показывая псевдографическую «крутилку».

Исходник на GitHub

Linux. Удалить комментарии из файла.

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

grep -o '^[^#]*' file.txt > cleaned.txt

Вместо file.txt подставляем исходный файл, вместо cleaned.txt — выходной.

Нашел здесь Копия в PDF

Там есть и другие варианты решения задачи.

Экзаменационные байки от PunkArr[]

Квасим сегодня редакцией по поводу очередной годовщины окончания «СООУ» (Самого Охуенного Образовательного Учреждения) товарищем PunkArr[]. Это же СООУ пытался закончить и L.S., но не взлетело, пришлось Гоптех aka Лестех заканчивать. Но сегодня истории не про Гоптех, а про СООУ.

Исполнитель анальных наказаний. Любимые приколы П.И.

Это наш препод по спецпредметам (электротехника и сопутствующие) П.И. Инженер, программист паскалем и ассемблером, тролль (хотя тогда такого слова не было) и электрик 80 уровня. Как он сам говорил — электротехнику я знаю на 4, на 5 ее знает Бог. Т.е. больше, чем на 3, рассчитывать на первом курсе не приходилось. Не, в группе был уникум, который получил 5. И вообще, редко получал даже 3. Ну он сейчас вроде как целую электростанцию возглавляет, так что не зря.

Из любимых приколов П.И.

Рисует П.И. на доске схему, чтоб мы ее рассчитали на уроке. И тут какая-то сука с задних парт возьми да ляпни: «Простая же схема».
П.И.: Действительно, простая. Ща поправлю.
Стирает и рисует хтонический ужас, достойный Лавкрафтовского Некрономикона. Сонмища резисторов, конденсаторов, индуктушек, расположенных в самом причудливом порядке. Элементов на 150 мог захреначить, причем из головы.
Естественно, вся группа ловит по «лебедю».
Его рекорд — 180 двоек на группу за одну пару. Точнее наш рекорд. За несданные практические работы.

Раздача медалей после контрольной. Робкий голос из зала «А почему мне 3?»
П.И.: Ну-ка, ходи сюда, еретик и крамольничек!
Внимательно рассматривает работу. Находит две ошибки.
Да какое тебе три! Тебе две!

Но препод был действительно от Ктулху, если видел в студенте заинтересованность, а студент задавал правильные вопросы хотя бы на семантическом уровне. Типа, не «почему мне два», а «какие ошибки я допустил». Кстати, когда мы более-менее подружились — надарил мне две огромных сумки раритетных изданий фантастики. Книги живы до сих пор.

Экзамен по электротехнике

Берем билеты, готовимся, первым отвечать никто не идет.

Повисает мхатовская пауза. Сильно мхатовская. Пауза превращается в Дамоклов меч.

Наконец, П.И. надоедает, он тасует зачетки как колоду карт, тыкает в меня пальцем и спрашивает, какая и откуда (сверху или снизу).

Я говорю, что первая снизу, и это оказывается МОЯ, блядь, МОЯ трижды ебаная зачетка. Так я даже в повсеместно стоящих игровых автоматах не проигрывал. 1/25, сука. Жестокая сука теория вероятности.

Он дает еще некоторое время дописать билет, а потом спрашивает «А ты кто по гороскопу?» Я отвечаю, что Дева. Он глубокомысленно листает зачетку, типа смотрит в гороскоп, а потом выдает:
— У тебя сегодня ужасное в гороскопе написано…
— А что?
— Сегодня Дева станет раком!
Пришлось идти отвечать, получил в результате 4, хотя с перепугу забыл закон Ома для полной цепи, который прямо на плакате в кабинете на стене висел. Над чем П.И. вместе со всей группой откровенно поржали.

Кому грядеше, экзамене?

Второй случай, у того же препода, но на этот раз он ведет два похожих предмета: «Ремонт электрооборудования» и «Эксплуатация электрооборудования». Мало того, что предметы похожие, вопросы в билетах похожие, так еще и экзамены подряд, один сегодня, другой завтра.

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

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

Я объясняю, дядька дает мне полтос и говорит, сходить за маленькой. Поскольку водку я тогда пил редко, то он в меня ее влил, размешивая с полстаканом кофе растворимого.

Я так стаканов 8 выпил. И меня поперло! Эффект, скажу вам, был не хуже, чем от спидов (которые я попробовал гораздо позже). Аж прибежал на экзамен, вытащил билет, и попросился первым без подготовки. Препод от такой наглости офигел, но согласился.

И вот я рассказал билет на одном дыхании.

Препод (П): Отлично! Но какой предмет мы сегодня сдаем?
Я: Ремонт!
П: Нет, это ты почему-то сдал без подготовки ремонт. А все сдают эксплуатацию. Поэтому по ремонту тебе 5, а по эксплуатации 2. Завтра пересдаешь на ремонте эксплуатацию.
Пришлось пересдавать…
Эксплуатацию сдал на 4 без допинга.

Замена концов строк MAC (CR, 015 OCT, 0xD HEX) на концы строк Linux (LF, 012 OCT, 0xA HEX)

Преамбула

Понадобилось для работы. Современная MacOS такие концы строк не использует, а некоторое ПО, для совместимости со старыми маками (LC II/LC III) их еще использует. И сами маки в качестве терминалов для наблюдения за датчиками погоды еще юзаются. И живые. Я на таких учился в школе.

Это же аптайам > 20 лет, хуясе. Сии компьютеры младше меня на 5 лет всего. А по характеристикам Intel 486DX 286, ну 386 в лучшем случае, а на них есть оконный интерфейс. Правда, консоли нет и коды ошибок уже не найти (а там скудные сообщения об ошибках — messagebox с кодом).

Решение

cat "file.txt" |tr '\015' '\012' >"file2.txt"

Техническое

Свобода слова предполагает и право на нахуй. Потому во всех копиях наших блогов аноны закрыты. Редактора заебало вручную удалять оффтоп и спам. Тем не менее, мы всегда ответим на адекватные комментарии.

L.S., директор проекта

Linux. Кодирование/декодирование файлов в/из BASE64

Преамбула

Как однажды сказал мой учитель, «линукс может все, а многое еще и из коробки, надо только знать как». Вот в данном случае именно так и оказалось. Понадобилось мне массово перекодировать файлы в/из BASE64.

BASE64 это такой древний формат данных, использующийся для передачи двоичных данных по сети. В век всеобщего юникода и кучи всякого софта это не так видно пользователю, но когда все только начиналось, с передачей данных была большая жопа. Даже на «железном» уровне некоторые машины поддерживали только семибитную кодировку, а уж о софте, не поддерживающем ничего кроме великого и могучего английского языка и говорить не приходится. Т.е. любой символ кроме букв английского алфавита и цифр мог быть либо выкинут к чертям собачьим, либо невозбранно использован как служебный. Соответственно, любой бинарный файл мог каким либо софтом, например, хитровыебанным почтовым сервером, разорван в клочья при передаче из пункта А в пункт Б.

Вот и придумали умные люди, а давайте все, что не влазит в английский алфавит и цифры, будем по хитрому алгоритму заменять, таки да. На английский алфавит и цифры. Так и получился BASE64. Подобная хрень в криптографии называется «транспортная броня».

Так вот, в Linux из коробки (входит в пакет Coreutils, который есть почти везде) есть утилита, способная кодировать/декодировать BASE64. Называется она, внезапно, base64.

Кодирование

Для опытов возьмем восьмибитного Сиро Исии. Он, когда живой был, много кого брал на опыты.

base64 isia.png >isia.b64

Закодированный Сиро Исии на Pastebin

Декодирование

base64 -d isia.b64 >isia.decoded.png

Тот случай, когда суть получилась короче преамбулы.

Аналог trim() в bash/linux shell

Преамбула

Пишу небольшой скрипт, для которого надо сохранять две выбранные пользователем при предыдущих запусках скрипта опции, а при следующих запусках их восстанавливать.
Решил воспользоваться самым простым методом. Создаем рядом со скриптом 2 файла, и туда сохраняем значения опций:

echo "$OPT1" > ./option1
echo "$OPT2" > ./option2

Некрасиво, но просто.

При запуске скрипта, читаем опции обратно:

OPT1=`cat ./option1`
OPT2=`cat ./option2`

На всякий случай лучше обрезать лишние пробелы или «пустые» символы типа TAB или переводов строки. Во всяком случае, так рекомендуют во всех руководствах по программированию, и обычно, в каждом современном языке, в некотором виде есть функция trim(), которая обрезает начальные и конечные пробелы.

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

Тестовый файл и вывод его в консоль

Возьмем, например, такой тестовый файл: На PasteBin

Если мы просто выведем его на консоль с помощью команды cat "trimtext.txt", то получится следующее:

smallwolfie@wolfschanze:~/myfiles/test/trim# ./testtrim


                   Trim()

smallwolfie@wolfschanze:~/myfiles/test/trim#

т.е. со всеми пробелами и пустыми строками.

Решение

Самая простая реализация trim() в shell/bash пропустить вывод команды через xargs без параметров. xargs, которая требует после себя команды, без указания команды все равно что echo без параметров. А echo без параметров все пустое обрезает.

#!/bin/bash

cat "trimtext.txt"|xargs

Вывод:

./testtrim
Trim()

Linux dialog, прикручиваем симпатичный progressbar к wget.

Преамбула

Какой бы ваш скрипт не был диалоговым, «само дерево жужжать не может», а в скрипте обычно жужжат утилиты, которые о диалоговых окнах представления не имеют. Так вот прикрутим контролируемый прогрессбар (--gauge) из виджетов dialog к всем известному wget.

Готовый код

#!/bin/bash

FADDR="http://tolik-punkoff.com/static/test.mp3"

wget --progress=dot -O "./test.mp3" "$FADDR" 2>&1 |\
stdbuf -o0 awk '/[.] +[0-9][0-9]?[0-9]?%/ { print substr($0,63,3) }' | \
dialog --gauge "Download file from $FADDR" 10 100

Разбираем, что он делает

1. Если просто запустить wget без параметра --progress=dot (параметр -O "./test.mp3" здесь показывает путь и имя для сохраняемого файла, для теста оно у нас жестко задано), то wget попытается нарисовать свой плохенький progressbar:

Использование параметра --progress=dot приводит вывод на экран в такой вид:

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

2. wget почему-то выводит сообщения о своей работе не на stdout, а на stderr. Аж морово поветрие какое-то, ибо и некоторые другие утилиты тоже выводят на stderr. Оправданно на stderr выводит сообщения только dialog, тому ще он всю псевдографику выводит на stdout. Отучаем wget писать на stderr вместо stdout:

2>&1

2 — зарезервированный канал для stderr 1 — для stdout. Тут мы просто перенаправляем один канал в другой.

3. Меняем буферизацию. На самом деле, буфер консоли (терминала) выводит на экран сообщение, если в буфере консоли накопилось 1024 байта. Или программа завершилась. Иначе нет. С помощью утилиты stdbuf устанавливаем буфер stdout (-o) в ноль (-o0), тем самым мы добиваемся, чтоб символы сразу поступали на вход awk. stdbuf в качестве второго параметра требует программу, которой она будет посылать измененный буфер. В данном случае awk.

buffering in standard streams
man stdbuf на русском

За разъяснение спасибо другу из Телеграм.

4. Далее вывод wget‘а передается awk, которая хитрой регуляркой и оператором substr вырезает из вывода все, кроме значений процентов.

5. dialog --gauge может принимать из stdout числа, и отображать их в виде progressbar’а

Пример на GitHub