Archive

Archive for the ‘Программирование’ Category

Магия в zsh

November 4th, 2008

Захотелось мне в этот чудесный выходной понедельник поагитировать за свой любимый шелл. Чем, кстати, вообще может похвастаться какой-либо шелл, имея в конкурентах такого монстра, как bash? Ведь в bash есть все, что может только пожелать обычный пользователь. А, собственно, и нечем. Поэтому разработчикам zsh пришлось пойти на хитрость и добавить в свое детище некоторое количество магии :)

Большая ее порция досталась подсистеме автодополнения. Вот таким можно сделать дополнение имен файлов:
zsh menu completion
(выбрать нужный вариант можно табом по старинке, курсорными клавишами или стандартными emacs-хоткеями)

А вот так – дополнение опций:

Еще одна моя любимая магическая штуковина в автодополнении выглядит так: допустим, нужно подправить файл /usr/local/etc/rc.d/mysql. Пользователь bash бы ввел такую команду:
vim /usr/l[TAB]/e[TAB]/r[TAB]/m[TAB]
В zsh все несколько проще, а именно:
vim /u/l/e/r/m[TAB]
После нажатия [TAB] все элементы пути будут дополнены. Если вариантов несколько, можно будет вручную выбрать нужный.

Ну вот, я вам передал примерно 0.65 процента магии zsh. Помимо автодополнения, ее залежи находятся в подсистеме подстановок (man zshexpn), но там я сам не очень уверенно ориентируюсь. Все-таки одна из прелестей zsh заключается в том, что мне необязательно учиться им пользоваться – для начала можно ограничиться только bash-совместимым подмножеством функций, регулярно натыкаясь на приятные мелочи в реализации привычных вещей.

Дальнейшее чтение:
обзор zsh от Алексея Федорчука,
Собрание хаков и идиом zsh: zsh lovers.

SHELL, UNIX, tips , , ,

Чудеса!!

June 6th, 2008

Я страсть как люблю хакерские программки, которые удивляют самим фактом своего существования.

Одна из таких: тетрис на sed’е. Это что-то невероятное!

p.s. да, и я тоже, оказывается, шаблонно мыслю :)

SHELL, Программирование , , ,

Пятничная загогулина (bash)

April 11th, 2008

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

#!/usr/bin/bash
echo "one two three" | read a b c
echo "<$a> <$b> <$c>"

Что в нем не так?

Я ожидаю увидеть два ответа. Первый — это просто переписать этот код так, чтобы он заработал. И второй ответ — опционально — объяснение, почему не работает в таком виде.

p.s. второго ответа я пока сам не знаю, поэтому буду с интересом ждать его от вас :)
p.p.s. ответивший верно, как всегда – умничка (-ца (маловероятно)).

UPD: На второй вопрос ответ уже есть:
gds
есть мнение, что переменные, прочитанные read, остаются в переменных “экземпляра” баша, запущенного для выполнения “read a b c”, и не попадают в родительский баш, запустивший “echo … | read a b c”.

gds’у – респект!
А я понял, что в следующий раз надо постить задачки посложнее..

UPD2 свое решение выложу сегодня в конце рабочего дня
UPD3

Кажется, я нечаянно сорвал рабочий день моего начальника, но зато он мне прислал вот такое остроумное решение:
str=`echo "one two three" | (read a b c; echo "a=$a; b=$b; c=$c;") `
eval "$str";
echo "<$a> <$b> <$c>"

Петя, ты отжог :)

Но на самом деле все это можно сделать несколько проще – через “here doc”:
read a b c <<<`echo "one two three"`
echo "<$a> <$b> <$c>"

SHELL, UNIX, Программирование, загогулины ,

SSH для бэкапов и мониторинга: ограничиваем доступ.

March 4th, 2008
“Бэкап – акт проявления трусости” (c) народная мудрость

