#!/usr/bin/perl -w use IO::Socket; use lib '.'; use l3config; use locale; our @Command_Lines; our @Command_Lines_Index; our %Commands_Description; our %Args_Description; our $Mywi_Socket; our %Sessions; # vvv Инициализация переменных выполняется процедурой init_variables our @Day_Name; our @Month_Name; our @Of_Month_Name; our %Search_Machines; our %Elements_Visibility; # ^^^ our %Stat; our %CommandsFDistribution; # Сколько раз в журнале встречается какая команда my %mywi_cache_for; # Кэш для экономии обращений к mywi sub make_comment; sub load_command_lines_from_xml; sub load_sessions_from_xml; sub sort_command_lines; sub process_command_lines; sub init_variables; sub main; sub collapse_list($); sub print_all; sub print_command_lines; sub print_stat; sub print_header; sub print_footer; main(); sub main { $| = 1; init_variables(); init_config(); open_mywi_socket(); load_command_lines_from_xml($Config{"backend_datafile"}); load_sessions_from_xml($Config{"backend_datafile"}); sort_command_lines; process_command_lines; print_all($Config{"output"}); close_mywi_socket; } # extract_from_cline # In: $what = commands | args # Out: return ссылка на хэш, содержащий результаты разбора # команда => позиция # Разобрать командную строку $_[1] и возвратить хэш, содержащий # номер первого появление команды в строке: # команда => первая позиция sub extract_from_cline { my $what = $_[0]; my $cline = $_[1]; my @lists = split /\;/, $cline; my @command_lines = (); for my $command_list (@lists) { push(@command_lines, split(/\|/, $command_list)); } my %position_of_command; my %position_of_arg; my $i=0; for my $command_line (@command_lines) { $command_line =~ s@^\s*@@; $command_line =~ /\s*(\S+)\s*(.*)/; if ($1 && $1 eq "sudo" ) { $position_of_command{"$1"}=$i++; $command_line =~ s/\s*sudo\s+//; } if ($command_line !~ m@^\s*\S*/etc/@) { $command_line =~ s@^\s*\S+/@@; } $command_line =~ /\s*(\S+)\s*(.*)/; my $command = $1; my $args = $2; if ($command && !defined $position_of_command{"$command"}) { $position_of_command{"$command"}=$i++; }; if ($args) { my @args = split (/\s+/, $args); for my $a (@args) { $position_of_arg{"$a"}=$i++ if !defined $position_of_arg{"$a"}; }; } } if ($what eq "commands") { return \%position_of_command; } else { return \%position_of_arg; } } # # Подпрограммы для работы с mywi # sub open_mywi_socket { $Mywi_Socket = IO::Socket::INET->new( PeerAddr => $Config{mywi_server}, PeerPort => $Config{mywi_port}, Proto => "tcp", Type => SOCK_STREAM); } sub close_mywi_socket { close ($Mywi_Socket) if $Mywi_Socket ; } sub mywi_client { my $query = $_[0]; my $mywi; open_mywi_socket; if ($Mywi_Socket) { local $| = 1; local $/ = ""; print $Mywi_Socket $query."\n"; $mywi = <$Mywi_Socket>; $mywi = "" if $mywi =~ /nothing app/; } close_mywi_socket; return $mywi; } sub make_comment { my $cline = $_[0]; #my $files = $_[1]; my @comments; my @commands = keys %{extract_from_cline("commands", $cline)}; my @args = keys %{extract_from_cline("args", $cline)}; return if (!@commands && !@args); #return "commands=".join(" ",@commands)."; files=".join(" ",@files); # Commands for my $command (@commands) { $command =~ s/'//g; $CommandsFDistribution{$command}++; if (!$Commands_Description{$command}) { $mywi_cache_for{$command} ||= mywi_client ($command) || ""; my $mywi = join ("\n", grep(/\([18]\)/, split(/\n/, $mywi_cache_for{$command}))); $mywi =~ s/\s+/ /; if ($mywi !~ /^\s*$/) { $Commands_Description{$command} = $mywi; } else { next; } } push @comments, $Commands_Description{$command}; } return join(" \n", @comments); # Files for my $arg (@args) { $arg =~ s/'//g; if (!$Args_Description{$arg}) { my $mywi; $mywi = mywi_client ($arg); $mywi = join ("\n", grep(/\([5]\)/, split(/\n/, $mywi))); $mywi =~ s/\s+/ /; if ($mywi !~ /^\s*$/) { $Args_Description{$arg} = $mywi; } else { next; } } push @comments, $Args_Description{$arg}; } } =cut Процедура load_command_lines_from_xml выполняет загрузку разобранного lab-скрипта из XML-документа в переменную @Command_Lines # In: $datafile имя файла # Out: @CommandLines загруженные командные строки Предупреждение! Процедура не в состоянии обрабатывать XML-документ любой структуры. В действительности файл cache из которого загружаются данные просто напоминает XML с виду. =cut sub load_command_lines_from_xml { my $datafile = $_[0]; open (CLASS, $datafile) or die "Can't open file of the class ",$datafile,"\n"; local $/; $data = <CLASS>; close(CLASS); for $command ($data =~ m@<command>(.*?)</command>@sg) { my %cl; while ($command =~ m@<([^>]*?)>(.*?)</\1>@sg) { $cl{$1} = $2; } push @Command_Lines, \%cl; } } sub load_sessions_from_xml { my $datafile = $_[0]; open (CLASS, $datafile) or die "Can't open file of the class ",$datafile,"\n"; local $/; my $data = <CLASS>; close(CLASS); for my $session ($data =~ m@<session>(.*?)</session>@sg) { my %session; while ($session =~ m@<([^>]*?)>(.*?)</\1>@sg) { $session{$1} = $2; } $Sessions{$session{local_session_id}} = \%session; } } # sort_command_lines # In: @Command_Lines # Out: @Command_Lies_Index sub sort_command_lines { my @index; for (my $i=0;$i<=$#Command_Lines;$i++) { $index[$i]=$i; } @Command_Lines_Index = sort { $Command_Lines[$index[$a]]->{"time"} <=> $Command_Lines[$index[$b]]->{"time"} } @index; } ################## # process_command_lines # # Обрабатываются командные строки @Command_Lines # Для каждой строки определяется: # class класс # note комментарий # # In: @Command_Lines_Index # In-Out: @Command_Lines sub process_command_lines { for my $i (@Command_Lines_Index) { my $cl = \$Command_Lines[$i]; next if !$cl; $$cl->{err} ||=0; # Класс команды $$cl->{"class"} = $$cl->{"err"} eq 130 ? "interrupted" : $$cl->{"err"} eq 127 ? "mistyped" : $$cl->{"err"} ? "wrong" : ""; if (!$$cl->{"euid"}) { $$cl->{"class"}.="_root"; } #Обработка пометок # Если несколько пометок (notes) идут подряд, # они все объединяются if ($$cl->{cline}=~ m@cat[^#]*#([\^=v])\s*(.*)@) { my $note_operator = $1; my $note_title = $2; if ($note_operator eq "=") { $$cl->{"class"} = "note"; $$cl->{"note"} = $$cl->{"output"}; $$cl->{"note_title"} = $2; } else { my $j = $i; if ($note_operator eq "^") { $j--; $j-- while ($j >=0 && (!$Command_Lines[$j] || $Command_Lines[$j]->{tty} ne $$cl->{tty})); } elsif ($note_operator eq "v") { $j++; $j++ while ($j <= @Command_Lines && (!$Command_Lines[$j] || $Command_Lines[$j]->{tty} ne $$cl->{tty})); } $Command_Lines[$j]->{note_title}=$note_title; $Command_Lines[$j]->{note}.=$$cl->{output}; $$cl=0; } } elsif ($$cl->{cline}=~ /#([\^=v])(.*)/) { my $note_operator = $1; my $note_text = $2; if ($note_operator eq "=") { $$cl->{"class"} = "note"; $$cl->{"note"} = $note_text; } else { my $j=$i; if ($note_operator eq "^") { $j--; $j-- while ($j >=0 && (!$Command_Lines[$j] || $Command_Lines[$j]->{tty} ne $$cl->{tty})); } elsif ($note_operator eq "v") { $j++; $j++ while ($j <= @Command_Lines && $Command_Lines[$j]->{tty} ne $$cl->{tty} || !$Command_Lines[$j]); } $Command_Lines[$j]->{note}.="$note_text\n"; $$cl=0; } } } } =cut Процедура print_command_lines выводит HTML-представление разобранного lab-скрипта. Разобранный lab-скрипт должен находиться в массиве @Command_Lines =cut sub print_command_lines { my @toc; # Оглавление my $note_number=0; my $result = q(); my $this_day_resut = q(); my $cl; my $last_tty=""; my $last_day=q(); my $last_wday=q(); my $in_range=0; my $current_command=0; my %filter; if ($Config{filter}) { # Инициализация фильтра my %filter; for (split /&/,$Config{filter}) { my ($var, $val) = split /=/; $filter{$var} = $val || ""; } } $Stat{LastCommand} ||= 0; $Stat{TotalCommands} ||= 0; $Stat{ErrorCommands} ||= 0; $Stat{MistypedCommands} ||= 0; COMMAND_LINE: for my $k (@Command_Lines_Index) { my $cl=$Command_Lines[$Command_Lines_Index[$current_command++]]; next unless $cl; # Пропускаем команды, с одинаковым временем # Это не совсем правильно. # Возможно, что это команды, набираемые с помощью <completion> # или запомненные с помощью <ctrl-c> next if $Stat{LastCommand} == $cl->{time}; # Набираем статистику # Хэш %Stat $Stat{FirstCommand} = $cl->{time} unless $Stat{FirstCommand}; if ($cl->{time} - $Stat{LastCommand} < $Config{stat_inactivity_interval}) { $Stat{TotalTime} += $cl->{time} - $Stat{LastCommand} } $Stat{LastCommand} = $cl->{time}; $Stat{TotalCommands}++; # Пропускаем строки, которые противоречат фильтру # Если у нас недостаточно информации о том, подходит строка под фильтр или нет, # мы её выводим for my $filter_key (keys %filter) { next COMMAND_LINE if defined($cl->{local_session_id}) && defined($Sessions{$cl->{local_session_id}}->{$filter_key}) && $Sessions{$cl->{local_session_id}}->{$filter_key} ne $filter{$filter_key}; } # Пропускаем строки, выходящие за границу "signature", # при условии, что границы указаны # Пропускаем неправильные/прерванные/другие команды if ($Config{"from"} && $cl->{"cline"} =~ /$Config{"signature"}\s*$Config{"from"}/) { $in_range=1; next; } if ($Config{"to"} && $cl->{"cline"} =~ /$Config{"signature"}\s*$Config{"to"}/) { $in_range=0; 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); if ($cl->{class} eq "note") { my $note = $cl->{note}; $note = join ("\n", map ("<p>$_</p>", split (/-\n/, $note))); $note =~ s@(http:[a-zA-Z.0-9/?\_%-]*)@<a href='$1'>$1</a>@g; $note =~ s@(www\.[a-zA-Z.0-9/?\_%-]*)@<a href='$1'>$1</a>@g; $result .= "<tr><td colspan='6'>"; $result .= "<h4 id='note$note_number'>".$cl->{note_title}."</h4>" if $cl->{note_title}; $result .= "".$note."<p/><p/></td></td>"; if ($cl->{note_title}) { push @{$toc[@toc]},"<a href='#note$note_number'>".$cl->{note_title}."</a>"; $note_number++; } next; } my $output=""; # Выводим <head_lines> верхних строк # и <tail_lines> нижних строк, # если эти параметры существуют my @lines = split '\n', $cl->{"output"}; if (($Config{"head_lines"} || $Config{"tail_lines"}) && $#lines > $Config{"head_lines"} + $Config{"tail_lines"} ) { 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 ($i=$start_line; $i<= $#lines; $i++) { $output .= $lines[$i]."\n"; } } else { $output .= $cl->{"output"}; } # ## ## Начинается собственно вывод ## # my ($sec,$min,$hour,$day,$mon,$year,$wday,$yday,$isdst) = localtime($cl->{time}); # Добавляем спереди 0 для удобочитаемости $min = "0".$min if $min =~ /^.$/; $hour = "0".$hour if $hour =~ /^.$/; $sec = "0".$sec if $sec =~ /^.$/; #my @new_commands; #my @new_files; #@new_commands = split (/\s+/, $cl->{"new_commands"}) if defined $cl->{"new_commands"}; #@new_files = split (/\s+/, $cl->{"new_files"}) if defined $cl->{"new_files"}; $class=$cl->{"class"}; $Stat{ErrorCommands}++ if $class =~ /wrong/; $Stat{MistypedCommands}++ if $class =~ /mistype/; # DAY CHANGE if ( $last_day ne $day) { if ($last_day) { $result .= "<h3 id='day$last_day'>".$Day_Name[$last_wday]."</h3>"; #$result .= "Новые команды<br/>"; $result .= "<table width='100%'>\n"; $result .= $this_day_result; $result .= "</table>"; } push @toc, "<a href='#day$day'>".$Day_Name[$wday]."</a>\n"; $last_day=$day; $last_wday=$wday; $this_day_result = q(); } $this_day_result .= "<tr class='command'>\n"; # CONSOLE CHANGE if ( $last_tty ne $cl->{"tty"}) { my $tty = $cl->{"tty"}; $this_day_result .= "<td colspan='6'>" ."<table><tr><td class='ttychange' width='140' align='center'>" . $tty ."</td></tr></table>" ."</td></tr><tr>"; $last_tty=$cl->{"tty"}; } # TIME $this_day_result .= $Config{"show_time"} =~ /^y/i ? "<td valign='top' class='time' width='$Config{time_width}'>$hour:$min:$sec</td>" : "<td width='0'/>"; # COMMAND my $hint = make_comment($cl->{"cline"}); my $cline; $cline = $cl->{"prompt"}.$cl->{"cline"}; $cline =~ s/\n//; $cline = "<span title='$hint' class='with_hint'>$cline</span>" if $hint; $cline = "<span class='without_hint'>$cline</span>" if !$hint; $this_day_result .= "<td class='script'>\n"; $this_day_result .= "<pre class='${class}_cline'>\n" . $cline . "</pre>\n"; # 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"}}) )) { $this_day_result .= "<pre class='".$class."_output'>" . $output . "</pre>\n"; } # DIFF if ( $Config{"show_diffs"} =~ /^y/i && $cl->{"diff"}) { $this_day_result .= "<table><tr><td width='5'/><td class='diff'><pre>" . $cl->{"diff"} . "</pre></td></tr></table>"; } #NOTES if ( $Config{"show_notes"} =~ /^y/i && $cl->{"note"}) { my $note=$cl->{"note"}; $note =~ s/\n/<br\/>\n/msg; if (not $note =~ s@(http:[a-zA-Z.0-9/_?%-]*)@<a href='$1'>$1</a>@g) { $note =~ s@(www\.[a-zA-Z.0-9/_?%-]*)@<a href='$1'>$1</a>@g; }; # Ширину пока не используем # $this_day_result .= "<table width='$Config{note_width}' class='note'>"; $this_day_result .= "<table class='note'>"; $this_day_result .= "<tr><td class='note_title'>".$cl->{note_title}."</td></tr>" if $cl->{note_title}; $this_day_result .= "<tr><td width='100%' class='note_text'>".$note."</td></tr>"; $this_day_result .= "</table>\n"; } # COMMENT if ( $Config{"show_comments"} =~ /^y/i) { my $comment = make_comment($cl->{"cline"}); if ($comment) { $this_day_result .= "<table width='$Config{comment_width}'><tr><td width='5'/><td>" . "<table class='note' width='100%'>" . $comment . "</table>\n" . "</td></tr></table>"; } } # Вывод очередной команды окончен $this_day_result .= "</td>\n"; $this_day_result .= "</tr>\n"; } $result .= "<h3 id='day$last_day'>".$Day_Name[$last_wday]."</h3>"; $result .= "<table width='100%'>\n"; $result .= $this_day_result; $result .= "</table>"; return ($result, collapse_list (\@toc)); } ############# # print_all # # # # In: $_[0] output_filename # Out: sub print_all { my $output_filename=$_[0]; my $result; my ($command_lines,$toc) = print_command_lines; $result = print_header($toc); $result.= "<h2 id='log'>Журнал</h2>" . $command_lines; $result.= "<h2 id='stat'>Статистика</h2>" . print_stat; $result.= "<h2 id='help'>Справка</h2>" . $Html_Help . "<br/>"; $result.= "<h2 id='about'>О программе</h2>". $Html_About. "<br/>"; $result.= print_footer; if ($output_filename eq "-") { print $result; } else { open(OUT, ">", $output_filename) or die "Can't open $output_filename for writing\n"; print OUT $result; close(OUT); } } ############# # print_header # # # # In: $_[0] Содержание # Out: Распечатанный заголовок sub print_header { my $toc = $_[0]; my $course_name = $Config{"course-name"}; my $course_code = $Config{"course-code"}; my $course_date = $Config{"course-date"}; my $course_center = $Config{"course-center"}; my $course_trainer = $Config{"course-trainer"}; my $course_student = $Config{"course-student"}; my $title = "Журнал лабораторных работ"; $title .= " -- ".$course_student if $course_student; if ($course_date) { $title .= " -- ".$course_date; $title .= $course_code ? "/".$course_code : ""; } else { $title .= " -- ".$course_code if $course_code; } # Управляющая форма my $control_form .= "<table id='visibility_form' class='visibility_form'><tr><td>Видимые элементы</TD></tr><tr><td><form>\n"; for my $element (keys %Elements_Visibility) { my @e = split /\s+/, $element; my $showhide = join "", map { "ShowHide('$_');" } @e ; $control_form .= "<input type='checkbox' name='$e[0]' onclick=\"$showhide\" checked>". $Elements_Visibility{$element}. "</input><br>\n"; } $control_form .= "</form></td></tr></table>\n"; my $result; $result = <<HEADER; <html> <head> <meta content='text/html; charset=utf-8' http-equiv='Content-Type' /> <link rel='stylesheet' href='$Config{frontend_css}' type='text/css'/> <title>$title</title> </head> <body> <script> $Html_JavaScript </script> <h1>Журнал лабораторных работ</h1> HEADER if ( $course_student || $course_trainer || $course_name || $course_code || $course_date || $course_center) { $result .= "<p>"; $result .= "Выполнил $course_student<br/>" if $course_student; $result .= "Проверил $course_trainer <br/>" if $course_trainer; $result .= "Курс " if $course_name || $course_code || $course_date; $result .= "$course_name " if $course_name; $result .= "($course_code)" if $course_code; $result .= ", $course_date<br/>" if $course_date; $result .= "Учебный центр $course_center <br/>" if $course_center; $result .= "</p>"; } $result .= <<HEADER; <table width='100%'> <tr> <td width='*'> <table border=0 id='toc' class='toc'> <tr> <td> <div class='toc_title'>Содержание</div> <ul> <li><a href='#log'>Журнал</a></li> <ul>$toc</ul> <li><a href='#stat'>Статистика</a></li> <li><a href='#help'>Справка</a></li> <li><a href='#about'>О программе</a></li> </ul> </td> </tr> </table> </td> <td valign='top' width=200>$control_form</td> </tr> </table> HEADER return $result; } ############# # print_footer # # # # # sub print_footer { return "</body>\n</html>\n"; } ############# # print_stat # # # # In: # Out: sub print_stat { %StatNames = ( FirstCommand => "Время первой команды журнала", LastCommand => "Время последней команды журнала", TotalCommands => "Количество командных строк в журнале", ErrorsPercentage => "Процент команд с ненулевым кодом завершения, %", MistypesPercentage => "Процент синтаксически неверное набранных команд, %", TotalTime => "Суммарное время работы с терминалом <sup><font size='-2'>*</font></sup>, час", CommandsPerTime => "Количество командных строк в единицу времени, команда/мин", CommandsFrequency => "Частота использования команд", RareCommands => "Частота использования этих команд < 0.5%", ); @StatOrder = ( FirstCommand, LastCommand, TotalCommands, ErrorsPercentage, MistypesPercentage, TotalTime, CommandsPerTime, CommandsFrequency, RareCommands, ); # Подготовка статистики к выводу # Некоторые значения пересчитываются! # Дальше их лучше уже не использовать!!! my %CommandsFrequency = %CommandsFDistribution; $Stat{TotalTime} ||= 0; my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($Stat{FirstCommand} || 0); $Stat{FirstCommand} = sprintf "%02i:%02i:%02i %04i-%2i-%2i", $hour, $min, $sec, $year+1900, $mon+1, $mday; ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($Stat{LastCommand} || 0); $Stat{LastCommand} = sprintf "%02i:%02i:%02i %04i-%2i-%2i", $hour, $min, $sec, $year+1900, $mon+1, $mday; if ($Stat{TotalCommands}) { $Stat{ErrorsPercentage} = sprintf "%5.2f", $Stat{ErrorCommands}*100/$Stat{TotalCommands}; $Stat{MistypesPercentage} = sprintf "%5.2f", $Stat{MistypedCommands}*100/$Stat{TotalCommands}; } $Stat{CommandsPerTime} = sprintf "%5.2f", $Stat{TotalCommands}*60/$Stat{TotalTime} if $Stat{TotalTime}; $Stat{TotalTime} = sprintf "%5.2f", $Stat{TotalTime}/60/60; my $total_commands=0; for $command (keys %CommandsFrequency){ $total_commands += $CommandsFrequency{$command}; } if ($total_commands) { for $command (reverse sort {$CommandsFrequency{$a} <=> $CommandsFrequency{$b}} keys %CommandsFrequency){ my $command_html; my $percentage = sprintf "%5.2f",$CommandsFrequency{$command}*100/$total_commands; if ($percentage < 0.5) { my $hint = make_comment($command); $command_html = "$command"; $command_html = "<span title='$hint' class='with_hint'>$command_html</span>" if $hint; $command_html = "<span class='without_hint'>$command_html</span>" if not $hint; my $command_html = "<tt>$command_html</tt>"; $Stat{RareCommands} .= $command_html."<sub><font size='-2'>".$CommandsFrequency{$command}."</font></sub> , "; } else { my $hint = make_comment($command); $command_html = "$command"; $command_html = "<span title='$hint' class='with_hint'>$command_html</span>" if $hint; $command_html = "<span class='without_hint'>$command_html</span>" if not $hint; my $command_html = "<tt>$command_html</tt>"; $percentage = sprintf "%5.2f",$percentage; $Stat{CommandsFrequency} .= "<tr><td>".$command_html."</td><td>".$CommandsFrequency{$command}."</td>". "<td>|".("="x int($CommandsFrequency{$command}*100/$total_commands))."| $percentage%</td></tr>"; } } $Stat{CommandsFrequency} = "<table>".$Stat{CommandsFrequency}."</table>"; $Stat{RareCommands} =~ s/, $// if $Stat{RareCommands}; } my $result = q(); for my $stat (@StatOrder) { next unless $Stat{"$stat"}; $result .= "<tr valign='top'><td width='300'>".$StatNames{"$stat"}."</td><td>".$Stat{"$stat"}."</td></tr>" } $result = "<table>$result</table>" . "<font size='-2'>____<br/>*) Интервалы неактивности длительностью " . ($Config{stat_inactivity_interval}/60) . " минут и более не учитываются</font></br>"; return $result; } sub collapse_list($) { my $res = ""; for my $elem (@{$_[0]}) { if (ref $elem eq "ARRAY") { $res .= "<ul>".collapse_list($elem)."</ul>"; } else { $res .= "<li>".$elem."</li>"; } } return $res; } sub init_variables { $Html_Help = <<HELP; Для того чтобы использовать LiLaLo, не нужно знать ничего особенного: всё происходит само собой. Однако, чтобы ведение и последующее использование журналов было как можно более эффективным, желательно иметь в виду следующее: <ol> <li><p> В журнал автоматически попадают все команды, данные в любом терминале системы. </p></li> <li><p> Для того чтобы убедиться, что журнал на текущем терминале ведётся, и команды записываются, дайте команду w. В поле WHAT, соответствующем текущему терминалу, должна быть указана программа script. </p></li> <li><p> Если код завершения команды равен нулю, команда была выполнена без ошибок. Команды, код завершения которых отличен от нуля, выделяются цветом. <table> <tr class='command'> <td class='script'> <pre class='wrong_cline'> \$ l s-l</pre> <pre class='wrong_output'>bash: l: command not found </pre> </td> </tr> </table> <br/> </p></li> <li><p> Команды, ход выполнения которых был прерван пользователем, выделяются цветом. <table> <tr class='command'> <td class='script'> <pre class='interrupted_cline'> \$ find / -name abc</pre> <pre class='interrupted_output'>find: /home/devi-orig/.gnome2: Keine Berechtigung find: /home/devi-orig/.gnome2_private: Keine Berechtigung find: /home/devi-orig/.nautilus/metafiles: Keine Berechtigung find: /home/devi-orig/.metacity: Keine Berechtigung find: /home/devi-orig/.inkscape: Keine Berechtigung ^C </pre> </td> </tr> </table> <br/> </p></li> <li><p> Команды, выполненные с привилегиями суперпользователя, выделяются слева красной чертой. <table> <tr class='command'> <td class='script'> <pre class='_root_cline'> # id</pre> <pre class='_root_output'> uid=0(root) gid=0(root) Gruppen=0(root) </pre> </td> </tr> </table> <br/> </p></li> <li><p> Изменения, внесённые в текстовый файл с помощью редактора, запоминаются и показываются в журнале в формате ed. Строки, начинающиеся символом "<", удалены, а строки, начинающиеся символом ">" -- добавлены. <table> <tr class='command'> <td class='script'> <pre class='cline'> \$ vi ~/.bashrc</pre> <table><tr><td width='5'/><td class='diff'><pre>2a3,5 > if [ -f /usr/local/etc/bash_completion ]; then > . /usr/local/etc/bash_completion > fi </pre></td></tr></table></td> </tr> </table> <br/> </p></li> <li><p> Для того чтобы изменить файл в соответствии с показанными в диффшоте изменениями, можно воспользоваться командой patch. Нужно скопировать изменения, запустить программу patch, указав в качестве её аргумента файл, к которому применяются изменения, и всавить скопированный текст: <table> <tr class='command'> <td class='script'> <pre class='cline'> \$ patch ~/.bashrc</pre> </td> </tr> </table> В данном случае изменения применяются к файлу ~/.bashrc </p></li> <li><p> Для того чтобы получить краткую справочную информацию о команде, нужно подвести к ней мышь. Во всплывающей подсказке появится краткое описание команды. </p></li> <li><p> Время ввода команды, показанное в журнале, соответствует времени <i>начала ввода командной строки</i>, которое равно тому моменту, когда на терминале появилось приглашение интерпретатора </p></li> <li><p> Имя терминала, на котором была введена команда, показано в специальном блоке. Этот блок показывается только в том случае, если терминал текущей команды отличается от терминала предыдущей. </p></li> <li><p> Вывод не интересующих вас в настоящий момент элементов журнала, таких как время, имя терминала и других, можно отключить. Для этого нужно воспользоваться <a href='#visibility_form'>формой управления журналом</a> вверху страницы. </p></li> <li><p> Небольшие комментарии к командам можно вставлять прямо из командной строки. Комментарий вводится прямо в командную строку, после символов #^ или #v. Символы ^ и v показывают направление выбора команды, к которой относится комментарий: ^ - к предыдущей, v - к следующей. Например, если в командной строке было введено: <pre class='cline'> \$ whoami </pre> <pre class='output'> user </pre> <pre class='cline'> \$ #^ Интересно, кто я? </pre> в журнале это будет выглядеть так: <pre class='cline'> \$ whoami </pre> <pre class='output'> user </pre> <table class='note'><tr><td width='100%' class='note_text'> <tr> <td> Интересно, кто я?<br/> </td></tr></table> </p></li> <li><p> Если комментарий содержит несколько строк, его можно вставить в журнал следующим образом: <pre class='cline'> \$ whoami </pre> <pre class='output'> user </pre> <pre class='cline'> \$ cat > /dev/null #^ Интересно, кто я? </pre> <pre class='output'> Программа whoami выводит имя пользователя, под которым мы зарегистрировались в системе. - Она не может ответить на вопрос о нашем назначении в этом мире. </pre> В журнале это будет выглядеть так: <table> <tr class='command'> <td class='script'> <pre class='cline'> \$ whoami</pre> <pre class='output'>user </pre> <table class='note'><tr><td class='note_title'>Интересно, кто я?</td></tr><tr><td width='100%' class='note_text'> Программа whoami выводит имя пользователя, под которым<br/> мы зарегистрировались в системе.<br/> <br/> Она не может ответить на вопрос о нашем назначении<br/> в этом мире.<br/> </td></tr></table> </td> </tr> </table> Для разделения нескольких абзацев между собой используйте символ "-", один в строке. <br/> </p></li> <li><p> Комментарии, не относящиеся непосредственно ни к какой из команд, добавляются точно таким же способом, только вместо симолов #^ или #v нужно использовать символы #= </p></li> </ol> HELP $Html_About = <<ABOUT; <p> LiLaLo (L3) расшифровывается как Live Lab Log.<br/> Программа разработана для повышения эффективности обучения Unix/Linux-системам.<br/> (c) Игорь Чубин, 2004-2005<br/> </p> ABOUT $Html_About.='$Id$ </p>'; $Html_JavaScript = <<JS; function getElementsByClassName(Class_Name) { var Result=new Array(); var All_Elements=document.all || document.getElementsByTagName('*'); for (i=0; i<All_Elements.length; i++) if (All_Elements[i].className==Class_Name) Result.push(All_Elements[i]); return Result; } function ShowHide (name) { elements=getElementsByClassName(name); for(i=0; i<elements.length; i++) if (elements[i].style.display == "none") elements[i].style.display = ""; else elements[i].style.display = "none"; //if (elements[i].style.visibility == "hidden") // elements[i].style.visibility = "visible"; //else // elements[i].style.visibility = "hidden"; } function filter_by_output(text) { var jjj=0; elements=getElementsByClassName('command'); for(i=0; i<elements.length; i++) { subelems = elements[i].getElementsByTagName('pre'); for(j=0; j<subelems.length; j++) { if (subelems[j].className = 'output') { var str = new String(subelems[j].nodeValue); if (jjj != 1) { alert(str); jjj=1; } if (str.indexOf(text) >0) subelems[j].style.display = "none"; else subelems[j].style.display = ""; } } } } JS %Search_Machines = ( "google" => { "query" => "http://www.google.com/search?q=" , "icon" => "$Config{frontend_google_ico}" }, "freebsd" => { "query" => "http://www.freebsd.org/cgi/man.cgi?query=", "icon" => "$Config{frontend_freebsd_ico}" }, "linux" => { "query" => "http://man.he.net/?topic=", "icon" => "$Config{frontend_linux_ico}"}, "opennet" => { "query" => "http://www.opennet.ru/search.shtml?words=", "icon" => "$Config{frontend_opennet_ico}"}, "local" => { "query" => "http://www.freebsd.org/cgi/man.cgi?query=", "icon" => "$Config{frontend_local_ico}" }, ); %Elements_Visibility = ( "note" => "замечания", "diff" => "редактор", "time" => "время", "ttychange" => "терминал", "wrong_output wrong_cline wrong_root_output wrong_root_cline" => "команды с ошибками", "interrupted_output interrupted_cline interrupted_root_output interrupted_root_cline" => "прерванные команды", "tab_completion_output tab_completion_cline" => "продолжение с помощью tab" ); @Day_Name = qw/ Воскресенье Понедельник Вторник Среда Четверг Пятница Суббота /; @Month_Name = qw/ Январь Февраль Март Апрель Май Июнь Июль Август Сентябрь Октябрь Ноябрь Декабрь /; @Of_Month_Name = qw/ Января Февраля Марта Апреля Мая Июня Июля Августа Сентября Октября Ноября Декабря /; } # Временно удалённый код # Возможно, он не понадобится уже никогда sub search_by { my $sm = shift; my $topic = shift; $topic =~ s/ /+/; return "<a href='". $Search_Machines{$sm}->{"query"}."$topic'><img width='16' height='16' src='". $Search_Machines{$sm}->{"icon"}."' border='0'/></a>"; }