Spec-Zone.ru › Perl 5.36

perldebtut

СОДЕРЖАНИЕ

  • ИМЯ
  • ОПИСАНИЕ
  • use strict
  • Просмотр данных и -w и v
  • help
  • Пошаговый просмотр кода
  • Заполнитель для a, w, t, T
  • РЕГУЛЯРНЫЕ ВЫРАЖЕНИЯ
  • СОВЕТЫ ПО ВЫВОДУ
  • CGI
  • GUIs
  • РЕЗЮМЕ
  • СМОТРИТЕ ТАКЖЕ
  • АВТОР
  • СОАВТОРЫ

ИМЯ

perldebtut - Руководство по отладке Perl

ОПИСАНИЕ

Очень краткое введение в использование отладчика Perl и указатель на существующие, более глубокие источники информации по отладке программ Perl.

Есть огромное количество людей, которые, похоже, ничего не знают о том, как использовать отладчик Perl, хотя они ежедневно используют этот язык. Это для них.

use strict

Прежде всего, есть несколько вещей, которые вы можете сделать, чтобы значительно упростить отладку программ Perl, не используя отладчик вообще. Для демонстрации вот простой скрипт с именем «hello» с проблемой:

#!/usr/bin/perl

$var1 = 'Hello World'; # always wanted to do that :-)
$var2 = "$varl\n";

print $var2; 
exit;

Хотя этот скрипт компилируется и выполняется без проблем, он, вероятно, не выполнит ожидаемые действия, а именно, не выведет «Hello World\n»; Вместо этого он выполнит ровно то, что ему было сказано, компьютеры иногда так устроены. То есть он выведет символ новой строки, и вы получите то, что выглядит как пустая строка. Похоже, что есть 2 переменных, тогда как (из-за опечатки) их на самом деле 3:

$var1 = 'Hello World';
$varl = undef;
$var2 = "\n";

Чтобы поймать этот тип проблемы, мы можем принудительно объявить каждую переменную перед использованием, включив модуль strict, добавив 'use strict;' после первой строки скрипта.

Теперь, когда вы запустите его, Perl пожалуется на 3 необъявленные переменные, и мы получим четыре сообщения об ошибках, потому что одна переменная упоминается дважды:

Global symbol "$var1" requires explicit package name at ./t1 line 4.
Global symbol "$var2" requires explicit package name at ./t1 line 5.
Global symbol "$varl" requires explicit package name at ./t1 line 5.
Global symbol "$var2" requires explicit package name at ./t1 line 7.
Execution of ./hello aborted due to compilation errors.     

Прекрасно! А для исправления этого мы явно объявляем все переменные, и теперь наш скрипт выглядит так:

#!/usr/bin/perl
use strict;

my $var1 = 'Hello World';
my $varl = undef;
my $var2 = "$varl\n";

print $var2; 
exit;

Затем мы делаем (всегда хорошая идея) проверку синтаксиса, прежде чем снова попытаться запустить его:

> perl -c hello
hello syntax OK 

И теперь, когда мы запустим его, мы все еще получим «\n», но, по крайней мере, мы знаем почему. Просто компиляция этого скрипта выявила переменную '$varl' (с буквой 'l'), и просто изменение $varl на $var1 решит проблему.

Просмотр данных и -w и v

Хорошо, но как насчет того, когда вы хотите действительно увидеть свои данные, что содержится в динамической переменной, сразу перед ее использованием?

#!/usr/bin/perl 
use strict;

my $key = 'welcome';
my %data = (
        'this' => qw(that), 
        'tom' => qw(and jerry),
        'welcome' => q(Hello World),
        'zip' => q(welcome),
);
my @data = keys %data;

print "$data{$key}\n";
exit;                               

Выглядит нормально, после проверки синтаксиса (perl -c scriptname), мы запускаем его, и все, что мы получаем, это пустая строка! Хммм.

Один из распространённых подходов к отладке в этом случае — щедро разбросать несколько операторов print, чтобы добавить проверку непосредственно перед выводом данных и после:

print "All OK\n" if grep($key, keys %data);
print "$data{$key}\n";
print "done: '$data{$key}'\n";

