Мини заметки — выпуск 6

20 апреля 2012

Главные темы шестого выпуска: комбинаторика на Haskell, исправление кодировки без использования iconv, полуавтоматическая поисковая оптимизация заголовков, настройка unbound и pptp, а также создание мгновенного снимка файловой системы под FreeBSD. Предыдущие выпуски: первый, второй, третий, четвертый и пятый.

1. Размещение «шариков» по «коробкам» на Haskell

Дано N разноцветных шариков, M коробок различной вместимости, а также стол бесконечной вместимости. Нужно получить список всех возможных размещений (не путать с размещением в комбинаторике) шариков по коробкам и столу. Вместо шариков могут быть яблоки или посылки, а вместо коробок — корзины или грузовики, суть от этого не меняется. Решение на Haskell:

module Placements where

import Data.List

-- первый аргумент - список "шариков"
-- второй аргумент - вместимость "коробок"
placements :: Eq a => [a] -> [Int] -> [[[a]]]
placements itemsList [] = [[ itemsList ]]
placements itemsList (maxItems:maxItemsTail) =
  [ (s:t) | s <- seqList, t <- allTails s]
  where
    -- все варианты размещения "шариков" в текущей "коробке"
    seqList = filter (\s -> length s <= maxItems)
      $ subsequences itemsList
    -- все варианты размещения "шариков"
    -- в остальных "коробках" и на "столе"
    allTails s = placements (itemsList \\ s) maxItemsTail

Если вам попадалось готовое решение, поделитесь, пожалуйста, ссылкой.

2. Настройка unbound

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

Для этих целей идеально подходит DNS сервер unbound:

pkg_add -r unbound

После установки пишем в /usr/local/etc/unbound/unbound.conf что-то вроде:

server:
    # принимаем пакеты только с локалхоста
    interface: 127.0.0.1
    access-control: 127.0.0.0/8 allow
    # минимальный и максимальный TTL
    cache-min-ttl: 21600
    cache-max-ttl: 86400

forward-zone:
    # за адресами доменов компании ходим по VPN
    name: "company.example.ru."
    forward-addr: 33.33.33.1
    forward-addr: 33.33.33.2

forward-zone:
    # остальные домены резолвим у провайдера
    name: "."
    forward-addr: 192.168.0.1

В /etc/rc.conf прописываем:

unbound_enalbe="YES"

Запускаем:

/usr/local/etc/rc.d/unbound start

Проверяем:

tcpdump -i tun0 udp port 53
dig @127.0.0.1 intra.company.example.ru

В /etc/resolv.conf прописываем:

nameserver 127.0.0.1

Чтобы resolv.conf не перезаписывался благодаря DHCP провайдера, в /etc/dhclient.conf пишем:

interface "rl0" {
  supersede domain-name-servers 127.0.0.1;
}

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

3. Использование разных ssh-ключей для разных серверов

Что делать, если требуется получить доступ к N серверам с одной машины, используя различные identity file для каждого сервера?

Копируем закрытый ключ для i-го сервера, например, в ~/.ssh/id_rsa.i-server (также копируем открытый ключ в id_rsa.pub.i-server — чтобы не потерять), меняем права доступа на 600, а затем пишем в ~/.ssh/config:

Host *.i-server.example.ru
  IdentityFile ~/.ssh/id_rsa.i-server
  User myusername

Повторяем описанные действия для i от 1 до N.

4. Настройка pptp под FreeBSD

Устанавливаем pptpclient:

pkg_add -r pptpclient

Правим /etc/ppp/ppp.conf:

company:
 set authname mylogin
 set authkey mypassword
 set timeout 0
 set ifaddr 0 0
 # весь трафик - через VPN!
 # add default HISADDR
 # через VPN ходим только в заданную подсеть
 set 123.45.67.0/24 HISADDR
 # ... и к заданному хосту
 set 45.67.89.10 HISADDR

Указываем, через какой шлюз гнать трафик к VPN серверу:

route add -host 33.33.33.1 192.168.0.1

Проверяем:

pptp 33.33.33.1 company

Варнинги типа «/bin/ip: not found» игнорируем, логи смотрим в /var/log/ppp.log. По умолчанию в ppp.conf прописано «enable dns», что приводит к перезаписи resolv.conf. Если вы оставили эту опцию, то после разрыва соединения с VPN сервером должны вручную восстановить resolv.conf:

nameserver 192.168.0.1

Также после разрыва соединения понадобится восстановить шлюз по умолчанию:

route add default 192.168.0.1

Напоминаю, что посмотреть все роуты можно командой «netstat -nr».

5. Создание мгновенного снимка ФС во FreeBSD

Снапшоты файловой системы во FreeBSD можно создать несколькими способами. Наиболее простым, по моим представлениям, является следующий:

# pkg_add -r freebsd-snapshot
$ echo "1111" > test.txt
# snapshot make -g4 /usr:backup

Здесь флаг -g задает максимальное количество снапшотов. Если превысить это число, происходит автоматическая ротация.

# snapshot list /usr
Filesystem          User   User%     Snap   Snap%  Snapshot      
/usr               146GB   25.8%    346MB    0.1%  backup.0

# mkdir /mnt/backup
# snapshot mount /usr:backup.0 /mnt/backup
# cat /mnt/backup/home/eax/test.txt
1111
$ echo "2222" > test.txt
# cat /mnt/backup/home/eax/test.txt
1111
# snapshot umount /mnt/backup
# ls -la /usr/.snap/
total 357652
drwxrwxr-x   2 root  operator           512  4 май 11:40 .
drwxr-xr-x  17 root  wheel              512 14 окт 21:59 ..
-r--------   1 root  operator  627341157960  4 май 12:12 backup.0
# snapshot make -g0 /usr:backup

