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
Комментировать
4 февраля 2014 20:36

Работа из командной строки с модемом на 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
В выходном потоке будет ответ модема, а в коде возврата — есть ли ошибка. Гугление показало, что отослать СМС при помощи команд очень просто, достаточно ввести две команды.

Теперь надо купить симку и попробовать.
20 комментариев
11 июля 2013 22:45

Под водой

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

Написал таймер сам, на баше. Запускается в консоли, прерывается по комбинации Ctrl+C, любая другая клавиша — сохранение на экране промежуточного результата.

Кроме того, мой таймер произносит сколько минут прошло — «one minute», «two minutes» и так далее, а каждую половину произносит слово «middle».

Громкости моего ноута за глаза хватает, чтобы услышать таймер в ванне, под водой. Без отсчёта держаться почему-то сложнее.
8 комментариев
7 июля 2013 18:06

Полезная «кошка»

Про антипаттерн «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
Ключи, конечно же, можно комбинировать. Кстати, первый ключ я использовал в последнем варианте «ругающегося однострочника», очень удобно там было пронумеровать им строки, только я там не учёл, что строки нумеруются с единицы, а не с нуля.
10 комментариев
12 февраля 2013 21:24

Антипаттерн: 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>&-
9 комментариев
12 февраля 2013 20:45

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 . ' '
Пам-пам-пам, пам-папам, пам-папам!
18 комментариев
10 февраля 2013 16:37

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==@
Комментировать
10 февраля 2013 12:52

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, мир!»
2 комментария
7 февраля 2013 21:19

Бу: ООП в 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»), если он есть, конечно.
Комментировать
31 января 2013 13:51

Бу: ООП в bash

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

Долго мучался с названием, решил, что пусть пока называется «Бу» (b-oo.sh), выложил туда же пример на котором вчера тестировал:
#!/bin/bash
# Evgeny.Stepanischev Jan 2013 http://bolknote.ru/

. 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 (чтобы вызывать родительские методы).

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

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

Во-вторых, в шеле всё уныло с передачей значения в переменную, обычные способы чаще всего создают так называемый «сабшел», поэтому передать значение в переменную следующим образом не получится:
# !неправильно
obj=$(Dog.New)
obj=`Dog.New`
Чтобы выполнить Dog.New интерпретатор создаёт отдельный процесс, где всё и выполняется, после выхода из этого процесса всё моё колдунство исчезает и от переданного значения толку нет (если у вас не «баш 4», тогда есть способы).

Поэтому конструктор у меня принимает первым параметром имя переменной, куда нужно положить экземпляр класса, а облегчения жизни программисту сделана специальная команда «@Ret», посмотрите как она используется в методе getInstance.

Естественно, в глобальном пространстве создаётся куча всякой магии, но у всего этого есть свои префиксы.
2 комментария
24 января 2013 10:51

Получение случайного числа 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.
}
4 комментария
24 января 2013 08:24

Сокращение ссылок через bit.ly из bash

На «Макрадаре» появилась статья про сокращение ссылок через bit.ly через «Автоматор», хорошая штука, иногда бывает нужно, когда кривой парсер какого-нибудь сайта не принимает какую-то ссылку, но сам скрипт (в примере «Автоматор» запускает его через «баш») мне не понравился.

Во-первых, в нём куча ошибок, во-вторых, мне не нравится идея хранить в скрипте пароль. На самом деле можно после регистрации на 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"
7 комментариев
16 января 2013 14:11

Список доменов, запрещённых к регистрации в зоне «.рф»

Оказывается существует список доменов, которые запрещено регистрировать в зоне «.рф». Полюбопытствуйте, расширьте свой словарный запас! Старательные люди собирали, там более четырёх тысяч доменов. Например, «взятьзамягкоеместо.рф» или «похабник.рф».

Вот вам ругающийся однострочник на «баше», чтобы учить каждый день новое слово:
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 ?!
Почему-то мне нравится программировать на «баше», остановиться не могу…
13 комментариев
10 января 2013 20:51

GEDCOM → MyHerigate

Для импорта и экспорта генеалогических деревьев используется известный формат «ГЕДКОМ», но сайт «MyHeritage» почему-то не понимает файлы в кодировках, отличных от UTF-8. Для конвертации я сделал небольшой скрипт на Баше, которым делюсь:
#!/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.ged
5 комментариев
15 октября 2012 11:12

Sleepsort

Из «Хабра» узнал про алгоритм 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
4 комментария
9 октября 2012 17:20