И попробуйте снова:

> perl data
All OK     

done: ''

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

> perl -d data 
Default die handler restored.

Loading DB routines from perl5db.pl version 1.07
Editor support available.

Enter h or `h h' for help, or `man perldebug' for more help.

main::(./data:4):     my $key = 'welcome';   

Теперь мы запустили встроенный отладчик Perl в нашем скрипте. Он остановился на первой строке исполняемого кода и ждет ввода.

Прежде чем мы пойдем дальше, вам нужно узнать, как выйти из отладчика: используйте просто букву 'q', а не слова 'quit' или 'exit':

DB<1> q
>

Вот и все, вы снова вернулись к своему рабочему столу.

help

Запустите отладчик на вашем скрипте, и мы рассмотрим справочное меню. Есть несколько способов вызова справки: простой 'h' получит краткое руководство, '|h' (pipe-h) передаст справку в ваш просмотрщик страниц (вероятно, 'more' или 'less'), а 'h h' (h-space-h) покажет весь экран справки. Вот краткая страница справки:

D1h

List/search source lines:               Control script execution:
 l [ln|sub]  List source code            T           Stack trace
 - or .      List previous/current line  s [expr]    Single step
                                                              [in expr]
 v [line]    View around line            n [expr]    Next, steps over
                                                                   subs
 f filename  View source in file         <CR/Enter>  Repeat last n or s
 /pattern/ ?patt?   Search forw/backw    r           Return from
                                                             subroutine
 M           Show module versions        c [ln|sub]  Continue until
                                                               position
