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: '' После долгого разглядывания одного и того же куска кода и не видения сути дела какое-то время, мы выпьем чашку кофе и попробуем другой подход. То есть, мы вызовем подкрепление, предоставив Perl переключатель '-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. Мы исправляем наше цитирование: 'том' => q(и джери) и снова запускаем его, и на этот раз получаем ожидаемый результат:
> perl -w data
Hello World Пока мы здесь, давайте внимательнее посмотрим на команду 'x', она действительно полезна и будет весело выводить вложенные ссылки, полные объекты, частичные объекты - практически все, что вы бросите в нее:
Давайте создадим быстрый объект и распечатаем его, сначала запустим отладчик: он хочет ввод из STDIN, поэтому мы дадим ему что-то не обязывающее, ноль:
> 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 -w
use strict;
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. Быстрое исправление нарушающей строки (вставьте недостающие скобки) в самом программе, и всё готово.
Placeholder для 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, perldiag, perlrun
АВТОР
Richard Foley <richard.foley@rfi.net> Copyright (c) 2000
СОАВТОРЫ
Различные люди внесли полезные предложения и поправки, в частности:
Ronald J Kimball <rjk@linguist.dartmouth.edu>
Hugo van der Sanden <hv@crypt0.demon.co.uk>
Peter Scott <Peter@PSDT.com>
© 1993–2020 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.32.0/perldebtut