# HG changeset patch # User Igor Chubin # Date 1268762730 -7200 # Node ID 8ee5e59f1bd324b5b6ff240c358ce06527a871b1 # Parent 71d6b2a6b8eb4b2c03e026f0dfa47d9c574f98ee Локальное хранение и анализ данных с помощью SQlite Очень много изменений, касающихся работы с sqlite и локального использования результатов записи. Подробнее: README.l3text diff -r 71d6b2a6b8eb -r 8ee5e59f1bd3 README --- a/README Mon Dec 28 22:00:37 2009 +0300 +++ b/README Tue Mar 16 20:05:30 2010 +0200 @@ -13,3 +13,4 @@ http://xgu.ru/wiki/LiLaLo + diff -r 71d6b2a6b8eb -r 8ee5e59f1bd3 README.l3text --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/README.l3text Tue Mar 16 20:05:30 2010 +0200 @@ -0,0 +1,380 @@ + + +l3text (или l3) это программа, которая предоставляет интерфейс +к информации, записанной LiLaLo, прямо в командной строке. + +Результаты распознавания записываются с помощью l3-agent +в базу данных SQLite, и они становятся сразу же доступными для +использования с помощью l3text. + +l3text — это набор инструментов, для работы с транскриптами сеансов, +сгруппированные под одной крышей. + +Некоторые примеры использования. + +Показать весь журнал команд в текущем контексте +(начиная с текущего положения). + $ l3text cat + +Показать только командные строки или только вывод: + $ l3text cat -c + $ l3text cat -o + +Показать последние 10 команд: + $ l3text tail + +Показать последние 20 команд: + $ l3text tail -20 + +Следить за текущим сеансом, то есть показывать все команды, +выполняющиеся в текущем контексте: + $ l3text tail -f + +По умолчанию показываются только команды и результаты их выполнения. +Вывод можно настроить: + $ l3 cat -o time,cline,output + +== Контекст == + +Все команды, которые вы набираете, и результаты их выполнения +не повисают в воздухе, а дописываются в конец листов, +находящихся в определённых местах. + +Лист, на который попадают команды называется ''контекстом записи''. + +Вы можете набирать команды в разных окнах, +но если они находятся в одном контексте, то они запишутся +на один лист, одна под другой. + +С другой стороны, в одном окне вы можете поменять контекст несколько раз, +и команды, набиравшиеся в этих контекстах, попадут на разные листы. + +По умолчанию контекст равен /default , +но для удобства дальнейшей работы с записями рекомендуется +задавать нужный контекст, которому соответствуют выполняемые действия. + +Сменить текущий контекст записи можно так: + l3 context /path/to/new/context + +Просмотреть контекст записи можно так: + l3 context + +Итак, контекст записи (контекст агента) определяет, +куда попадают команды при записи. + +Мы записывали команды не просто так. +Мы хотим поработать с тем, что мы записали. +Лист, на котором записаны команды, с которыми мы хотим +поработать, определяется ''контекстом чтения''. + +То есть он определяет, какие именно команды +из записанных ранее мы имеем в виду. + +По умолчанию текущий контекст чтения становится равным +текущему контексту записи. + +Смена текущего контекста чтения: + $ l3 cd /path/to/new/context +или + $ l3 go /path/to/new/context + +Просмотр доступных контекстов: + $ l3 ls + +Просмотреть имя текущего контекста чтения: + $ l3 pwd + +Символы : и / являются в имени контекста специальными. + +Символ / формирует иерархию контекстов, +а символ : указывает координаты строки в контексте. + +Перейти на 3 команды назад: + $ l3 go :-3 + +Перейти в контекст /adm/lab1, в его самый конец (и находиться +всё время в конце, если он растёт, то есть делаются новые записи): + $ l3 go /adm/lab1 + +Перейти в контекст /adm/lab1, на первую команду grep: + $ l3 go /adm/lab1:/grep/ + +О том, как указывать координаты интересующих нас строк, +написано ниже, в разделе «Интервалы». + + +== Команда == + + l3 [ОПЦИИ1] [ИНТЕРВАЛ] [filter ВЫРАЖЕНИЕ] [grep РЕГВЫР] [КОМАНДА] [ОПЦИИ2] + +== Интервалы == + +По умолчанию операция выполняется над всеми командами, +находящимися в текущем контексте, начиная с текущей точки и до его конца. + +Можно ограничить команды с помощью интервала: + + выражение + выражение,выражение + +Примеры: + + -10,. последние 10 команд + .,+3 от текущей команды три вниз + /grep/ последняя команда grep + o/grep/ последняя команда, содержащая grep в выводе (output) + . текущая строка + @12:23 команда, выполненная в 12:23 (12:23 текущего часа) + @08:12:00 команда, выполненная в 8:12:00 (или ближайшая после этого времени) + 1 первая команда в контексте + $ последняя команда в контексте + % весь контекст + + +Интервал указывается перед командой, работу которой он ограничивает: + + l3 -10,. cat +напечатать последние 10 команд + +равносильно этому: + l3 tail -10 + +интервал = строка +интервал = строка,строка +интервал = % +строка = число + = -число + = +число + = @время + = /регвыр/ + = . + = $ + +== Фильтрация == + +Можно указать дополнительный фильтр, +который оставляет команды, которые нас интересуют. + +Есть два вида фильтров: + * по регулярным выражениям + * по вычисляемому выражению (синтаксис perl с некоторыми отличиями) + +grep -v regexp инверсия +grep regexp прямой поиск + +-f полный +-o только в выводе +-c только в команде (по умолчанию) + +filter выражение + +== Команды == + +cat +tail +head +history + +Все эти команды являются сокращением для одной команды. + +== Зачем всё это нужно == + +* использование данных вывода команд при автоматическом продолжении команд +* быстрое выделение и копирование команд +* поиск команд +* привязка ко времени +* подключение к журналу + +=== Tab-completion: Данные вывода команд при наборе других команд === + +Вы выполнили одну команду, +и хотите использовать результаты её исполнения +в качестве аргумента другой. + +Например, вы просканировали wifi-сети: + # iwlist scanning +В выводе команды присутствовали названия SSID, один из которых мы будем использовать дальше. +Было бы классно, если бы дальше, когда мы вводим команду + # iwconfig wlan0 essid ____ +мы могли использовать табуляцию при наборе essid. + +В качестве списка возможных продолжений +должны использоваться сети, которые выдала предыдущая команда. + +Это становится возможным с использованием l3text +(при написании соответствующих расширения для bash_completion). + +=== Быстрое выделение и копирование команд === + +Ваш друг помогает вам настроить систему по джабберу. +Он присылает вам команды, которые вы творчески перерабатываете +и вводите в консоль. + +Потом, для того чтобы он видел, что получается, вы копируете результаты +исполнения ему в джаббер. + +Например, вы хотите показать результат пяти последних команд: + +В самом простейшем случае (если вы используете графический-джаббер клиент) + l3 tail -5 | xsel + +Потом идёте в джаббер-клиент и делаете: + shift-insert + +Можно отправить ему код прямо из консоли: + l3 tail -5 | sendxmpp igor@chub.in + +Или даже ещё проще, всё, что вы делаете будет копироваться ему: + l3 tail -f | sendxmpp igor@chub.in + +Или, если вы не хотите, чтобы копировались команды, набранные неверно: + l3 filter err!=128 tail -f | sendxmpp igor@chub.in + + +Команды l3 не обязательно вызывать в той сессии, где идёт настройка. +Вы можете их вызывать в соседнем терминале, где вообще не идёт запись. +Конечно, вы должны будете указать соответствующий контекст. + + l3 cd /adm/lab1 + +(предполагается, что настройка идёт в /adm/lab1 ) + +=== Поиск команд === + +Вы выполняете настройку сервера, +в ходе которой сделали много инсталляций пакетов. + +Вы делали их в разных окнах, уже сами не помните в каких, +но запись всё время шла. + +Вы хотите увидеть имена пакетов, которые были установлены. + +Простейший вариант: + + l3 grep apt-get + +Если вы помните, что начали работу по инсталляции после обеда, +а всё, что было до этого, к делу не относится: + l3 @14:00:00,$ grep apt-get + +Если вы помните, что было много инсталляций, но в некоторых из них +вы неправильно указывали имена пакетов, и вы хотели бы оставить +только те из них, которые выполнились успешно: + l3 grep err=0 grep apt-get +или + l3 filter err=0&&/apt-get/ +или + l3 filter err=0 grep apt-get + +=== Привязка ко времени === + +Вы рассказываете, как настроить какую-то вещь. +Вы хотели бы, чтобы всё, что вы рассказываете, +можно было использовать как основу для составления +более подробного документа. + +Вы используете доску (или графический планшет), +некоторые вещи делаются не в командной строке, +а на экране с графическим интерфейсом, +кроме того ваш голос записывается. + +Вы не можете тратить время на то, чтобы положить скриншоты +в нужное место, но вы можете их сделать. +Аналогично и со скринкастами. Вы можете записывать то, +что вы делаете при подключении к какой-то системе, +но вы не можете выполнять никаких других операций +по обработке записи. Потому что на это просто нет времени. + +Ход вашей работы фиксируется, операции запоминаются, +скриншоты помечаются временными метками. +Аналогично происходит со схемами на доске, +которые фотографируются и представляются в виде помеченных +временными метками файлов. + +После того, как сеанс окончен, +вы получаете его транскрипт, который можно дальше редактировать. +Команды в этом транскрипте сопровождаются специальными якорями, +с помощью которых они привязываются к записанному ранее сеансу. + +В транскрипте вы удаляете лишние команды, +добавляете текст и получаете документ, +который подробно описывает происходившее, +причём при желании вы можете получить недостающую информацию +из архива сделанных ранее операций. + +Туда же, в нужные (соответствующие времени) места, +автоматически попадают сделанные скриншоты, +скринкасты, фотографии, запись звука. + + +=== Подключение к журналу === + +Продолжим предыдущий пример. + +Вы прочитали документацию, составленную по описанной выше методике, +и теперь хотите выполнить такую же настройку. + +В обычном случае вы просто копируете команды в свою командную строку +или набираете их заново, с учётом ваших собственных условий. + +Можно значительно ускорить и упростить этот процесс, +а так же уменьшить вероятность ошибки возможной в ходе его выполнения. + +Вы подключаетесь к сделанной записи: + l3 go http://xgu.ru/l3/option-82 + +Все рассмотренные ранее команды становятся доступным. + +Становится доступным продолжение команд (tab completion) +на основе информации, набранной в той работе +и даже больше: вы можете добавить команды +из контекста в историю текущего командного интерпретатора, +как будто вы их сами уже выполняли. + +Автоматически они не добавляются, чтобы не создавать путаницы. +Но вы можете их загрузить: + l3 % | l3hist +или, если вас интересуют только определённые команды, то с использование интервала, +например: + l3 @14:00,$ | l3hist + +Вам тогда не нужно будет их заново набирать. +Достаточно прокрутить историю вверх, а потом просто выполнять +по одной, нажимая ctrl-o, при этом адаптируя к своим условиям. + +Чтобы ещё сильнее сократить себе объём работы, +можно применить процедуру адаптации, которая позволяет +автоматически заменять специфические для процедуры параметры. + +Для этого вы вызываете: + l3 parameterize + l3 param (сокращённо) +которая открывает текстовый редактор, где указывается два столбика параметров: +что заменить и на что заменить. + +Например, пусть в описанном документе используются адреса: + 192.168.15.1 + 192.168.15.254 + example.com + +Причём составитель этого документа мог указать, что эти параметры являются специфичными, +а мог не указывать. Если он указал, то совсем хорошо — тогда вам нужно просто пройтись +и добавить справа, на что они должны заменяться в вашем случае. +Если же он не указал, то вам нужно придумать список замен по своему вкусу. + +Вы вводите локализованную версию: + 192.168.15.1 10.0.35.1 + 192.168.15.254 10.0.35.254 + example.com xgu.ru + +Теперь, во всех выводах команды l3 будут исправленные команды +и исправленные результаты их выполнения. +Автопродолжение (tab completion) в шелле уже будет показывать исправленные аргументы. +Историю командного интерпретатора вы тоже можете обновить, +и в ней тогда будут изменённые команды. + +Использовать параметризацию с загрузкой удалённого контекста +настолько логично, что вы можете это сделать одной командой: + l3 go -p http://xgu.ru/l3/option-82 + + diff -r 71d6b2a6b8eb -r 8ee5e59f1bd3 l3-agent --- a/l3-agent Mon Dec 28 22:00:37 2009 +0300 +++ b/l3-agent Tue Mar 16 20:05:30 2010 +0200 @@ -10,6 +10,7 @@ use Text::Iconv; use Time::Local 'timelocal_nocheck'; use IO::Socket; +use DBI; use lib "/etc/lilalo"; use l3config; @@ -176,8 +177,7 @@ { my $cline = $_[0]; my @lists = split /\;/, $cline; - - + my @commands = (); for my $list (@lists) { push @commands, split /\|/, $list; @@ -344,7 +344,7 @@ my $tty = $1; my %cl; my $last_output_length=0; - my $saved_output; + my $saved_output; while () { $commandlines_processed++; @@ -357,7 +357,8 @@ $commandlines_loaded++; $last_output_length=0; - # Previous command + # Сохраняем часть выполненного ранее разбора в переменной last_cl, + # которую мы дополним некоторой информацией и сохраним позже my %last_cl = %cl; my $this_line = $1; my $err = $2 || ""; @@ -425,7 +426,6 @@ $last_cl{"err"}=$err; $last_cl{"err"}=130 if $err eq "^C"; - # Output if (!$last_cl{"suppress_output"} || $last_cl{"err"}) { for (my $i=0; $i<$Config{"terminal_height"}; $i++) { @@ -570,8 +570,19 @@ } $vt{$local_session_id}->reset(); - $saved_output=""; + $saved_output=""; +# Обработка команд с одинаковым временем +# Скорее всего они набраны с помощью tab-completion + if ((defined($last_cl{time}) + && defined($cl{time}) + && $last_cl{time} == $cl{time}) + && + (defined($last_cl{nonce}) + && defined($cl{nonce}) + && $last_cl{nonce} == $cl{nonce})) { + $last_cl{"tab"}=1; + }; # Changing encoding for (keys %last_cl) { @@ -675,21 +686,109 @@ $text =~ s/&/&/g; $text =~ s//>/g; + $text =~ s/"/"/g; print $TO $text; } +sub strq +{ + my $text = join "", @_; + $text =~ s/&/&/g; + $text =~ s//>/g; + $text =~ s/"/"/g; + return $text; +} =cut Вывести результат обработки журнала. =cut +sub print_commands_to_sqlite_cache +{ + my $output_filename=$_[0]; + my $db = DBI->connect("dbi:SQLite:$output_filename", "", "", + {RaiseError => 1, AutoCommit => 1}); + + my $cl; + for my $i (@Command_Lines_Index) { + my $val; + my $var_list; + my $values_list; + my $create_var_list; + my $sql; + $cl = $Command_Lines[$i]; + + $cl->{l3cd}=$Config{l3cd} if $Config{"l3cd"}; + for my $element (qw( + local_session_id + history + uid + pid + time + pwd + raw_start + raw_output_start + raw_end + raw_file + tty + err + last_command + nonce + l3cd + tab + )) { + if (defined($cl->{"$element"})) { + $val = $cl->{"$element"}; + } + else { + $val = "" + }; + $var_list .= "$element,"; + $values_list .= '"'.$val."\","; + $create_var_list .= ", $element TEXT"; + } + for my $element (qw( + prompt + cline + output + )) { + if (defined($cl->{"$element"})) { + $val = $cl->{"$element"}; + } + else { + $val = "" + }; + $var_list .= "$element,"; + $values_list .= '"'.strq($val)."\","; + $create_var_list .= ", $element TEXT"; + } + if ($cl->{"diff"}) { + $val = strq(${$Diffs{$cl->{"diff"}}}{"text"}); + } + else { + $val=""; + } + $var_list .= "diff,"; + $create_var_list .= ", diff TEXT"; + $values_list .= '"'.$val."\","; + + $db->do("CREATE TABLE IF NOT EXISTS commands (id INTEGER PRIMARY KEY $create_var_list)"); + #print "CREATE TABLE commands (id INTEGER PRIMARY KEY $create_var_list)\n"; + $values_list =~ s/,$//; + $var_list =~ s/,$//; + $sql = "INSERT INTO commands ($var_list) VALUES($values_list) "; + print "$sql\n"; + $db->do($sql); + } +} + sub print_command_lines { my $output_filename=$_[0]; open(OUT, ">>", $output_filename) or die "Can't open $output_filename for writing\n"; - my $cl; my $in_range=0; for my $i (@Command_Lines_Index) { @@ -704,17 +803,12 @@ next; } next if ($Config{"from"} && $Config{"to"} && !$in_range) - || - ($Config{"skip_empty"} =~ /^y/i && $cl->{"cline"} =~ /^\s*$/ ) - || - ($Config{"skip_wrong"} =~ /^y/i && $cl->{"err"} != 0) - || - ($Config{"skip_interrupted"} =~ /^y/i && $cl->{"err"} == 130); - + || ($Config{"skip_empty"} =~ /^y/i && $cl->{"cline"} =~ /^\s*$/ ) + || ($Config{"skip_wrong"} =~ /^y/i && $cl->{"err"} != 0) + || ($Config{"skip_interrupted"} =~ /^y/i && $cl->{"err"} == 130); + # Вырезаем из вывода только нужное количество строк - my $output=""; - if (!grep ($_ eq $cl->{"last_command"}, @{$Config{"full_output_commands"}}) && ($Config{"head_lines"} || $Config{"tail_lines"})) { @@ -730,20 +824,20 @@ if ($start < 0) { $start=0; $mark=0; - } + } if ($start < $Config{"cache_head_lines"}) { $start=$Config{"cache_head_lines"}; $mark=0; - } + } $output .= $Config{"skip_text"}."\n" if $mark; for ($i=$start; $i<= $#lines; $i++) { $output .= $lines[$i]."\n"; } - } + } else { # Full output $output .= $cl->{"output"}; - } + } # Совместимость с labmaker @@ -759,7 +853,6 @@ # timelocal( $sec, $min, $hour, $mday,$mon,$year); $cl->{time} ||= timelocal_nocheck($cl->{sec},$cl->{min},$cl->{hour},$cl->{day},0,$year); - # Начинаем вывод команды print OUT "\n"; print OUT "$Config{l3cd}\n" if $Config{"l3cd"}; @@ -917,6 +1010,7 @@ load_command_lines($Config{"input"}, $Config{"input_mask"}); sort_command_lines; #process_command_lines; + print_commands_to_sqlite_cache($Config{"cache_sqlite"}); print_command_lines($Config{"cache"}); } else { @@ -975,6 +1069,7 @@ if (@Command_Lines) { sort_command_lines; #process_command_lines; + print_commands_to_sqlite_cache($Config{"cache_sqlite"}); print_command_lines($Config{"cache"}); } save_cache_stat(); diff -r 71d6b2a6b8eb -r 8ee5e59f1bd3 l3bashrc --- a/l3bashrc Mon Dec 28 22:00:37 2009 +0300 +++ b/l3bashrc Tue Mar 16 20:05:30 2010 +0200 @@ -57,6 +57,7 @@ { export L3_SESSION_ID=${RANDOM}${RANDOM}${RANDOM}${RANDOM}-`date +%s` export L3_HOME=~/.lilalo/ + L3_SQLITE=$L3_HOME/report.sqlite mkdir -p $L3_HOME tty=`/usr/bin/tty` @@ -70,6 +71,21 @@ hostname=`hostname -f 2> /dev/null` [ -n "$bsd" ] && hostname=`hostname` + cat <connect("dbi:SQLite:$L3_SQLITE", "", "", +{RaiseError => 1, AutoCommit => 1}); + +\$db->do("CREATE TABLE IF NOT EXISTS sessions (id INTEGER PRIMARY KEY, + session TEXT, hostname TEXT, user TEXT, uid TEXT, login_from TEXT, + tty TEXT, system TEXT, parent TEXT, ppid TEXT, pid TEXT, start_time TEXT, lang TEXT)"); +\$db->do("INSERT INTO sessions VALUES (NULL, '$L3_SESSION_ID', '$hostname', '$USER', '$UID', '$login_from', + '$tty', '$system', '$parent', '$PPID', '$$', + '$start_time', '$LANG')"); +INFO + #perl $temp_l3_name #; rm $temp_l3_name; unset temp_l3_name + cat < $L3_HOME/$L3_SESSION_ID.info $L3_SESSION_ID @@ -258,11 +274,14 @@ l3 () { case "$1" in - on) - echo switching on - ;; - off) - echo switcing off + context) + if [ -z "$2" ] + then + echo "$L3_CONTEXT" + else + echo $2 | grep -q ^/ && L3_CONTEXT="$2" || L3_CONTEXT="$L3_CONTEXT/$2" + export L3_CONTEXT="`echo $L3_CONTEXT | perl -e '$_=<>; 1 while s@/[^/]*/\.\.@@; print;'`" + fi ;; cd) echo l3cd="$2" > ~/.l3rc @@ -271,26 +290,10 @@ grep ^l3cd= ~/.l3rc | sed s/[^=]*=// ;; *) - cat < "utf-8", "cache" => "$ENV{HOME}/.lilalo/report.xml", + "cache_sqlite" => "$ENV{HOME}/.lilalo/report.sqlite", "cache_stat" => "$ENV{HOME}/.lilalo/.report.dat", "output" => "/tmp/report.html", @@ -152,32 +153,44 @@ sub read_config_file { - my $config = $_[0]; - my $filename = $_[1]; - open(CONFIG, "$filename") - or return; - while () { + my $config = $_[0]; + my $filename = $_[1]; + open(CONFIG, "$filename") + or return; + while () { chomp; - s/#.*//; - next if /^\s*$/; - my ($var, $val) = split /\s*=\s*/, $_, 2; - $var =~ s/\s*//; - $config->{$var} = $val; - } - close(CONFIG); + s/#.*//; + next if /^\s*$/; + my ($var, $val) = split /\s*=\s*/, $_, 2; + $var =~ s/\s*//; + $config->{$var} = $val; + } + close(CONFIG); } sub init_config { - my %argv_config; - my %file_config; - read_config_file(\%file_config, $System_Config_File); - read_config_file(\%file_config, $User_Config_File); - GetOptions(\%argv_config, map "$_=s", keys %Config); - %Config = (%Config, %file_config, %argv_config); + my %argv_config; + my %file_config; + read_config_file(\%file_config, $System_Config_File); + read_config_file(\%file_config, $User_Config_File); + GetOptions(\%argv_config, map "$_=s", keys %Config); + %Config = (%Config, %file_config, %argv_config); for my $key (keys %Config) { utf8::decode($Config{$key}); } } +sub init_config_without_command_line_processing +{ + my %argv_config; + my %file_config; + read_config_file(\%file_config, $System_Config_File); + read_config_file(\%file_config, $User_Config_File); + %Config = (%Config, %file_config); + for my $key (keys %Config) { + utf8::decode($Config{$key}); + } +} + diff -r 71d6b2a6b8eb -r 8ee5e59f1bd3 l3text --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/l3text Tue Mar 16 20:05:30 2010 +0200 @@ -0,0 +1,365 @@ +#!/usr/bin/perl -w + +use POSIX qw(strftime); +use lib '/etc/lilalo'; +use l3config; +use utf8; +use DBI; +#no warnings; + +our @Fields=qw/id time prompt cline output err last_command tab diff l3cd/; +our $Fields=join(", ",@Fields); +our $db; + +=cut +Множество команд, с которыми выполняется операция, +определяется текущим контекстом и текущей строкой, +а также параметрами: + * фильтр; + * регулярное выражение; + * интервал. +=cut +our $Filter; +our $Grep; +our $Interval; +our $Command; + +# Если мы используем режим -f, то в этой переменной запоминается +# до какой точки мы дошли +our $Follow=0; + +our $Context; + +our @Commands = qw/head tail cat history/; + +sub main(); +sub connect_database(); + +main(); + +sub main() +{ + $| = 1; + + init_config_without_command_line_processing(); + $Context=$Config{l3cd}; + connect_database(); +# Обработка всех grep'ов + $Grep=""; + my $i=0; + my $arg; + my @argv=(); + while ($i<=$#ARGV) { + $arg=$ARGV[$i]; + #print "arg=$arg\n"; + if ($arg eq "grep") { + my $grep_keys=""; + my $grep_regexp=""; + $i++; + while(defined ($ARGV[$i]) and $ARGV[$i] =~ /^-/) { + $grep_keys .= " ".$ARGV[$i]; + $i++; + } + if (defined($ARGV[$i])) { + $grep_regexp = $ARGV[$i]; + $i++; + } + else { + die "ERROR: grep argument is not specified"; + } + $Grep .= grep_options($grep_regexp,$grep_keys); + } + else { + $i++; + push(@new_argv,$arg); + } + } + @argv=@new_argv; + + if (grep(/^-f$/, @argv)) { + $follow=1; + } + if (grep(/^history$/, @argv)) { + my $interval=$argv[0]||"%"; + $interval="%" if $interval eq "history"; + my $exit=0; + while (not $exit) { + print_commands(select_interval($interval, $Grep)); + if (not $follow) { + $exit=1; + } else { + sleep(1); + } + } + } else { + my $exit=0; + while (not $exit) { + print_all(select_interval($argv[0]||"%", $Grep)); + if (not $follow) { + $exit=1; + } else { + sleep(1); + } + } + } + #print_all(select_by_cline("*privet*")); +} + +sub connect_database() +{ + $db = DBI->connect("dbi:SQLite:".$Config{cache_sqlite}, "", "", + {RaiseError => 1, AutoCommit => 1}); + $db->func('regexp', 2, sub { + my ($regex, $string) = @_; + return $string =~ /$regex/; + }, 'create_function'); +} + +sub select_all() { + $l3cd=$Config{l3cd}; + return $db->selectall_arrayref("SELECT $Fields FROM commands WHERE l3cd='$l3cd'"); +} + +sub select_last_n($) { + my $last=$_[0]; + $count = $db->selectall_arrayref("SELECT COUNT(*) FROM commands"); + my $offset=$$count[0][0]-$last; + return $db->selectall_arrayref("SELECT $Fields FROM commands LIMIT $last OFFSET $offset"); +} + +sub select_interval($) { + my $interval = shift; + my $grep = shift; + + my $q; # Рабочая переменная для запросов +# % + $interval='1,$' if ($interval eq "%"); +# return $db->selectall_arrayref( +# "SELECT $Fields FROM commands WHERE l3cd='$Context'"); + +# Вычисляем номера начальной и конечной строки +# на основе интервала + my ($start,$stop); + my ($start_n, $stop_n, $curr_n) = ("","",""); # номера начальной и конечной строк + if ($interval =~ /,/) { + ($start,$stop) = split(/,/,$interval); + } + else { + $start = $interval; + $stop = $interval; + } + + $q = $db->selectall_arrayref("SELECT COUNT(*) FROM commands"); + my $count = $$q[0][0]; + +# Номер текущей строки +# По умолчанию текущая строка указывает на последнюю + $curr_n = $count; + #$curr_n = 2; + + if ($start =~ /^[0-9]*$/) { $start_n = $start; } + if ($stop =~ /^[0-9]*$/) { $stop_n = $stop; } + if ($start =~ /^-[0-9]*$/) { $start_n = $curr_n + $start; } + if ($stop =~ /^-[0-9]*$/) { $stop_n = $curr_n + $stop; } + if ($start =~ /^\+[0-9]*$/) { $start_n = $curr_n + $start; } + if ($stop =~ /^\+[0-9]*$/) { $stop_n = $curr_n + $stop; } + if ($start eq '.') { $start_n = $curr_n; } + if ($stop eq '.') { $stop_n = $curr_n; } + if ($start eq '$') { $start_n = $count; } + if ($stop eq '$') { $stop_n = $count; } + if ($start =~ m@^/(.*)/@ ) { + $q = $db->selectall_arrayref( + "SELECT id FROM commands WHERE cline REGEXP '$1' LIMIT 1"); + $start_n=$$q[0][0]; + } + if ($stop =~ m@^/(.*)/@ ) { + $q = $db->selectall_arrayref( + "SELECT id FROM commands + WHERE cline REGEXP '$1' + AND (id >= $start_n) LIMIT 1"); + $stop_n=$$q[0][0]; + } + if ($start =~ m@^o/(.*)/@ ) { + $q = $db->selectall_arrayref( + "SELECT id FROM commands WHERE output REGEXP '$1' LIMIT 1"); + $start_n=$$q[0][0]; + } + if ($stop =~ m@^o/(.*)/@ && $start_n) { + $q = $db->selectall_arrayref( + "SELECT id FROM commands + WHERE output REGEXP '$1' AND (id >= $start_n) LIMIT 1"); + $stop_n=$$q[0][0]; + } + + if (not $start_n or not $stop_n) {return ""}; + + $stop_n = $count if $count < $stop_n; + $start_n =1 if 1 > $start_n; + + my $offset=$start_n-1; + my $limit = $stop_n - $offset; + + my $pre_follow=$Follow; + $q = $db->selectall_arrayref( + "SELECT id FROM commands + WHERE ((id>=$start_n AND id<=$stop_n AND id>$pre_follow) $grep) + ORDER BY id DESC"); + $Follow = $$q[0][0] if $$q[0][0]; + return $db->selectall_arrayref( + "SELECT $Fields FROM commands + WHERE ((id>=$start_n AND id<=$stop_n AND id>$pre_follow) $grep)"); +} + +sub grep_options($$) { +=cut + -c ищем только в комадной строке # по умолчанию + -o ищем только в выводе + -f ищем и там, и там + -v инверсия +=cut + my $regexp=shift; return "" if $regexp eq ''; + my $options=shift; + my @options = split //, $options; + my $not = ""; + $not = "NOT" if grep(/v/,@options); + + my $grep =""; + if (grep(/f/,@options)) { + $grep = "AND $not ((cline REGEXP '$regexp') OR (output REGEXP '$regexp'))"; + } + elsif (grep(/o/,@options)) { + $grep = "AND $not (output REGEXP '$regexp')"; + } + elsif (1 || grep(/c/,@options)) { + $grep = "AND $not (cline REGEXP '$regexp')"; + } + + return $grep; +} + +sub select_by_cline($) { + my $cline = $_[0]; + return $db->selectall_arrayref("SELECT $Fields FROM commands WHERE cline GLOB '$cline'"); +} + +sub print_commands($) +{ + my @fields=@Fields; + my $fields=join(", ",@fields); + my $all=$_[0]; + foreach my $row (@$all) { + my $cl; #Каждая строка + my $i=0; + for my $f (@fields) { + $cl->{$fields[$i]}=$$row[$i]; + $i++; + } + print $cl->{id}." ".strdequot($cl->{cline})."\n"; + } +} +sub print_all($) { + my @fields=@Fields; + my $fields=join(", ",@fields); + my $all=$_[0]; + my $prev_cl; # Предыдущая строка + my $tab_seq; # Номер в табуляционной последовательности + foreach my $row (@$all) { + my $cl; #Каждая строка + my @anchor; #Начальная строка, якорь, по которому привязывается команда + my $res=""; #Буфер, в который выводится результат вывода каждой строки + $i=0; + for my $f (@fields) { + $cl->{$fields[$i]}=$$row[$i]; + $i++; + } + next if ($Config{"skip_empty"} =~ /^y/i && $cl->{"cline"} =~ /^\s*$/ ) + || ($Config{"skip_wrong"} =~ /^y/i && $cl->{"err"} != 0) + || ($Config{"skip_interrupted"} =~ /^y/i && $cl->{"err"} == 130); + + # Время + # Добавляем спереди 0 для удобочитаемости + my ($sec,$min,$hour,$day,$mon,$year,$wday,$yday,$isdst) = localtime($cl->{time}); + $min = "0".$min if $min =~ /^.$/; + $hour = "0".$hour if $hour =~ /^.$/; + $sec = "0".$sec if $sec =~ /^.$/; + + if ($Config{"show_time"} =~ /^y/i) { + push @anchor,"$hour:$min:$sec "; + } + + if ($cl->{"err"}) { + push @anchor, "err=".$cl->{'err'}; + } + if ($cl->{"tab"}) { + push @anchor, "tab=".$cl->{'tab'}; + } + $res.="#=".join("",@anchor)."\n"; + $res.=$cl->{prompt}.strdequot($cl->{cline})."\n"; + + my $output = ""; # фомируем вывод команды + my $last_command = $cl->{"last_command"}; + if (!( $Config{"suppress_editors"} =~ /^y/i + && grep ($_ eq $last_command, @{$Config{"editors"}}) + || $Config{"suppress_pagers"} =~ /^y/i + && grep ($_ eq $last_command, @{$Config{"pagers"}}) + || $Config{"suppress_terminal"}=~ /^y/i + && grep ($_ eq $last_command, @{$Config{"terminal"}}) + )) { + $output = strdequot($cl->{output}); + + # Вырезаем слишком длинные выводы + my @lines = split '\n', $output; + if (!$Config{"command_id"} + && ( $Config{"head_lines"} || $Config{"tail_lines"}) + && $#lines > $Config{"head_lines"} + $Config{"tail_lines"}) { + $output=""; + for (my $i=0; $i<= $#lines && $i < $Config{"head_lines"}; $i++) { + $output .= $lines[$i]."\n"; + } + $output .= $Config{"skip_text"}."\n"; + + my $start_line=$#lines-$Config{"tail_lines"}+1; + for (my $i=$start_line; $i<= $#lines; $i++) { + $output .= $lines[$i]."\n"; + } + } + } + $res.=$output; + if ( $Config{"show_diffs"} =~ /^y/i && $cl->{"diff"}) { + $res.= $cl->{"diff"} + } + #open(OUTPUT, '|sendxmpp nata_samoylenko@jabber.ru'); + #print OUTPUT $res; + #close(OUTPUT); + print $res; + $res=""; + $prev_cl=$cl; + } +} + +sub strdequot($) +{ + my $text = join "", @_; + $text =~ s/&/&/g; + $text =~ s/<//g; + $text =~ s/"/"/g; + return $text; +} + +sub show_usage() +{ + print <