Я труслив :) Во-первых, я делаю бэкапы. Во-вторых, я их боюсь передать без шифрования и, в третьих, иногда и храню их зашифрованными.

Как правильно организовать передачу бэкапов с одного сервера на другой? Дампы этого блога у меня передаются по http. Они зашифрованны, да и особо ценных данных здесь нет, поэтому http меня не смущает. А как поступить с данными по-важнее? Конечно, ssh. А как при этом запретить пользователю выполнять какие-либо действия, кроме как копирования к себе бэкапа? Представим себе, что бэкап-сервер находится во вражеском дата-центре или просто вы не можете проконтролировать к нему доступ (в т.ч. физический). Нас выручит опция command файла authorized_keys.

Например, если необходимо предоставить доступ только к файлу backup.tgz, то в authorized_keys в самом начале соответствующей строки можно дописать следующее “command=’cat backup.tgz’”. Теперь при каждом коннекте будет автоматически выполняться команда cat backup.tgz, вам остается только перенаправить вывод в файл. Если дампов несколько, то можно написать небольшой скриптик вида:#!/bin/sh
read file
case "$file" in
"foo") cat foo.tgz ;;
"bar") cat bar.tgz ;;
esac
И прописать его в качестве команды по умолчанию. Да, не лишним было бы добавить опции no-port-forwarding, no-pty и все прочие no-*

Теперь копировать файл с удаленной машины можно вот такой командой:echo "foo" | ssh backup@server > foo-`date +%Y-%m-%d`.tgz

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

По-моему, все, что я описал примитивно по сложности и вместе с тем весьма надежно. Так может быть прекратим без надобности дописывать authorized_keys на серверах? Одна запись для себя, любимого, одна для бэкапов, одна для мониторинга.. ой, еще каких-то две.. дальше продолжать? :)

Кстати, а есть ли способы более тонко настраивать уровень доступа к ssh?

SHELL, UNIX, tips , , ,

О прозрачности в командной строке [pv]

December 4th, 2007

Эта запись будет посвящена программе с лаконичным названием pv. А слово “прозрачность” пусть не ассоциируется у вас с пресловутыми compiz-fusion, тут все гораздо интереснее :)

Итак, чем плох избитый пример c передачей файлов через netcat? Мне, например, не удобно то, что нет возможности наблюдать за процессом. В самом деле, возможности юниксовых шеллов в этом плане довольно скромные: с помощью tee можно сохранить поток в файл, который потом наблюдать через tail или следить за его размером командой ‘watch ls’. Этого достаточно для отладки, но мало для комфортной работы.

Поэтому программа pv просто не могла не появиться. Все очень в духе UNIX – маленькая утилитка, которая пропускает через себя поток данных и в STDERR отправляет некоторую статистику по ним. Но _как_ она это делает! Покажу на примерах:

Всегда хотел узнать:
dainichi@fujitsu:~$ cat /dev/toilet_paper | pv >/dev/shreder
1.01GB 0:00:02 [ 569MB/s] [ <=> ]

Ага, больше десяти рулонов в секунду..

Прикинемся взрослой программой с выводом а-ля wget:
dainichi@fujitsu:~$ cat file | pv -s `stat -c %s file` | nc so.far.away 1234
236kB 0:00:05 [ 346КB/s] [==================> ] 72%

Ограничим скорость обработки файла, чтобы mplayer не тормозил:
dainichi@fujitsu:~$ cat big_log | pv -L 100к | log_processor > report

Пример из мана – архивирование каталога и одновременное отображение прогрессбара
(tar cf - . | pv -n -s `du -sb . | awk '{print $1}'` \
| gzip -9 > out.tgz) 2>&1 | dialog --gauge 'Progress' 7 70

И, напоследок, скриншот с домашней страницы проекта:
скриншот с домашней странички проекта pv

Здорово? :) По-моему, да :)

Кстати. а какие еще способы “очеловечить” интерфейс командной строки вы знаете?