Мгновенные снимки часто используются при резервном копировании. Например, если на сервере крутится некая СУБД, мы можем останавливаем ее (или временно запретить запись и скинуть все данные на диск), создать снапшот файловой системы, после чего снова запускаем СУБД (разрешить запись) и сделать резервную копию из снапшота.

В результате сервер не простаивает, а мы получаем резервную копию данных в непротиворечивом состоянии. За счет использования механизма copy-on-write создание мгновенного снимка происходит очень быстро (на то он и мгновенный), а место на диске расходуется с умом.

6. Полуавтоматическая поисковая оптимизации заголовков

Дан список URL и поисковых запросов, по которым продвигаются соответствующие страницы. Следующий скрипт проверяет, заголовки каких страниц следует оптимизировать:

#!/usr/bin/perl

# check-header.pl v 0.1
# (c) Alexandr A Alexeev 2012 | http://eax.me/

use strict;
use warnings;
use utf8;

use Mojo::UserAgent;

my $ua = Mojo::UserAgent->new();

while(my $line = <>) {
  chomp($line);
  my ($url, $query) = split /\t/, $line;
  utf8::decode($query);
  my $title;
  eval {
    $title = $ua->get($url)
      ->res->dom->html->head->title->text;
  };
  if($@) {
    print "ERROR:\t$url\n";
    next;
  }

  if(titleMatch($title, $query)) {
    print "OK:\t$url\n";
  } else {
    utf8::encode($query);
    print "FIXME:\t$url\t$query\n";
  }
}  

sub titleMatch {
  my($title, $query) = @_;
  $title = lc $title;
  $query = lc $query;
  my @tmp = split /\s+/, $query;
  for my $q(@tmp) {
    return 0 if(index($title, $q) < 0);
  }
  return 1;
}

Проверка довольно топорная — если страница продвигается по запросу «купить яблоки», то в title должны содержаться слова «купить» и «яблоки». Этот скрипт удобно использовать со скриптом из десятого пункта предыдущего выпуска мини-заметок.

7. Как скопировать музыку с CD под UNIX

В портах FreeBSD есть такая замечательная утилита audio/ripit, предназначенная для конвертирования музыки с CD дисков в MP3. Устанавливаем, немного правим /usr/local/bin/ripit.pl:

my $cddev = "/dev/cd0";
my $outputdir = "/home/user/cdrip";

Затем просто вставляем диск в CD привод и говорим из под рута «ripit». Музыка в формате MP3 магическим образом окажется в директории $outputdir.

8. Удобные сочетания клавиш в bash

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

Сочетание Ctr+R позволяет быстро выполнять команды, набранные ранее. Жмем Ctr+R и вводим часть команды. Если bash предложил то, что мы хотели, жмем Enter. Если снова нажать Ctr+R, будет предложена другая команда. Нажатие Escape возвращает нас в нормальный режим.

Сочетание Alt+точка подставляет в вводимую команду последний использованный аргумент. Например, если выполнить команду «ls -la», затем набрать «cat » и нажать Alt+точка, команда превратиться в «cat -la». Повторное нажатие Alt+точка подставляет другие аргументы, используемые ранее. Это сочетание особенно удобно при выполнении нескольких команд над файлами, к которым мы обращаемся по абсолютным или длинным относительным путям.

Может, есть еще какие-то сочетания клавиш, которые мне давно следовало бы держать на вооружении?

9. Утилита sloth

Если какой-то фоновый процесс съедает много ресурсов, а (re)nice не помогает, попробуйте утилиту sloth:

pkg_add -r sloth

Эта программа бомбит процесс сигналами SIGSTOP и SIGCONT, что существенно замедляет его выполнение:

$ time md5 PCBSD9.0-x86-DVD.iso
MD5 (PCBSD9.0-x86-DVD.iso) = 805eebb4d24e2ada0466d5baa1997b45

real  1m46.598s
user  0m17.914s
sys 0m7.744s

$ time sloth 5000 md5 PCBSD9.0-x86-DVD.iso
MD5 (PCBSD9.0-x86-DVD.iso) = 805eebb4d24e2ada0466d5baa1997b45

real  7m16.996s
user  0m18.523s
sys 0m7.713s

Если для вашей системы не оказалось готового пакета, вы можете собрать sloth из исходников. Последние представляют собой сотню строк на языке Си.

10. Исправление кодировки без использования iconv

Недавно мне попался дамп базы данных с испорченной кодировкой. Декодер студии Артемия Лебедева определил, что исправить кодировку можно путем перекодирования текста из cp1252 в cp1251. Но ни enconv, ни iconv никак не могли справиться этой, казалось бы, элементарнейшей задачей. В итоге пришлось написать такой скрипт:

#!/usr/bin/perl

# decoder.pl v 0.2
# (c) Alexandr A Alexeev 2012 | http://eax.me/

use strict;
use warnings;
use utf8;
use List::MoreUtils qw/uniq/;

# http://www.artlebedev.ru/tools/decoder/advanced/
my @bad = split //,
  '??????????????????????????????????????????????????????????????????';
my @good = split //,
  'абвгдеёжзийклмнопрстуфхцчшщъыьэюяАБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ';

my %map;
$map{$_} = shift @good for (@bad);

while(my $line = <>) {
  utf8::decode($line);
  $line = join '', (
      map { defined $map{$_} ? $map {$_} : $_ } (split //, $line)
    );
  utf8::encode($line);
  print $line;
}

Как обычно, я буду несказанно рад вашим вопросам и дополнениям. Также мне было бы интересно узнать, какие пункты этого выпуска мини-заметок больше всего вам понравились и почему.

Дополнение: Мини заметки — выпуск 7

Метки: .


Вы можете прислать свой комментарий мне на почту, или воспользоваться комментариями в Telegram-группе.