如何在 Perl 中获得调用堆栈列表?

在 Perl 脚本的当前位置之前,有没有一种方法可以访问(为了打印输出)子模块的列表到任意深度的子调用?

我需要更改一些 Perl 模块(。下午4时)。工作流从一个 Web 页面通过一个 cgi-script 启动,通过几个模块/对象传递输入,这些模块/对象以我需要使用数据的模块结尾。数据在某处发生了变化,我得找出是哪里。

39419 次浏览

你可以使用 StackTrace

use Devel::StackTrace;
my $trace = Devel::StackTrace->new;
print $trace->as_string; # like carp

它的行为类似于 Carp 的跟踪,但是您可以对帧进行更多的控制。

一个问题是引用是字符串化的,如果引用的值发生变化,您将看不到它。然而,您可以使用 PadWalker快速打印一些东西来打印出完整的数据(尽管它会非常庞大)。

调用者 可以做到这一点,尽管您可能需要更多的信息。

还有 Carp::confessCarp::cluck

Carp::longmess可以做你想做的,它是标准的。

use Carp qw<longmess>;
use Data::Dumper;
sub A { &B; }
sub B { &C; }
sub C { &D; }
sub D { &E; }


sub E {
# Uncomment below if you want to see the place in E
# local $Carp::CarpLevel = -1;
my $mess = longmess();
print Dumper( $mess );
}


A();
__END__
$VAR1 = ' at - line 14
main::D called at - line 12
main::C called at - line 10
main::B called at - line 8
main::A() called at - line 23
';

我想出了这个代课老师(现在可选的祝福动作!)

my $stack_frame_re = qr{
^                # Beginning of line
\s*              # Any number of spaces
( [\w:]+ )       # Package + sub
(?: [(] ( .*? ) [)] )? # Anything between two parens
\s+              # At least one space
called [ ] at    # "called" followed by a single space
\s+ ( \S+ ) \s+  # Spaces surrounding at least one non-space character
line [ ] (\d+)   # line designation
}x;


sub get_stack {
my @lines = split /\s*\n\s*/, longmess;
shift @lines;
my @frames
= map {
my ( $sub_name, $arg_str, $file, $line ) = /$stack_frame_re/;
my $ref =  { sub_name => $sub_name
, args     => [ map { s/^'//; s/'$//; $_ }
split /\s*,\s*/, $arg_str
]
, file     => $file
, line     => $line
};
bless $ref, $_[0] if @_;
$ref
}
@lines
;
return wantarray ? @frames : \@frames;
}

一个更漂亮的: PrettyTrace

use Devel::PrettyTrace;
bt;

这个代码工作 没有任何额外的模块。 只要在需要的地方包括它。

my $i = 1;
print STDERR "Stack Trace:\n";
while ( (my @call_details = (caller($i++))) ){
print STDERR $call_details[1].":".$call_details[2]." in function ".$call_details[3]."\n";
}

如果你不能使用(或者不想使用)非核心模块,这里有一个简单的子例程:

#!/usr/bin/perl
use strict;
use warnings;


sub printstack {
my ($package, $filename, $line, $subroutine, $hasargs, $wantarray, $evaltext, $is_require, $hints, $bitmask, $hinthash);
my $i = 1;
my @r;
while (@r = caller($i)) {
($package, $filename, $line, $subroutine, $hasargs, $wantarray, $evaltext, $is_require, $hints, $bitmask, $hinthash) = @r;
print "$filename:$line $subroutine\n";
$i++;
}
}


sub i {
printstack();
}


sub h {
i;
}


sub g {
h;
}


g;

它的产出如下:

/root/_/1.pl:21 main::i
/root/_/1.pl:25 main::h
/root/_/1.pl:28 main::g

或者一句俏皮话:

for (my $i = 0; my @r = caller($i); $i++) { print "$r[1]:$r[2] $r[3]\n"; }

您可以找到关于调用方 给你的文档。

移动 我的评论到一个答案:

  1. 以正确的方式安装 坦白的 < a href = “ https://cpan.metacpan.org/module/INSTALL.html”rel = “ nofollow noReferrer”>

    cpanm Devel::Confess
    
  2. Run with

    perl -d:Confess myscript.pl
    

On errors, this will show the whole call stack list.