UPD Андрей Афанасенко поделился ссылкой вот на такое чудо:

Написана эта программа на bash и во многих случаях ее использовать гораздо удобнее, чем pv.

SHELL, UNIX, tips , ,

Загадка!

October 19th, 2007

“Если достаточно долго лазить по карманам своих курток, можно найти практически любую сумму денег” (народная мудрость). Вот и с задачками примерно такая же ситуация – вспомнил весьма любопытную.
Приятно, знаете ли, иногда посмотреть как люди мучаются над задачками, которые когда-то серьезно озаботили тебя самого :)

Итак, есть следующий код:

#!/bin/sh

get_size() {
if [ "`uname`" = "FreeBSD" ]; then
r=`stat -f %z $1`
else
r=`stat -c %s $1`
fi
return $r
}

if [ -z $1 ]; then
echo "usage: print_size ";
else
get_size $1;
echo $?
fi

Вот результат работы скрипта:
asveshnikov@fe08a023fb1cc27a:~/tmp$ ls -la
total 112
drwxr-xr-x 2 asveshnikov asveshnikov 4096 2007-10-19 15:38 .
drwxr-xr-x 85 asveshnikov asveshnikov 8192 2007-10-19 13:15 ..
-rw-r--r-- 1 asveshnikov asveshnikov 39781 2007-08-29 15:22 DSC02473.jpg
-rw-r--r-- 1 asveshnikov asveshnikov 49553 2007-08-31 17:41 DSC02481.jpg
-rwxr-xr-x 1 asveshnikov asveshnikov 231 2007-10-19 15:27 print_size.sh
asveshnikov@fe08a023fb1cc27a:~/tmp$ ./print_size.sh print_size.sh
231
asveshnikov@fe08a023fb1cc27a:~/tmp$ ./print_size.sh DSC02473.jpg
39781

Как видите, абсолютно рабочий код, проверялся в Linux и FreeBSD.
При этом он имеет просто ужасную.. нет, чудовищную проблему переносимости, которую при некоторых обстоятельствах крайне сложно обнаружить в скриптах несколько большего размера – какую?

p.s. угадавший первым будет умничкой :)

UPD:

Уже двое человек указали на то, что `uname` желательно взять еще и в кавычки. Вобщем, предлагаю не мелочиться, здесь проблема если и есть, то явно не “чудовищная”. Чтобы больше никого не смущать этим, подправил код.

Решено!

Михаил достаточно подробно описал проблему:

Если не брать в голову отсутствие кавычек вокруг $1, а также неполный способ определения размера файла (под солярой нет stat, например), то мне кажется основная проблема в возврате значения из функции. На самом деле таким образом можно вернуть только целое, причем очень ограниченное, так как по сути возвращается код завершения функции (если не изменяет память – максимум – 256). таким образом на больших файлах получим неверное значение.

Решением может служить использование для возврата глобальное переменной, либо через echo.

С моей стороны осталось лишь пояснить, почему проблема является “чудовищной” – она запросто может привести к порче данных, так как никакие сообщения об ошибках выводиться не будут. Ну и выглядит внешне этот код вполне надежным, так что отлавливать такой баг может быть задачей не из легких.

Собственно, основные проблемы возникают при портировании скрипта с sh на bash:

asveshnikov@fe08a023fb1cc27a:~/tmp$ ls -al DSC02473.jpg
-rw-r--r-- 1 asveshnikov asveshnikov 39781 2007-08-29 15:22 DSC02473.jpg
asveshnikov@fe08a023fb1cc27a:~/tmp$ bash print_size.sh DSC02473.jpg
101

SHELL, UNIX, Программирование, загогулины , ,

HTTP сервер в одну строку: версия 2.0

August 30th, 2007

Идея с HTTP сервером на bash мне не дает покоя.

Вернуться к ней меня побудила.. попытка воспользоваться старой версией: получалсь не очень :)

