42 заметки с тегом

bash

Позднее Ctrl + ↑

Русские имена на «Маке» после разархивации ZIP

Я вчера скачал несколько десятков зип-архивов, внутри которых тысячи файлов с русскими именами. Скачивал через браузер «Сафари» и этот поганец, как у него водится, услужливо мне их распаковал после скачивания. У меня время от времени вылетает из головы эта особенность, в общем, я получил кучу папок и файлов, которые выглядят вот так:

Кракозябры (17.71КиБ)

Эта давнишняя проблема «Мака», проистекающая из родовой травмы зип-архивов — этот архиватор родился ещё во времена ДОСа и для совместимости сохраняет русские имена ещё в той, ДОСовской кодировке. Правда, лет 15 назад я ковырял формат и увидел, что в каких-то метаданных лежит оригинальное имя, но должно быть «Мак» про это поле ничего не знает.

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

Способ употребления такой: складываете всё испорченное в какую-либо папку и запускаете внутри fixzip. В процессе «ремонта» он будет показывать на экране восстановленные имена. Два раза запускать нельзя — имена снова будут испорчены.

Случайные числа в Sed

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

В основном, задачи очень простые и даже рутинные, ничего интересного, но вот вчера прислали действительно интересный вопрос. Вопрос в том можно ли командами «Седа» получить случайное число?

Уж не знаю что именно такого спросивший собирается с ним потом делать, надеюсь, что-то очень интересное, но в общем случае его ждёт разочарование, задача не решается — Sed полностью детерменирован командами и входным потоком. Но в частных случаях кое-что придумать можно.

Итак, что же можно сделать? Команды жёстко заданы и менять их нельзя, возможно что-то можно сделать с входными данными?

Я как-то давно эксперементировал с гнушным «Седом» (gnu-sed, gsed) — он содержит в себе расширенный набор команд, в частности — команду «R», позволяющую читать первую строку файла. Тогда же я подумал — интересно, что будет, если прочитать файл /dev/urandom? Я о нём как-то писал — при чтении из него генерируется случайный поток байт.

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

Тогда я не обратил на это должного внимания, а когда получил упомянутое письмо — вспомнил. Общая идея оформилась быстро: нужно очистить экран, скрыть курсор, выставить цвет фона и шрифта одинаковыми (чтобы скрыть прочитанный из файла мусор — ведь 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 11211

DBF → text

Попросили сконвертировать кучу досовских файлов формата DBF (dBase) во что-нибудь путное. Вдруг кому-то ещё понадобится, публикую решение. Для того, чтобы достать информацию из файлов, воспользовался утилитой pgdbf, просто потому что она есть в «Брю» для «Мака» и небольшим скриптом, чтобы пакетно всё обработать, обрубая всё лишнее:

#!/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

Одна из задач, которую я хочу решить дома — слать СМС, если произошло что-то непредвиденное дома. Ближайшая задача — СМС, если уровень СО2 повысился выше нормы или произошло повышение температуры выше 40° (т. е. дома что-то горит).

С датчиком я определился, расскажу как-нибудь в другой раз, разбираюсь как можно отправить СМС по событию. У меня дома лежит не при деле смартбук Тошиба ЭйСи-100, на который я относительно недавно установил Линукс.

Что хорошо — у него есть 3Г-модем, а значит возможность слать СМС, осталось научиться это делать. Для начала надо найти как это устройство отображается в Линуксе. Нашёлся модем быстро, устройство F3307 — это модем фирмы Эрикссон (до названия идут какие-то странные спецсимволы):

Модем фирмы Эрикссон (43.48КиБ)

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

Логи (26.19КиБ)

С этими файлами работать просто — нужно отослать команду, потом передать возврат каретки, перевод строки и символ с кодом ноль. Потом прочитать что устройство ответило.

# Нужен баш версии 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

В выходном потоке будет ответ модема, а в коде возврата — есть ли ошибка. Гугление показало, что отослать СМС при помощи команд очень просто, достаточно ввести две команды.

Теперь надо купить симку и попробовать.

2013   bash   toshiba   гаджет

Под водой

Таймер (20.55КиБ)

Решил потренировать задержку дыхания под водой, когда-то мне удавалось продержаться почти три минуты, а сейчас получается чуть больше минуты. Посмотрел на Апп Сторе таймеры для «Мака», ничего толкового и бесплатного не нашёл. Мне всего-то надо было где-то удобно отмерять время.

Написал таймер сам, на баше. Запускается в консоли, прерывается по комбинации 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. Это загромождает код и неэффективно, ведь cat это не просто команда, это программа, которая располагается на диске в директории /bin, её шеллу ещё запустить надо.

# неправильно:
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

Подождите, пока сигнал выйдет за пределы вашей сети и наслаждайтесь! Для Виндоуз команда будет выглядеть так: «tracert -h 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==@
Ранее Ctrl + ↓