如何通过命令行开关为我的Perl程序启用调试模式?

时间:2010-05-22 04:06:50

标签: perl command-line debugging

我正在以“头脑优先”的方式学习Perl。我绝对是这种语言的新手:

我正在尝试从CLI进行debug_mode切换,可以通过“打开和关闭”切换某些子例程来控制我的脚本的工作方式。

以下是我到目前为止所得到的:

#!/usr/bin/perl -s -w

# purpose : make subroutine execution optional,
# which is depending on a CLI switch flag

use strict;
use warnings;

use constant DEBUG_VERBOSE             => "v";
use constant DEBUG_SUPPRESS_ERROR_MSGS   => "s";
use constant DEBUG_IGNORE_VALIDATION     => "i";
use constant DEBUG_SETPPING_COMPUTATION  => "c";

our ($debug_mode);

mainMethod();

sub mainMethod # ()
{
    if(!$debug_mode)
    {
        print "debug_mode is OFF\n";
    }
    elsif($debug_mode)
    {
        print "debug_mode is ON\n";
    }
    else
    {
        print "OMG!\n";
        exit -1;
    }

    checkArgv();
    printErrorMsg("Error_Code_123", "Parsing Error at...");
    verbose();
}

sub checkArgv #()
{
    print ("Number of ARGV : ".(1 + $#ARGV)."\n");
}

sub printErrorMsg # ($error_code, $error_msg, ..)
{
    if(defined($debug_mode) && !($debug_mode =~ DEBUG_SUPPRESS_ERROR_MSGS))
    {
        print "You can only see me if -debug_mode is NOT set".
          " to DEBUG_SUPPRESS_ERROR_MSGS\n";
        die("terminated prematurely...\n") and exit -1;
    }
}

sub verbose # ()
{
    if(defined($debug_mode) && ($debug_mode =~ DEBUG_VERBOSE))
    {
        print "Blah blah blah...\n";
    }
}

据我所知,至少它有效......

  1. -debug_mode开关不会干扰正常的ARGV
  2. 以下命令行有效:
    • ./ optional.pl
    • ./ optional.pl -debug_mode
    • ./ optional.pl -debug_mode = v
    • ./ optional.pl -debug_mode = s
  3. 但是,当多个debug_modes“混合”时,我感到困惑,例如:

    1. ./ optional.pl -debug_mode = sv
    2. ./ optional.pl -debug_mode = vs
    3. 我不明白为什么上面的代码行“神奇地起作用”。

      我看到“DEBUG_VERBOS”和“DEBUG_SUPPRESS_ERROR_MSGS”都适用于脚本,在这种情况下这很好。

      但是,如果有一些“冲突”的调试模式,我不知道如何设置“debug_modes的优先级”?

      另外,我不确定我的方法对于Perlists是否足够好,我希望我能朝着正确的方向前进。

      一个最大的问题是我现在把if语句放在里面 我的大部分子程序用于控制不同模式下的行为。这个可以吗? 有更优雅的方式吗?

      我知道必须有来自CPAN或其他地方的调试模块,但我想要一个真正的最小解决方案,它不依赖于除“默认”之外的任何其他模块。

      我无法控制执行此脚本的环境......

4 个答案:

答案 0 :(得分:6)

要处理命令行选项,请查看Getopt::Long。你会得到各种光滑的参数解析选项。

有许多模块可以处理日志记录。 Log4Perl是一个非常受欢迎的日志记录模块。

如果你真的,真的想通过避免CPAN来限制自己(这是一个坏主意),你可以很容易地将记录模块放在一起。

这是我为你砍的一个小东西。它需要测试和真实的文档等等。我还使用了一些高级技术,比如自定义import()方法。围绕我使用单个变量来存储整个应用程序的DEBUG设置也存在一些问题。但它的确有效。我在一个项目中使用了类似的模块,对它非常满意。

package QLOG;

use strict;
use warnings;
use Carp qw(croak);

our %DEBUG_OPTIONS;
our %VALID_DEBUG_OPTIONS;
our %DEBUG_CONFLICTS;

sub import {

    my $pkg = shift;
    my $target = caller();

    my %opts = @_;


    # Configure options

    croak "Must supply an array ref of valid modes"
       unless exists $opts{options};

    @VALID_DEBUG_OPTIONS{ @{$opts{options}} } = ();

    # Configure conflicts

    if( exists $opts{conflicts} ) {
        @DEBUG_CONFLICTS{ keys %{$opts{conflicts}} } 
            = values %{$opts{conflicts}}
    }

    # Export DEBUG method

    {   no strict 'refs';
        *{$target.'::DEBUG'} = \&DEBUG;
    }

    return;
}

sub DEBUG {
    my $mode = shift;

    croak "DEBUG mode undefined"
        unless defined $mode;

    return unless
        ( $mode eq 'ANY' and %DEBUG_OPTIONS )
        or exists $DEBUG_OPTIONS{$mode};

    warn "$_\n" for @_;

    return 1;
}


sub set_options {

    for my $opt ( @_ ) {
        die "Illegal option '$opt'"
           unless exists $VALID_DEBUG_OPTIONS{$opt};

        $DEBUG_OPTIONS{$opt}++;
    }

    return;
}

sub check_option_conflicts {

    for my $opt ( keys %DEBUG_OPTIONS ) {

        if (exists $DEBUG_CONFLICTS{$opt}) {

            for ( @{$DEBUG_CONFLICTS{$opt}} ) {

                die "Debug option $opt conflicts with $_" 
                    if exists $DEBUG_OPTIONS{$_} 
            }
        }
    }

    return;
}


1;

然后像这样使用它:

#!/usr/bin/perl 

use strict;
use warnings;


use Getopt::Long;

use QLOG
    options => [qw(
        VERBOSE
        SUPPRESS_ERROR_MSGS
        IGNORE_VALIDATION
        SETPPING_COMPUTATION
    )], 
    conflicts => {
        VERBOSE => [qw(
            SUPPRESS_ERROR_MSGS
            SETPPING_COMPUTATION
        )],
    };




process_args();

DEBUG VERBOSE => 'Command line data parsed.';

main();

### ---------------

sub main {

    DEBUG VERBOSE => 'BEGIN main()';

    if( DEBUG 'ANY' ) {
        print "debug_mode is ON\n";
    }
    else {
        print "debug_mode is OFF\n";
    }

    warn "Error which could be surpressed\n"
        unless DEBUG 'SUPPRESS_ERROR_MSGS';
}


# Get arguments and process flags like 'v' and 'sc' into strings specified
# in QLOG configuration above.
# This processing makes the nice DEBUG VERBOSE => 'blah'; syntax work.
sub process_args {

    # Use Getopt::Long to parse @ARGV

    my @debug_options;
    GetOptions (
        'debug-options=s@' => \@debug_options,
        'help'             => \&usage,
    ) or usage();

    # Convert option flags to strings.
    my %option_lut = qw(
        v  VERBOSE  
        s  SUPPRESS_ERROR_MSGS
        i  IGNORE_VALIDATION 
        c  SETPPING_COMPUTATION 
    );

    my @options = map {          # This chained map 
        exists $option_lut{$_}   #    looks in the lut for a flag
        ? $option_lut{$_}        #       translates it if found
        : $_                     #       or passes on the original if not.
    } 
    map {                        # Here we split 'cv' into ('c','v')
       split //
    } @debug_options;

    # Really should use Try::Tiny here.
    eval {    
        # Send a list of strings to QLOG
        # QLOG will make sure they are allowed.
        QLOG::set_options( @options );

        QLOG::check_option_conflicts(); 

        1;          # Ensure true value returned if no exception occurs.
    } or usage($@);

    return;
}

sub usage {

    my $message = shift || '';
    $message = '' if $message eq 'help';

    print <<"END";
$message

Use this proggy right.

END

    exit;
}

您可能希望添加一种方法来使您的调试消息可以抑制。

类似的东西:

sub SUPPRESSED_BY {
     my $mode = shift;

     return if exists $DEBUG_OPTIONS{$mode);

     return @_; 
}

导出符号,然后使用它:

DEBUG VERBOSE => SUPPRESSED_BY SUPPRESS_ERRORS => 'My message here';

将日志记录模块放在一起的容易程度导致可用的大量此类模块。有很多方法可以完成这项任务,并且在检测代码时需要有不同的变化,甚至更多。我甚至编写了一些日志模块来满足各种需求。

无论如何,当你首先潜入Perl时,这应该会给你一个严重的扣篮。

随意问我'到底是什么?'输入问题。我意识到我在向你扔了很多。

答案 1 :(得分:2)

根据你对Tore的回应,我修改了这个样本。

#!/usr/bin/perl 

use strict;
use warnings;

use Getopt::Long;

my $count_letters;
my $eat_beans;
my $count_beans;
my $data_file;

GetOptions (
    'count-letters' => \$count_letters,
    'count-beans'   => \$count_beans,
    'eat-beans'     => \$eat_beans,
    'data-file=s'   => \$data_file,
    'help'          => \&usage,
) or usage();

# Build code ref arrays.

my @validate_file =
  ( $count_beans   ? \&count_beans   : () ),
  ( $count_letters ? \&count_letters : () ),
  ;

my @validate_line = 
  ( $eat_beans     ? \&eat_beans     : () ),
  ;


process_file( $data_file, \@validate_line, \@validate_file );


sub process_file {
    my $file           = shift;
    my $validate_lines = shift;
    my $validate_file  = shift;

    open my $fh, '<', $file or die "$file : $!";

    my @data;
    while( my $line = $fh->readline ) {

        # Validate each line with each line validator

        $_->($line) or die 'Invalid line' 
            for @$validate_lines;

        push @data, $line;
    }

    # Validate the whole file with the each file validator.
    $_->(\@data) or die 'Invalid file' 
        for @$validate_file;
}

# Put real subs here:

sub eat_beans     { 1 }

sub count_beans   { 1 }
sub count_letters { 1 }

至于测试,你可能想把所有的验证子都放到一个模块中并使用普通的perl测试工具(参见Test :: Simple和Test :: More来开始)。

我喜欢通过一个瘦CLI解析器来构建我的应用程序,该解析器配置主模块中使用的主应用逻辑所使用的基础数据集。

这使得编写单元测试非常容易,以验证代码是否正常。

答案 2 :(得分:2)

回答这个问题:

  

我不明白为什么上面的代码行“神奇地起作用”。

原因是您正在使用正则表达式检查调试开关的值,如:

if(defined($debug_mode) && !($debug_mode =~ DEBUG_SUPPRESS_ERROR_MSGS))

所以如果你有:

$debug_mode = "sv"

并提醒:

use constant DEBUG_VERBOSE             => "v";
use constant DEBUG_SUPPRESS_ERROR_MSGS   => "s";

然后这两个都将评估为真:

$debug_mode =~ DEBUG_SUPPRESS_ERROR_MSGS;
$debug_mode =~ DEBUG_VERBOSE;

如果您想检查一个值,可以尝试:

if ($debug_mode eq DEBUG_SUPPRESS_ERROR_MSGS) {...}
if ($debug_mode eq DEBUG_VERBOSE) {...}

或者

if ($debug_mode =~ /\bDEBUG_SUPPRESS_ERROR_MSGS\b/) {...}
if ($debug_mode =~ /\bDEBUG_VERBOSE/b\) {...}

其中\b告诉正则表达式匹配单词边界。当然,如果你有$debug_mode ="s v",那么正则表达式也会评估为真。

答案 3 :(得分:1)

我认为你在这里遇到两个问题。首先,要处理更复杂的命令行解析,请使用核心Getopt::StdGetopt::Long模块而不是-s命令行开关。

第二个问题(我认为)是您在尝试启用调试模式时尝试跳过调试语句。我不知道有任何标准模块可以做到这一点,但它可以使用各种结构:

eval {  ...code block... } if($debug);

这并不意味着根据是否启用调试模式来改变程序的逻辑一定是个好主意。您的目标应该是限制“调试模式”以改变程序的输出而不是逻辑,或者您将花费很多时间来了解为什么它在调试模式下工作而不是在“生产模式”。