bash
Случайные числа в Sed
После шахмат на «Седе» я, похоже, в глазах общественности стал
В основном, задачи очень простые и даже рутинные, ничего интересного, но вот вчера прислали действительно интересный вопрос. Вопрос в том можно ли командами «Седа» получить случайное число?
Уж не знаю что именно такого спросивший собирается с ним потом делать, надеюсь,
Итак, что же можно сделать? Команды жёстко заданы и менять их нельзя, возможно
Я
Тогда я не обратил на это должного внимания, а когда получил упомянутое письмо — вспомнил. Общая идея оформилась быстро: нужно очистить экран, скрыть курсор, выставить цвет фона и шрифта одинаковыми (чтобы скрыть прочитанный из файла мусор — ведь sed выведет прочитанное на экран), прочитать файл случайных чисел, потом сосчитать позицию в которой оказался курсор, восстановить параметры экрана и вывести позицию курсора.
В манипуляциях с экраном ничего сложного нет — существуют специальные управляющие последовательности при помощи которых это делается, даже координаты курсора можно получить ими же.
Получившийся код вполне уверенно работает, разве что у чисел, которые выводятся, распределение далековато от равномерного, но алгоритмы, исправляющие этот недостаток, в изобилии есть в интернете. Да и сам алгоритм получения случайного числа можно изменить: например не считывать позицию курсора,
В общем, у меня вышел вот такой код:
#!/usr/local/bin/gsed -n -f
# Очищаем экран, выставляем цвет чёрное на чёрном, убираем курсор
1i\\x1B[2J\x1B[f\x1B[30;40m\x1B[?25l
# Читаем файл случайных чисел, пока не встретим перевод строки
1R /dev/urandom
# Запрашиваем текущие координаты курсора
1a\\x1B[6n
2{
# Смотрим чему равна первая (Y) координата, оставляем только её
s/.*\x1B\[\([0-9]*\);1R.*/Random number: \1/
# Убираем текущий текст в буфер
h
# Очищаем экран, восстанавливаем цвет и курсор
i\\x1B[2J\x1B[f\x1B[0m\x1B[?25h
# Вынимаем из буфера сохранённое
g
# Печатаем сохранённое на экран
p
# Выходим
q
}У него есть и недостаток — так как «Сед» всегда совершает действия только после того как получит данные снаружи, вам придётся дважды нажать «Энтер», чтобы получить ожидаемое. Но от этого уже никуда не денешься, разве что можно сократить количество нажатий до одного — если отказаться от считывания координат.
Как получить значения всех ключей в Memcached
В комментариях к одной из статей на «Хабре» привели однострочник для командной строки, чтобы вывести содержимое всех ключего локального memcached, я его прямо там причесал, но подумал, что он может мне и самому в будущем пригодиться. Переношу сюда:
nc localhost 11211 <<<"stats items" | awk -F: '/STAT items:[0-9]+/{if (!a[$2]++) print $2}' |
xargs -I{} echo 'stats cachedump {} 100' | nc localhost 11211 | awk '/ITEM/{print $2}' | xargs -n1 echo get |
nc localhost 11211Без использования awk:
nc localhost 11211 <<<"stats items" | sed -n '/STAT/{s/[^:]*:\([^:]*\).*/stats cachedump \1 100/;p}' | sort -u |
nc localhost 11211 | sed -n '/ITEM/{s/[^ ]* \([^ ]*\) .*/get \1/;p}' | nc localhost 11211Без использования awk и sed:
nc localhost 11211 <<<"stats items" | grep -Po '(?<=STAT items:)\d+' | sort -u |
xargs -I. echo stats cachedump . 100 | nc localhost 11211 | grep -oP '(?<=ITEM )\S*' |
xargs -n1 echo get | nc localhost 11211DBF → text
Попросили сконвертировать кучу досовских файлов формата DBF (dBase)
#!/bin/bash
[ -e converted ] || mkdir converted
# ищем все файлы dbf
find . -iname '*.dbf' | while read name; do
echo "$name"
# файл куда будет выгружен результат
outname=converted/$(tr / '_' <<< "${name/./}").txt
# есть ли файл memo?
memo=${name%.*}.DBT
if [ -e "$memo" ]; then
# не добавлять удаление таблицы, кодировка CP866
pgdbf -D -s cp866 -m "$memo" "$name"
else
pgdbf -D -s cp866 "$name"
fi |
grep -vE '^(BEGIN|\\COPY|\\\.|COMMIT)' |
(
# ↑↑↑ срезаем лишнее от утилиты pgsql
read header
# CREATE TABLE преобразуем в заголовок
if [[ "$header" == 'CREATE TABLE'* ]]; then
grep -o "(.*)" <<< "$header" |
sed -e 's/ [^ ,]*,//g;s/ [^ ]*$//;s/(//' | tr ' ' '\t'
else
echo "$header"
fi
# остальное — без измненений
cat
) > "$outname"
done
Работа из командной строки с модемом на Toshiba AC-100
Одна из задач, которую я хочу решить дома — слать СМС, если произошло
С датчиком я определился, расскажу
Что хорошо — у него есть

В логах видно, что это устройство «се́ло» на один из файлов /dev/ttyACM* (позднее испытания показали, что на первый):

С этими файлами работать просто — нужно отослать команду, потом передать возврат каретки, перевод строки и символ с кодом ноль. Потом прочитать что устройство ответило.
# Нужен баш версии 4 и выше
shopt -s lastpipe
function Say {
echo -en "$1\r\n\0" > /dev/ttyACM1
local line
cat -v < /dev/ttyACM1 |
while read -r line; do
[ "$line" = OK^M ] && return 0
[ "$line" = ERROR^M ] && return 1
[ "$line" = ^M ] || echo "${line/%^M/}"
done
}
# пример использования
Say AT+CGMIВ выходном потоке будет ответ модема, а в коде возврата — есть ли ошибка. Гугление показало, что отослать СМС при помощи команд очень просто, достаточно ввести две команды.
Теперь надо купить симку и попробовать.
Под водой

Решил потренировать задержку дыхания под водой,
Написал таймер сам, на баше. Запускается в консоли, прерывается по комбинации Ctrl+C, любая другая клавиша — сохранение на экране промежуточного результата.
Кроме того, мой таймер произносит сколько минут прошло — «one minute», «two minutes» и так далее, а каждую половину произносит слово «middle».
Громкости моего ноута за глаза хватает, чтобы услышать таймер в ванне, под водой. Без отсчёта держаться
Полезная «кошка»
Про антипаттерн «Useless cat» я рассказал, отдельной заметкой хочу описать чем может быть полезна команда cat, кроме вывода файла на экран. Немногие знают, что у этой команды есть ключи командной строки. В некоторых случаях очень полезно их знать, правда, в разных операционках они могут различаться.
Я приведу те, которые одинаковы у меня в «Маке» и на Линуксе, в моём смартбуке:
# вывести и пронумеровать все непустые строки
cat -b /etc/passwd
# вывести, включая непечатаемые символы (в нотации «^» и «М-»), в конце строки вывести «$»
# (зачем нужен в конце доллар я не знаю, видимо какой-то очень специальный случай)
cat -e /etc/passwd
# вывести, нумеруя все строки
cat -n /etc/passwd
# вывести, заменяя идущие подряд пустые строки на одну
cat -s /etc/passwd
# вывести с непечатаемыми символами, преобразуя табуляцию в «<i>^I</i>»
cat -t /etc/passwd
# вывести, включая непечатаемые символы (в нотации «^» и «М-»)
cat -v /etc/passwdКлючи, конечно же, можно комбинировать. Кстати, первый ключ я использовал в последнем варианте «ругающегося однострочника», очень удобно там было пронумеровать им строки, только я там не учёл, что строки нумеруются с единицы, а не с нуля.
Антипаттерн: useless cat
Есть такой антипаттерн программирования в командной строке как useless cat. Переводится не «бесполезная кошка» (хотя для смеха можно переводить и так), а как «бесполезная [команда] cat».
Несмотря на то, что многие команды — awk, sed, cut и прочие умеют самостоятельно читать из файла, программисты
# неправильно:
cat /etc/passwd | cut -d: -f1
# правильно:
cut -d: -f1 /etc/passwdЕсли команда не умеет принимать имя файла в качестве параметра, то и в этом случае cat не нужен, достаточно воспользоваться перенаправлением. Тут могут быть детали, так как синтаксис перенаправления гораздо богаче, чем используемое в 90% случаев простое перенаправление из файла и в файл и размер этого богатства разный в разных шеллах, я приведу пример для bash:
# плохо (пример для bash):
cat /etc/passwd | read firstline && echo $firstline
# можно так (такой синтаксис bash поддерживает не в полном объёме):
</etc/passwd read firstline && echo $firstline
# лучше так:
read firstline </etc/passwd && echo $firstlineВообще, стоит изучить внимательно синтаксис перенаправлений, потому что он позволяет делать такие вот вещи без лишних команд (опять bash):
# читаем файл /etc/passwd в массив, разделители элементов в файле — двоеточие
IFS=: passwd=($(</etc/passwd))
# читаем первые десять байт из последних десяти строк файла /etc/passwd
dd bs=1 count=10 if=<(tail -10 /etc/passwd) 2>&-A new hop
У Дмитрия Радищева (DiBR) в ЖЖ увидел. Попробуйте набрать в консоли
traceroute -m 100 216.81.59.173Подождите, пока сигнал выйдет за пределы вашей сети и наслаждайтесь! Для Виндоуз команда будет выглядеть так: «
Если хочется прочитать без лишнего мусора, то я вот такой вариант сделал (это под Виндоуз уже работать не будет):
traceroute -m100 -q1 216.81.59.173 2>&- | awk '/pisode/{_=1}{if(_)print$2}' | tr . ' 'ROT13 и ROT47 в Bash
Неожиданно сообразил, что функции ROT13 и ROT47 очень просто делаются в командной строке:
# Это ROT13, «крыша» превращается в «отливы»:
$ echo roof | tr n-za-mN-ZA-M a-zA-Z
ebbs
# А это ROT47, «привет» превращается в чёрт знает что:
$ echo Hello | tr '!-~' 'P-~!-O'
w6==@
JS-string decode by Bash
Столкнулся я тут с необходимостью раскодировать строку в том формате, который используется в JSON, т. е. когда символы, кроме латиницы и ещё некоторых знаков кодируются как \uXXXX.
Можно было, конечно, вызвать
Сделал на «Баше» и утилитах командной строки (кстати, легко переделать, при желании, и на «sh»):
:|sed -E '' 2>&- && SED='sed -E' || SED='sed -r'
uNNNNhelper() {
printf $(echo -n "$@") | iconv -f utf-16be
}
uNNNN() {
eval $(
echo -n "$@" |
$SED '
s/([^[:alnum:]])/\\\1/g
s/^/echo /
s/((\\\\[Uu]....)+)/$(uNNNNhelper \1)/g
s/\\\\[Uu](..)(..)/\\\\x\1\\\\x\2/g
'
)
}
uNNNN 'Hello, \U043C\U0438\U0440!' # выведет «Hello, мир!»Бу: ООП в bash, дополнение
Дописал немного «Бу», библиотеку ООП для «Баша», о которой я рассказывал недавно. Прошлая версия не умела работать напрямую (не из методов, снаружи) со свойствами. Новая умеет.
# … пример из предыдущего поста, дополненный
Dog.New dog # создаём экземпляр «собаки»
$dog.cnt =100 # записываем в свойство «cnt» значение «100», обратите внимание на пробел перед «равно»
cnt="$($dog.cnt)" # чтение значения из свойстваПомимо этого, появилась возможность уничтожить объект (все ссылки на него становятся недействительными, их надо очистить вручную, а так же автоматический сборщик мусора, который лучше не включать (довольно заметно тормозит), но зато он уничтожает объекты сам, когда все переменные и свойства других объектов, указывающие на него, исчезают:
@Destroy $dog # yничтожаем объект, в $dog теперь мусор
Dog.New dog # опять создаём «собаку»
unset dog # удаляем ссылку
@Class.gc # вызов сборщика вручную
@Class.gc.on # включаем тормоза и автоматический сборщик мусора
Dog.New dog # и снова создаём «собаку»
dog2="$dog" # ссылка на тот же экземпляр «собаки»
unset dog
echo Объект ещё существует, осталась одна ссылка
unset dog2
echo Объекта уже нетКстати, при уничтожении объекта любым из этих способов вызывается деструктор (метод «__destruct»), если он есть, конечно.
Бу: ООП в bash
ООП к «башу» приделать и до меня пытались, ничего в этом смысле нового, но мне ни одна реализация не нравится, из того, что я видел, всё примитивное и синтаксис кривой, всё время хотелось свою сделать. Вчера вечером заморочился, думал на выходных
Долго мучался с названием, решил, что пусть пока называется «Бу» (
#!/bin/bash
# Evgeny.Stepanischev Jan 2013 /
. b-oo.sh
@Class Base
@Dim cnt
@Dim
__construct() {
This[cnt]=0
}
@Dim
IncCnt() {
let 'This[cnt]++'
}
@Dim
GetCnt() {
[ "$This[cnt]" -gt 1 ] && s=s
echo $Self said $This[cnt] time$s
}
@End
@Class Dog Base
@Dim
say() {
$This.IncCnt
echo 'Bow-wow!'
}
@End
@Class Car Base
@Dim
say() {
$This.IncCnt
echo 'Beep'
}
@End
@Class Proxy
@Dim obj
@Dim Static
getInstance() {
$1.New Self[obj]
@Ret $2 $Self[obj]
}
@End
Proxy.getInstance Car car # создание через «фабрику», через статический метод
Proxy.getInstance Dog dog # создание через «фабрику», через статический метод
$car.say
$car.say
$car.GetCnt # 2, вызывается из родительского класса
$dog.say
$dog.GetCnt # 1, вызывается из родительского класса
Dog.New anothedog # создание через конструктор
$anothedog.say
$anothedog.say
$anothedog.GetCnt # 2, вызывается из родительского классаЯ не стал заморачиваться с модификаторами доступа (хотя может ещё и сделаю), из реализованного: создание объектов, наследование (есть множественное), статические и динамические методы и свойства, конструктор, есть переменная This (текущий объект), Self (текущий класс) и Parent (чтобы вызывать родительские методы).
Всё делается при помощи вызовов, начинающихся с собачки. Вряд ли синтаксис
# !неправильно
obj=$(Dog.New)
obj=`Dog.New`Чтобы выполнить Dog.New интерпретатор создаёт отдельный процесс, где всё и выполняется, после выхода из этого процесса всё моё колдунство исчезает и от переданного значения толку нет (если у вас не «баш 4», тогда есть способы).
Поэтому конструктор у меня принимает первым параметром имя переменной, куда нужно положить экземпляр класса, а облегчения жизни программисту сделана специальная команда «@Ret», посмотрите как она используется в методе getInstance.
Естественно, в глобальном пространстве создаётся куча всякой магии, но у всего этого есть свои префиксы.
Получение случайного числа 0…255 в bash
Понадобилось получить случайное число в шеле из устройства /dev/urandom, написал небольшую функцию, сохраню у себя, чтобы не потерялась.
getrand() {
head -c1 /dev/urandom | hexdump -d | sed -E '1!d;s/.+ +0+//'
}Ещё вариант придумался (но необходим openssl):
getrand() {
openssl rand 1 -hex | xargs -I. printf "%d" 0x.
}Сокращение ссылок через bit.ly из bash
На «Макрадаре» появилась статья про сокращение ссылок через bit.ly через «Автоматор», хорошая штука, иногда бывает нужно, когда кривой парсер
Вот мой вариант:
TOKEN=<ваш токен, полученный по инструкции выше>
URL="$1"
[[ "$URL" =~ ^https?:// ]] || URL="http://$URL"
path="${URL#*//*/}"
host="${URL:0:${#URL}-${#path}}"
path=$(echo -n "$path" | hexdump -ve '1/1 "x%02X"' | tr x %)
curl 'https://api-ssl.bitly.com/v3/shorten' \
--data "access_token=$TOKEN" \
--data 'format=txt' \
--data "longUrl=$host$path"Список доменов, запрещённых к регистрации в зоне «.рф»
Оказывается существует список доменов, которые запрещено регистрировать в зоне «.рф». Полюбопытствуйте, расширьте свой словарный запас! Старательные люди собирали, там более четырёх тысяч доменов. Например, «взятьзамягкоеместо.рф» или «похабник.рф».
Вот вам ругающийся однострочник на «баше», чтобы учить каждый день новое слово:
curl -so- http://flisti.ru/stop-list.txt |
iconv -f cp1251 | awk -F'[\t\.]' 'BEGIN {srand()} $3 {print rand(), $3}' |
sort -n | awk '{print $2; exit}' | tee >(cut -c1 | tr '[:lower:]' '[:upper:]') |
sed -n '1s/^.//;1h;2p;2g;2p' | tr -d '\n' ; echo '!'Или можно так:
curl -so- http://flisti.ru/stop-list.txt | iconv -f cp1251 |
awk 'BEGIN {srand()} {print rand()"."$2}' |
sort -nt. | head -1 | cut -f2 -d. |
tee >(cut -c1 | tr '[:lower:]' '[:upper:]') | tail -r | tr -d '\n' | xargs -I? echo ?! | cut -c1,3-Его можно сильно упростить, если выкинуть часть, которая отвечает за изменение регистра первой буквы (заодно будет работать не только в «баше»):
curl -so- http://flisti.ru/stop-list.txt |
iconv -f cp1251 | awk -F'[\t\.]' 'BEGIN {srand()} $3 {print rand(), $3}' |
sort -n | awk '{print $2"!"; exit}'Вариант для «баша», но без awk:
curl -so- http://flisti.ru/stop-list.txt |
iconv -f cp1251 | grep -Eo '\s.+рф' | cat -b |
tee >(echo $((`wc -l`*$RANDOM/32768))) | tail -r |
grep -w `head -1` | cut -f3 | cut -d. -f1 | xargs -I? echo ?!GEDCOM → MyHerigate
Для импорта и экспорта генеалогических деревьев используется известный формат «ГЕДКОМ», но сайт «MyHeritage»
#!/bin/bash
# проверим в какой кодировке приходит файл
function DetectEncoding {
awk '/^1 CHAR/ {print $3}' "$1" | tr -d '\r'
}
# конвертируем входной файл в UTF-8, добавляем BOM
function ConvertEncoding {
printf "\xef\xbb\xbf"
iconv -f `DetectEncoding "$1"` -t utf-8 "$1" |
sed 's/^1 CHAR.*$/1 CHAR UTF-8/'
}
ConvertEncoding "$1"Запускать следует таким образом:
./convert2myheritage somefile.ged > somefile-utf8.gedSleepsort
Из «Хабра» узнал про алгоритм sleepsort. Он больше забавный, чем практичный, я переписал его на баш:
while [ -n "$1" ]; do
( sleep "${1/%[0-9]/}.${1:(-1)}" ; echo "$1" ) &
shift
done
wait
echoСмысл в том, что запускается несколько параллельных процессов, по числу сортируемых значений, с паузой кратного значению размера. Вывод каждого из процессов происходит после истечения паузы, поэтому значения появляются в уже отсортированном виде:
$ bash sleepsort 1 2 7 10 3
1
2
3
7
10Удаляем расладку «U.S. English» из «Мака»
Артём Поликарпов написал рецепт как можно удалить из «Мака» раскладку «U.S. English». Она очень мешается и совершенно не нужна, если поставить великолепную раскладку Ильи Бирмана (кстати, если вы
Но рецепт Артёма мне не нравится тем, что для его выполнения приходится иметь у себя редактор Xcode, который ещё и несколько гигабайт весит. Поэтому я, как водится, написал скрипт, который нужно выполнить из «Терминала» (после его выполнения придётся опять залогиниться в систему) и ненужной раскладки уже не будет.
Какое счастье!
Проще всего скрипт запускать из «Терминала» вот так:
bash <(curl -fsSkL raw.github.com/bolknote/shellgames/master/us_layout_remover.sh)Чтобы вернуть всё обратно достаточно запустить программу ещё раз.
Добавлено позднее: удалению раскладки может мешать Punto Switcher.
Бонжур, примите файл!
На «Маке» есть «Бонжур» — протокол, позволяющий регистрировать сервисы и смотреть какие сервисы зарегистрированы в локальной сети. Удобная штука, через неё «Айтюнс» делится музыкой с соседями по локалке, через неё работает «Эйрдроп» — служба для быстрого обмена файлами на «Маке».
В общем, пришла мне в голову идея — нельзя ли
Если её запустить без параметров, то она ждёт анонса специальной службы и, если находит, коннектится на указанный IP по порту 1111 и выдаёт переданный файл на экран. Если запустить с параметром, ожидается, что это имя файла для передачи, далее анонсируется служба, ожидается соединение на порт 1111 и туда выдаётся файл.
При передаче контролируется контрольная сумма (md5), а файл сжимается утилитой gzip. Для анонса и получения анонсированных служб используется утилита
bash bonfile.sh > somefile # запуск клиента (ожидаем приём файла)
# или
bash bonfile.sh somefile # запуск сервера (ожидаем подключения клиента)«Граул» и протокол ГНТП
У меня тут «Граул» обновился на «Маке» и я краем глаза увидел, что появилась поддержка
«Граул», для тех кто не в курсе, программулина, которая позволяет различным сторонним программам формировать всплывающие уведомления в едином унифицированном виде. Кстати, существует и версия для «Виндоуз».
Включается поддержка ГНТП специальной галочкой в настройках, после чего можно отсылать свои уведомления. Там же можно задать и пароль, чтобы ни одна сволочь снаружи не смогла

Протокол несложный и поддерживает три команды: можно зарегистрировать своё приложения и его типы сообщений, сформировать сообщение и подписаться на события. Для параноиков поддерживается ещё и шифрование (AES, DES или 3DES на выбор).
Прежде чем ваше приложение отошлёт сообщение, оно должно «зарегистрироваться» в «Грауле» — представиться и рассказать что за сообщения оно будет слать и как их оформлять. Ниже на скриншоте показан список зарегистрированных приложений, включая мой скрипт.

Я тут попробовал поработать с ГНТП из bash-скрипта, получилось удобоваримо, заодно вспомнил как работать с утилитой expect (можно было бы использовать netcat, но telnet плюс expect — интереснее и более гибко).
Увы, у expect под «Маком»
Внутри файла, в конце есть пример, который показывает как отослать сообщение и, в случае неудачи, попробовать сначала зарегистрировать своё приложение и потом отправить своё сообщение ещё раз:
# Пробуем отослать сообщение
cmd='GrowlSendNotify "" "" "Hello all!"'
eval $cmd || (
GrowlSendRegister && eval $cmd
)Внутри моего скрипта есть ещё функция получения IP локальной машины, на самом деле она там лишняя — «Граул» слушает на всех адресах, но мне было интересно написать такую функцию.