Debugger controls:                       L           List break/watch/
                                                                actions
 o [...]     Set debugger options        t [expr]    Toggle trace
                                                           [trace expr]
 <[<]|{[{]|>[>] [cmd] Do pre/post-prompt b [ln|event|sub] [cnd] Set
                                                             breakpoint
 ! [N|pat]   Redo a previous command     B ln|*      Delete a/all
                                                            breakpoints
 H [-num]    Display last num commands   a [ln] cmd  Do cmd before line
 = [a val]   Define/list an alias        A ln|*      Delete a/all
                                                                actions
 h [db_cmd]  Get help on command         w expr      Add a watch
                                                             expression
 h h         Complete help page          W expr|*    Delete a/all watch
                                                                  exprs
 |[|]db_cmd  Send output to pager        ![!] syscmd Run cmd in a
                                                             subprocess
 q or ^D     Quit                        R           Attempt a restart
Data Examination:     expr     Execute perl code, also see: s,n,t expr
 x|m expr       Evals expr in list context, dumps the result or lists
                                                               methods.
 p expr         Print expression (uses script's current package).
 S [[!]pat]     List subroutine names [not] matching pattern
 V [Pk [Vars]]  List Variables in Package.  Vars can be ~pattern or
                                                              !pattern.
 X [Vars]       Same as "V current_package [Vars]".
 y [n [Vars]]   List lexicals in higher scope <n>.  Vars same as V.
For more help, type h cmd_letter, or run man perldebug for all docs. 

Более запутанных опций, чем можно и представить! Это не так плохо, как кажется, и очень полезно знать больше о них всех, и это интересно!

Есть несколько полезных, которые нужно знать сразу же. Вы бы не подумали, что мы используем какие-либо библиотеки, но 'M' покажет, какие модули в настоящее время загружены, и их номер версии, в то время как 'm' покажет методы, а 'S' покажет все подпрограммы (по шаблону), как показано ниже. 'V' и 'X' показывают переменные в программе по области видимости пакета и могут быть ограничены шаблоном.

DB<2>S str 
dumpvar::stringify
strict::bits
strict::import
strict::unimport  

Использование 'X' и его аналогов требует, чтобы вы не использовали идентификаторы типов ($@%), а только 'имя':

DM<3>X ~err
FileHandle(stderr) => fileno(2)    

Помните, что мы находимся в нашей небольшой программе с проблемой, нам нужно посмотреть, где мы находимся и как выглядят наши данные. Прежде всего, давайте посмотрим на код в нашем текущем положении (в данном случае первая строка кода) с помощью 'v':

DB<4> v
1       #!/usr/bin/perl
2:      use strict;
3
4==>    my $key = 'welcome';
5:      my %data = (
6               'this' => qw(that),
7               'tom' => qw(and jerry),
8               'welcome' => q(Hello World),
9               'zip' => q(welcome),
10      );                                 

В строке 4 есть полезная подсказка, которая показывает, где вы сейчас находитесь. Чтобы увидеть больше кода, снова введите 'v':

DB<4> v
8               'welcome' => q(Hello World),
9               'zip' => q(welcome),
10      );
11:     my @data = keys %data;
12:     print "All OK\n" if grep($key, keys %data);
13:     print "$data{$key}\n";
14:     print "done: '$data{$key}'\n";
15:     exit;      

А если бы вы хотели перечислить строку 5 снова, введите 'l 5' (обратите внимание на пробел):

DB<4> l 5
5:      my %data = (

В этом случае особо ничего и нет, но, конечно же, обычно есть много вещей, которые нужно просмотреть, и 'l' может быть очень полезным. Чтобы переместить свой обзор на строку, которую мы собираемся выполнить, введите одиночную точку '.':

DB<5> .
main::(./data_a:4):     my $key = 'welcome';  

Отображаемая строка — это строка, которая должна быть выполнена следующей, она ещё не выполнена. Поэтому, хотя мы можем вывести переменную с буквой 'p', на этом этапе мы получим пустое (неопределенное) значение. Что нам нужно сделать, так это перейти к следующему исполняемому оператору с помощью 's':

DB<6> s
main::(./data_a:5):     my %data = (
main::(./data_a:6):             'this' => qw(that),
main::(./data_a:7):             'tom' => qw(and jerry),
main::(./data_a:8):             'welcome' => q(Hello World),
main::(./data_a:9):             'zip' => q(welcome),
main::(./data_a:10):    );   

Теперь мы можем взглянуть на первую переменную ($key):

DB<7> p $key 
welcome 

строка 13 — это место действия, поэтому давайте продолжим до неё с помощью буквы 'c', которая, кстати, вставляет временный контрольный пункт в указанную строку или подпрограмму:

DB<8> c 13
All OK
main::(./data_a:13):    print "$data{$key}\n";

Мы прошли нашу проверку (где было напечатано «Все в порядке») и остановились незадолго до самого нашего задания. Мы можем попробовать вывести несколько переменных, чтобы посмотреть, что происходит:

DB<9> p $data{$key}

В них не так много, давайте взглянем на наш хеш:

DB<10> p %data
Hello Worldziptomandwelcomejerrywelcomethisthat 

DB<11> p keys %data
Hello Worldtomwelcomejerrythis  

Ну, это не очень удобно читать, и с помощью полезного руководства (h h), команда 'x' выглядит многообещающе:

DB<12> x %data
0  'Hello World'
1  'zip'
2  'tom'
3  'and'
4  'welcome'
5  undef
6  'jerry'
7  'welcome'
8  'this'
9  'that'     

Это не очень помогает, несколько приветствий там, но нет указаний, какие являются ключами, а какие значениями, это просто выгруженная массивная запись, и в данном случае не очень полезная. Хитрость здесь — использовать ссылку на структуру данных:

DB<13> x \%data
0  HASH(0x8194bc4)
   'Hello World' => 'zip'
   'jerry' => 'welcome'
   'this' => 'that'
   'tom' => 'and'
   'welcome' => undef  

Ссылка действительно выводится, и мы наконец-то можем увидеть, с чем имеем дело. Наша кавычка была совершенно правильной, но неправильной для наших целей, при этом «и Джерри» рассматривались как 2 отдельных слова, а не как фраза, что выводило даже спаренную структуру хеша из строя.

Переключатель '-w' сообщил бы нам об этом, если бы мы его использовали вначале, и сэкономил бы нам много хлопот:

> perl -w data
Odd number of elements in hash assignment at ./data line 5.    

Мы исправляем нашу кавычку: 'tom' => q(and jerry) и запускаем её снова, на этот раз мы получаем ожидаемый вывод:

> perl -w data
Hello World

Пока мы здесь, посмотрите повнимательнее на команду 'x', она очень полезна и умело выводит вложенные ссылки, полные объекты, частичные объекты — почти всё, что вы в неё бросите:

Давайте быстро создадим объект и выведем его, для начала запустим отладчик: он хочет что-то получить из стандартного ввода, поэтому мы дадим ему что-нибудь не очень значимое, ноль:

> perl -de 0
Default die handler restored.

Loading DB routines from perl5db.pl version 1.07
Editor support available.

Enter h or `h h' for help, or `man perldebug' for more help.

main::(-e:1):   0

Теперь создадим объект на лету на нескольких строках (обратите внимание на обратную косую черту):

DB<1> $obj = bless({'unique_id'=>'123', 'attr'=> \
cont:  {'col' => 'black', 'things' => [qw(this that etc)]}}, 'MY_class')

И давайте взглянем на него:

       DB<2> x $obj
0  MY_class=HASH(0x828ad98)
               'attr' => HASH(0x828ad68)
       'col' => 'black'
       'things' => ARRAY(0x828abb8)
               0  'this'
               1  'that'
               2  'etc'
               'unique_id' => 123       
       DB<3>

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

DB<3> @data = qw(this that the other atheism leather theory scythe)

DB<4> p 'saw -> '.($cnt += map { print "\t:\t$_\n" } grep(/the/, sort @data))
atheism
leather
other
scythe
the
theory
saw -> 6

Если вы хотите увидеть историю команд, введите 'H':

DB<5> H
4: p 'saw -> '.($cnt += map { print "\t:\t$_\n" } grep(/the/, sort @data))
3: @data = qw(this that the other atheism leather theory scythe)
2: x $obj
1: $obj = bless({'unique_id'=>'123', 'attr'=>
{'col' => 'black', 'things' => [qw(this that etc)]}}, 'MY_class')
DB<5>

А если вы хотите повторить предыдущую команду, используйте восклицательный знак: '!':

DB<5> !4
p 'saw -> '.($cnt += map { print "$_\n" } grep(/the/, sort @data))
atheism
leather
other
scythe
the
theory
saw -> 12

Для получения дополнительной информации о ссылках см. perlref и perlreftut

Пошаговый просмотр кода

Вот простая программа, которая преобразует Цельсий в Фаренгейт, у неё тоже есть проблема:

#!/usr/bin/perl
use v5.36;

my $arg = $ARGV[0] || '-c20';

if ($arg =~ /^\-(c|f)((\-|\+)*\d+(\.\d+)*)$/) {
       my ($deg, $num) = ($1, $2);
       my ($in, $out) = ($num, $num);
       if ($deg eq 'c') {
               $deg = 'f';
               $out = &c2f($num);
       } else {
               $deg = 'c';
               $out = &f2c($num);
       }
       $out = sprintf('%0.2f', $out);
       $out =~ s/^((\-|\+)*\d+)\.0+$/$1/;
       print "$out $deg\n";
} else {
       print "Usage: $0 -[c|f] num\n";
}
exit;

sub f2c {
       my $f = shift;
       my $c = 5 * $f - 32 / 9;
       return $c;
}

sub c2f {
       my $c = shift;
       my $f = 9 * $c / 5 + 32;
       return $f;
}

По какой-то причине преобразование Фаренгейта в Цельсий не возвращает ожидаемый результат. Вот что он делает:

> temp -c0.72
33.30 f

> temp -f33.3
162.94 c

Не очень последовательно! Мы установим контрольный пункт в коде вручную и запустим его под отладчиком, чтобы посмотреть, что происходит. Контрольный пункт — это флажок, до которого отладчик будет работать без остановок, когда он достигнет контрольного пункта, выполнение остановится, и будет предложен ввод для дальнейшего взаимодействия. В обычном использовании эти команды отладчика игнорируются полностью и безопасны — хотя и немного громоздки — для использования в рабочем коде.

my ($in, $out) = ($num, $num);
$DB::single=2; # insert at line 9!
if ($deg eq 'c') 
        ...

> perl -d temp -f33.3
Default die handler restored.

Loading DB routines from perl5db.pl version 1.07
Editor support available.

Enter h or `h h' for help, or `man perldebug' for more help.

main::(temp:4): my $arg = $ARGV[0] || '-c100';     

Мы просто продолжим выполнение до нашего заданного контрольного пункта с помощью 'c':

DB<1> c
main::(temp:10):                if ($deg eq 'c') {   

За ним следует команда просмотра, чтобы увидеть, где мы находимся:

DB<1> v
7:              my ($deg, $num) = ($1, $2);
8:              my ($in, $out) = ($num, $num);
9:              $DB::single=2;
10==>           if ($deg eq 'c') {
11:                     $deg = 'f';
12:                     $out = &c2f($num);
13              } else {
14:                     $deg = 'c';
15:                     $out = &f2c($num);
16              }                             

И вычисление, чтобы показать текущие значения, которые мы используем:

DB<1> p $deg, $num
f33.3

Мы можем установить другой контрольный пункт в любой строке, начинающейся с двоеточия, мы используем строку 17, так как это как раз когда мы выходим из подпрограммы, и нам нужно будет приостановиться там позже:

DB<2> b 17

Обратной связи от этого нет, но вы можете увидеть установленные контрольные точки, используя команду 'L':

DB<3> L
temp:
        17:            print "$out $deg\n";
        break if (1)     

Обратите внимание, что для удаления контрольной точки используется 'B'.

Теперь мы продолжим выполнение в нашу подпрограмму, на этот раз вместо номера строки мы будем использовать имя подпрограммы, за которым следует уже знакомая 'v':

DB<3> c f2c
main::f2c(temp:30):             my $f = shift;  

DB<4> v
24:     exit;
25
26      sub f2c {
27==>           my $f = shift;
28:             my $c = 5 * $f - 32 / 9; 
29:             return $c;
30      }
31
32      sub c2f {
33:             my $c = shift;   

Обратите внимание, что если между нами и строкой 29 была вызов подпрограммы, и мы хотели бы шаг за шагом через неё, мы могли бы использовать команду 's', а чтобы перепрыгнуть через неё, мы использовали бы 'n', что выполнило бы подпрограмму, но не спустилось в неё для проверки. В этом случае мы просто продолжим выполнение до строки 29:

DB<4> c 29  
main::f2c(temp:29):             return $c;

И посмотрим на возвращаемое значение:

DB<5> p $c
162.944444444444

Это совсем неверный ответ, но сумма выглядит правильно. Интересно, связано ли это с приоритетом оператора? Мы попробуем несколько других возможностей с нашей суммой:

DB<6> p (5 * $f - 32 / 9)
162.944444444444

DB<7> p 5 * $f - (32 / 9) 
162.944444444444

DB<8> p (5 * $f) - 32 / 9
162.944444444444

DB<9> p 5 * ($f - 32) / 9
0.722222222222221

:-) это уже больше похоже на правду! Хорошо, теперь мы можем установить нашу возвращаемую переменную и вернёмся из подпрограммы с помощью 'r':

DB<10> $c = 5 * ($f - 32) / 9

DB<11> r
scalar context return from main::f2c: 0.722222222222221

Выглядит хорошо, давайте просто продолжим выполнение до конца скрипта:

DB<12> c
0.72 c 
Debugged program terminated.  Use q to quit or R to restart,
use O inhibit_exit to avoid stopping after program termination,
h q, h R or h O to get additional info.   

Быстрое исправление строки с ошибкой (добавление недостающих скобок) в фактической программе, и мы закончили.

Заполнитель для a, w, t, T

Действия, отслеживание переменных, трассировки стека и т.д.: в списке дел.

a 

w 

t 

T

РЕГУЛЯРНЫЕ ВЫРАЖЕНИЯ

Хотели ли вы узнать, как выглядит регулярное выражение? Вам потребуется Perl, скомпилированный со флагом DEBUGGING:

> perl -Dr -e '/^pe(a)*rl$/i'
Compiling REx `^pe(a)*rl$'
size 17 first at 2
rarest char
 at 0
   1: BOL(2)
   2: EXACTF <pe>(4)
   4: CURLYN[1] {0,32767}(14)
   6:   NOTHING(8)
   8:   EXACTF <a>(0)
  12:   WHILEM(0)
  13: NOTHING(14)
  14: EXACTF <rl>(16)
  16: EOL(17)
  17: END(0)
floating `'$ at 4..2147483647 (checking floating) stclass
  `EXACTF <pe>' anchored(BOL) minlen 4
Omitting $` $& $' support.

EXECUTING...

Freeing REx: `^pe(a)*rl$'

Вы действительно хотели это узнать? :-) Для получения более подробной информации о работе с регулярными выражениями ознакомьтесь со страницами perlre, perlretut, а чтобы расшифровать загадочные метки (BOL и CURLYN и т. д. выше), обратитесь к perldebguts.

СОВЕТЫ ПО ВЫВОДУ

Чтобы получить все данные из вашего файла журнала ошибок и не пропустить сообщения из-за буферизации операционной системы, добавьте в начале своего скрипта строку:

$|=1;   

Чтобы следить за концом динамически растущего файла журнала (из командной строки):

tail -f $error_log

Оборачивание всех вызовов die в обработчик может быть полезным, чтобы увидеть, как и откуда они вызываются; perlvar содержит дополнительную информацию:

BEGIN { $SIG{__DIE__} = sub { require Carp; Carp::confess(@_) } }

Различные полезные методы перенаправления файловых дескрипторов STDOUT и STDERR описаны в perlopentut и perlfaq8.

CGI

Вот быстрый совет для всех программистов CGI, которые не могут понять, как обойти приглашение «ожидание ввода» при запуске скрипта CGI из командной строки. Попробуйте следующее:

> perl -d my_cgi.pl -nodebug 

Конечно, CGI и perlfaq9 расскажут вам больше.

Графические интерфейсы

Командная строка тесно интегрирована с расширением emacs, а также есть интерфейс vi.

Однако вам не обязательно всё делать в командной строке; есть несколько графических вариантов. Преимущество этих вариантов заключается в том, что вы можете навести курсором мыши на переменную, и в соответствующем окне или всплывающем окне появится её содержимое. Больше не нужно утомительно печатать 'x $varname' :-)

В частности, обратите внимание на следующие:

ptkdb — обертка perlTK для встроенного отладчика

ddd — отладчик для отображения данных

PerlDevKit и PerlBuilder — специфичные для NT

Примечание: (было бы приятно получить больше информации об этих и других инструментах).

РЕЗЮМЕ

Мы увидели, как применять лучшие практики кодирования с помощью use strict и -w. Мы можем запустить perl-отладчик perl -d scriptname, чтобы просмотреть данные изнутри perl-отладчика с помощью команд p и x. Вы можете пройтись по вашему коду, установить точки останова с помощью b, перейти по коду с помощью s или n, продолжить с помощью c и вернуться из подпрограммы с помощью r. Довольно интуитивно, когда вы разобрались.

Конечно, есть ещё много всего, что можно узнать; мы лишь слегка коснулись поверхности. Лучший способ узнать больше — использовать perldoc, чтобы узнать больше о языке, прочитать справку онлайн (perldebug — вероятно, следующее место), и, конечно, экспериментировать.

СМОТРИТЕ ТАКЖЕ

perldebug, perldebguts, perl5db.pl, perldiag, perlrun

АВТОР

Richard Foley <richard.foley@rfi.net> Авторское право (c) 2000

СОАВТОРЫ

Разные люди внесли полезные предложения и дополнения, в частности:

Ronald J Kimball <rjk@linguist.dartmouth.edu>

Hugo van der Sanden <hv@crypt0.demon.co.uk>

Peter Scott <Peter@PSDT.com>

© 1993–2021 Larry Wall and others
Licensed under the GNU General Public License version 1 or later, or the Artistic License.
The Perl logo is a trademark of the Perl Foundation.
https://perldoc.perl.org/5.36.0/perldebtut

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API