Поэтому решил довести его до ума. Что-то дописал, что-то выкинул, полчилось следующее:
:;while [ $? -eq 0 ];do nc -vlp 8080 -c'(r=read;e=echo;$r a b c;z=$r;while [ ${#z} -gt 2 ];do $r z;done;f=`$e $b|sed 's/[^a-z0-9_.-]//gi'`;h="HTTP/1.0";o="$h 200 OK\r\n";c="Content";if [ -z $f ];then($e $o;ls|(while $r n;do if [ -f "$n" ]; then $e "<a href=\"/$n\">`ls -gh $n`</a><br>";fi;done););elif [ -f $f ];then $e "$o$c-Type: `file -ib $f`\n$c-Length: `stat -c%s $f`";$e;cat $f;else $e -e "$h 404 Not Found\n\n404\n";fi)';done

Теперь по URL http://ваш_ip:8080/ можно получить доступ ко всем файлам, находящимся в текущим каталоге. Очевидных и прямолинейных способов сменить каталог нет. Как и раньше, протестировано и работает под Linux, bash 3.2.13, и с хоббитовским netcat v1.10 с поддержкой опции -с (запустите netcat -h и посмотрите. Как минимум в Ubuntu, Debian и Fedora Core такая опция есть).

Теперь строка занимает 434 байт, что на 212 больше, чем прошлая версия. И мне кажется, что я с толком потратил эти байты: новый сервер обрабатывает ошибку 404, кроме того, теперь он работает без задержки, которая раньше требовалась для того, чтобы была возможность его остановить с помощью Ctrl-C. Для успешных запросов сервер отдает нормальные заголовки, с размером файла и даже с его MIME-типом. Например:

dainichi@fujitsu:~/backup$ echo "GET /6.1-RELEASE-i386-disc1.iso HTTP/1.1" | nc localhost 8080 | head -n3
HTTP/1.x 200 OK
Content-Type: application/x-iso9660
Content-Length: 529784832

Для удобства можно сохранить его в файл и написать в .bash_profile примерно следующее:alias share='sh ~/bash_httpd.sh'

Да, были попытки его еще уменьшить, но они не увенчались успехом. Есть такой вариант, но он даже больше:
w=while;d=done;e=echo;r=read;echo "true; $w [ @? -eq 0 ];do nc -vlp 8080 -c'($r a b c;f={$e @b|sed 's/[^a-z0-9_.-]//gi'{;h=}HTTP/1.x};o=}@h 200 OK#};c=}Content};if [ -z @f ];then($e @o;ls|(while $r n;do $e }@n
};done));elif [ -f @f ];then $e }@o@c-Type: {file -ib @f{#@c-Length: {stat -c%s @f{#};cat @f;else $e }@h 404 Not Found##404#};fi)';done"|tr "@{}#" '$`"
'|sh
Оставил его, потому что выглядит страшно, детей пугать – самое то :)

Если у вас получится сэкономить еще пару байтов, то милости просим в комментаторскую :)

UPD: 03.09.2007: Код обновлен, в него вошли исправления, которые сделал тов. jetxee, за которые ему большое спасибо.

UPD: 03.09.2007: Я удивлен тому факту, что эта заметка попала в топ news2, спасибо dik’у за то, что добавил ее. Я рад, что она многим показалась интересной.

UPD: 07.09.2007: Товарищ Ed очень здорово прооптимизировал код. Вот, что у него вышло:
while :;do nc -p8080 -vnlc'r=read;e="echo -e";$r a b c;while [ -n "`$e $a|tr -d "\r\n"`" ];do $r a;done;f=`$e $b|sed s/.//`;h="HTTP/1.0";z="404 Not Found\n";[ -z $f ]&&(ls|while $r n;do [ -f $n ]&&$e "$n";done)||([ -f $f ]&&($e "$h 200 OK\r\nContent-Type: `file -ib $f`\n";cat $f)||$e "$h $z\n$z")';done

Работоспособность проверялась автором на таблетке Nokia N800, а у меня в Kubuntu 7.04 что-то не заработало. Сегодня вечером буду разбираться :)

p.s. Мне теперь стыдно из-за того, что сам не догадался использовать && и || вместо if..then. :(

p.p.s. stay tuned!

UPD 18.08.2008: О, написали про это безобразие на Хабре, назвали меня извращенцем. Хулиганьё!! :)

SHELL, UNIX, tips, Программирование , , , , ,

Программерские загогулины [2]

August 23rd, 2007

Очередная программерская загогулина, на этот раз решение сразу не дам :)

Допустим, надо выполнить какую-либо команду на нескольких хостах, перечисленных в файле. Пишу:

cat file | (
while read host; do
ssh user@$host "command"
done
)

Ошибка, однако! В чем?
Кто сможет разобраться в уме, не запуская код, тому респект и уважуха :)

UPD:

Решил человек, скрывающийся под ником gds:

поток из “cat file” будет выкушан только первой запущенной командой “ssh …”?

gds, респект тебе уважуха :)

SHELL, UNIX, Программирование, загогулины, общее , , , ,

Perl и фиксированные ключи в хеше.

July 5th, 2007

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

Тогда решения не нашлось. И сейчас PHP для меня лично уходит потихоньку в прошлое, т.к. в perl нахожу все больше интересных возможностей, которых раньше не хватало.
Так вот, по поводу фиксированного набора ключей, вот пример объекта на основе такого хеша:

package some;

use warnings;
use strict;
use Hash::Util qw{lock_keys};

sub new
{
my $class = shift;
my %hash;
my @keys = ("one", "two", "three", "four", "five");
my $self = bless \%hash, $class;
lock_keys %hash, @keys;
return $self;
}

1;

Теперь при выполнении инструкция $some->{‘there’} произойдет ошибка:
Attempt to access disallowed key 'there' in a restricted hash at ./test.pl line 11.

Причем сделано это не через Tie, чего я сначала опасался, а собственными средствами компилятора, т.е. должно работать очень быстро.

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


package Foo;
use base qw(Class::Accessor);
Foo->mk_accessors('foo'); # или mk_ro_accessors(qw{ one two three }) для read-only полей

my $obj = Class->new({ foo => 42 });
print $obj->foo; # 42
$obj->foo("24");
print $obj->foo; # 24

Ну вот и все! Теперь если захочется изменить поведение объекта таким образом, чтобы при изменении свойства foo, менялось также значение bar, достаточно реализовать самостоятельно нужную функцию, а править использующий класс код не нужно.

Все-таки perl язык великий и могучий, этого у него не отнять!

PERL, Программирование, общее , , ,

return 0;

June 27th, 2007

Я вернулся :)
За время учебного отпуска нагулял отличное рабочее настроение!

В первый рабочий день обратил внимание на небольшой юбилей: 1 год и 4 дня исполнилось моему процессу screen, запущенному на работе, которым я активно пользуюсь по 40 часов в неделю. Слава героям!!

Попутно веду свою маленькую коллекцию программерских загогулин, вот очередной экземпляр:


#!/usr/bin/perl
$var1 = "one|two|three";
$var2 = join('|', split ('|', $var1));

Опрос знакомых программистов показал, что предполагаемым значением $var2 является исходная строка ($var1). PERL думает иначе.

Вся мощь перла с его реально перегруженными функциями именно в этом моменте дала слабину: split в качестве первого аргумента принимает _только_ регулярное выражение, и, соответственно, символ ‘|’ воспринимается как логическое “или”.. Таким образом, в var2 будет “o|n|e||t|w|o”…

Пусть такое поведение и является документированным, меня оно все равно расстраивает..

PERL, Программирование, загогулины , , ,