首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Perl:需要帮助将if-elsif-else转换为更简单的东西。

Perl:需要帮助将if-elsif-else转换为更简单的东西。
EN

Stack Overflow用户
提问于 2017-11-15 05:12:20
回答 3查看 263关注 0票数 2

我一直在阅读分派表,我对它们的工作方式有了大致的了解,但我在网上看到的东西和把这个概念应用到我最初编写的一些代码中遇到了一些困难,我最初写的代码是一堆难看的if-elsif-else语句。

我通过使用GetOpt::Long配置了选项解析,而这些选项又根据使用的选项在%OPTIONS哈希中设置了一个值。

以下面的代码为例。(用更详细的更新)

代码语言:javascript
复制
use     5.008008;
use     strict;
use     warnings;
use     File::Basename qw(basename);
use     Getopt::Long qw(HelpMessage VersionMessage :config posix_default require_order no_ignore_case auto_version auto_help);

my $EMPTY      => q{};

sub usage
{
    my $PROG = basename($0);
    print {*STDERR} $_ for @_;
    print {*STDERR} "Try $PROG --help for more information.\n";
    exit(1);
}

sub process_args
{
    my %OPTIONS;

    $OPTIONS{host}              = $EMPTY;
    $OPTIONS{bash}              = 0;
    $OPTIONS{nic}               = 0;
    $OPTIONS{nicName}           = $EMPTY;
    $OPTIONS{console}           = 0;
    $OPTIONS{virtual}           = 0;
    $OPTIONS{cmdb}              = 0;
    $OPTIONS{policyid}          = 0;
    $OPTIONS{showcompliant}     = 0;
    $OPTIONS{backup}            = 0;
    $OPTIONS{backuphistory}     = 0;
    $OPTIONS{page}              = $EMPTY;

    GetOptions
      (
        'host|h=s'              => \$OPTIONS{host}               ,
        'use-bash-script'       => \$OPTIONS{bash}               ,
        'remote-console|r!'     => \$OPTIONS{console}            ,
        'virtual-console|v!'    => \$OPTIONS{virtual}            ,
        'nic|n!'                => \$OPTIONS{nic}                ,
        'nic-name|m=s'          => \$OPTIONS{nicName}            ,
        'cmdb|d!'               => \$OPTIONS{cmdb}               ,
        'policy|p=i'            => \$OPTIONS{policyid}           ,
        'show-compliant|c!'     => \$OPTIONS{showcompliant}      ,
        'backup|b!'             => \$OPTIONS{backup}             ,
        'backup-history|s!'     => \$OPTIONS{backuphistory}      ,
        'page|g=s'              => \$OPTIONS{page}               ,
        'help'                  => sub      { HelpMessage(-exitval => 0, -verbose ->1)     },
        'version'               => sub      { VersionMessage()  },
      ) or usage;

    if ($OPTIONS{host} eq $EMPTY)
    {
        print {*STDERR} "ERROR: Must specify a host with -h flag\n";
        HelpMessage;
    }

    sanity_check_options(\%OPTIONS);

    # Parse anything else on the command line and throw usage
    for (@ARGV)
    {
        warn "Unknown argument: $_\n";
        HelpMessage;
    }

    return {%OPTIONS};
}

sub sanity_check_options
{
    my $OPTIONS     = shift;

    if (($OPTIONS->{console}) and ($OPTIONS->{virtual}))
    {
        print "ERROR: Cannot use flags -r and -v together\n";
        HelpMessage;
    }
    elsif (($OPTIONS->{console}) and ($OPTIONS->{cmdb}))
    {
        print "ERROR: Cannot use flags -r and -d together\n";
        HelpMessage;
    }
    elsif (($OPTIONS->{console}) and ($OPTIONS->{backup}))
    {
        print "ERROR: Cannot use flags -r and -b together\n";
        HelpMessage;
    }
    elsif (($OPTIONS->{console}) and ($OPTIONS->{nic}))
    {
        print "ERROR: Cannot use flags -r and -n together\n";
        HelpMessage;
    }

    if (($OPTIONS->{virtual}) and ($OPTIONS->{backup}))
    {
        print "ERROR: Cannot use flags -v and -b together\n";
        HelpMessage;
    }
    elsif (($OPTIONS->{virtual}) and ($OPTIONS->{cmdb}))
    {
        print "ERROR: Cannot use flags -v and -d together\n";
        HelpMessage;
    }
    elsif (($OPTIONS->{virtual}) and ($OPTIONS->{nic}))
    {
        print "ERROR: Cannot use flags -v and -n together\n";
        HelpMessage;
    }

    if (($OPTIONS->{backup}) and ($OPTIONS->{cmdb}))
    {
        print "ERROR: Cannot use flags -b and -d together\n";
        HelpMessage;
    }
    elsif (($OPTIONS->{backup}) and ($OPTIONS->{nic}))
    {
        print "ERROR: Cannot use flags -b and -n together\n";
        HelpMessage;
    }

    if (($OPTIONS->{nic}) and ($OPTIONS->{cmdb}))
    {
        print "ERROR: Cannot use flags -n and -d together\n";
        HelpMessage;
    }

    if (($OPTIONS->{policyid} != 0) and not ($OPTIONS->{cmdb}))
    {
        print "ERROR: Cannot use flag -p without also specifying -d\n";
        HelpMessage;
    }

    if (($OPTIONS->{showcompliant}) and not ($OPTIONS->{cmdb}))
    {
        print "ERROR: Cannot use flag -c without also specifying -d\n";
        HelpMessage;
    }

    if (($OPTIONS->{backuphistory}) and not ($OPTIONS->{backup}))
    {
        print "ERROR: Cannot use flag -s without also specifying -b\n";
        HelpMessage;
    }

    if (($OPTIONS->{nicName}) and not ($OPTIONS->{nic}))
    {
        print "ERROR: Cannot use flag -m without also specifying -n\n";
        HelpMessage;
    }

    return %{$OPTIONS};
}

我想把上面的代码转换成调度表,但是我想不出怎么做。

任何帮助都是非常感谢的。

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2017-11-15 05:39:41

我不确定调度表会有什么帮助,因为您需要经历特定可能性的成对组合,因此不能通过一次查找触发适当的操作。

这是另一种组织方式

代码语言:javascript
复制
use List::MoreUtils 'firstval';

sub sanity_check_options
{
    my ($OPTIONS, $opt_excl) = @_;

    # Check each of 'opt_excl' against all other for ConFLict
    my @excl = sort keys %$opt_excl;
    while (my $eo = shift @excl) 
    {
        if (my $cfl = firstval { $OPTIONS->{$eo} and $OPTIONS->{$_} } @excl) 
        {
            say "Can't use -$opt_excl->{$eo} and -$opt_excl->{$cfl} together";
            HelpMessage();
            last;
        }
    }

    # Go through specific checks on
    # policyid, showcompliant, backuphistory, and nicName
    ...
    return 1;  # or some measure of whether there were errors
}

# Mutually exclusive options
my %opt_excl = (
    console => 'r', virtual => 'v', cmdb => 'c', backup => 'b', nic => 'n'
); 

sanity_check_options(\%OPTIONS, \%opt_excl);

这将检查%opt_excl中列出的所有选项是否存在冲突,从而删除涉及相互排斥的(5个)选项的elsif段。它使用名单::MoreUtils::firstval。其他几个特定调用最好一个一个地检查。

返回$OPTIONS是没有用的,因为它是作为引用传递的,所以任何更改都适用于原始结构(尽管它也不打算更改)。也许您可以跟踪是否存在错误,如果可以在调用方中使用,则返回该错误,或者只返回1

这解决了被问到的长elsif链,而不是进入其余的代码。不过,这里有一个注释:不需要{%OPTIONS},它复制哈希以创建匿名哈希;只需使用return \%OPTIONS;

对可能的多个相互冲突的选项的评论

目前的答案并不是打印所有在有两个以上的情况下使用的相互冲突的选项,正如池格米在注释中所提出的那样;它确实捕捉到了任何冲突,从而导致运行中止。

代码可以很容易地进行调整。而不是if块中的代码

  • 在检测到冲突并脱离循环时设置一个标志,然后打印不可相互使用的标记列表(values %opt_excl)或指向以下使用消息
  • 收集所观察到的冲突,并在循环后打印它们。
  • 或者,在ikegami的回答中看到另一种方法

但是,人们应该知道是否允许调用程序,任何冲突列表都是对健忘的用户(或调试辅助工具)的一种礼遇;无论如何,使用信息也会被打印出来。

考虑到冲突选项的数量,使用信息应该在这方面有一个突出的注意事项。还要考虑到这么多相互冲突的选项可能表明了设计缺陷。

最后,该代码完全依赖于这样一个事实:每次运行该处理一次,并使用少数选项操作;因此,它与效率无关,并且可以自由使用辅助数据结构。

票数 3
EN

Stack Overflow用户

发布于 2017-11-15 05:22:05

您不应该在这里使用elsif,因为多个条件可能是真的。而且,由于多个条件可能为真,所以不能使用调度表。您的代码仍然可以大大简化。

代码语言:javascript
复制
my @errors;

push @errors, "ERROR: Host must be provided\n"
   if !defined($OPTIONS{host});

my @conflicting =
   map { my ($opt, $flag) = @$_; $OPTIONS->{$opt} ? $flag : () }
      [ 'console', '-r' ],
      [ 'virtual', '-v' ],
      [ 'cmdb',    '-d' ],
      [ 'backup',  '-b' ],
      [ 'nic',     '-n' ];

push @errors, "ERROR: Can only use one the following flags at a time: @conflicting\n"
   if @conflicting > 1;

push @errors, "ERROR: Can't use flag -p without also specifying -d\n"
   if defined($OPTIONS->{policyid}) && !$OPTIONS->{cmdb};

push @errors, "ERROR: Can't use flag -c without also specifying -d\n"
   if $OPTIONS->{showcompliant} && !$OPTIONS->{cmdb};

push @errors, "ERROR: Can't use flag -s without also specifying -b\n"
   if $OPTIONS->{backuphistory} && !$OPTIONS->{backup};

push @errors, "ERROR: Can't use flag -m without also specifying -n\n"
   if defined($OPTIONS->{nicName}) && !$OPTIONS->{nic};

push @errors, "ERROR: Incorrect number of arguments\n"
   if @ARGV;

usage(@errors) if @errors;

请注意,上面的代码修复了大量错误。

帮助与使用错误

  • --help应该向STDOUT提供所请求的帮助,并且不应该导致错误退出代码。
  • 使用错误应打印到STDERR,并应导致错误退出代码。

因此,在这两种情况下不以不同方式调用HelpMessage是不正确的。

创建以下名为usage的子程序,以便在GetOptions返回false时使用(没有参数),并在发生其他使用错误时使用错误消息:

代码语言:javascript
复制
use File::Basename qw( basename );

sub usage {
   my $prog = basename($0);
   print STDERR $_ for @_;
   print STDERR "Try '$prog --help' for more information.\n";
   exit(1);
}

继续使用HelpMessage响应--help,但是参数的默认值不适合于--help。您应该使用以下方法:

代码语言:javascript
复制
'help' => sub { HelpMessage( -exitval => 0, -verbose => 1 ) },
票数 0
EN

Stack Overflow用户

发布于 2017-11-15 12:47:03

如果有很多选项,可以使用调度表。我会以编程的方式构建那个表。在这里,它可能不是最好的选择,但它可以工作,而且配置比您的elsif结构更易读。

代码语言:javascript
复制
use strict;
use warnings;
use Ref::Util::XS 'is_arrayref';    # or Ref::Util

sub create_key {
    my $input = shift;

    # this would come from somewhere else, probably the Getopt config
    my @opts = qw( host bash nic nicName console virtual cmdb
        policyid showcompliant backup backuphistory page );

    # this is to cover the configuration with easier syntax
    $input = { map { $_ => 1 } @{$input} }
        if is_arrayref($input);

    # options are always prefilled with false values
    return join q{}, map { $input->{$_} ? 1 : 0 }
        sort @opts;
}

my %forbidden_combinations = (
    map { create_key( $_->[0] ) => $_->[1] } (
        [ [qw( console virtual )] => q{Cannot use flags -r and -v together} ],
        [ [qw( console cmdb )]    => q{Cannot use flags -r and -d together} ],
        [ [qw( console backup )]  => q{Cannot use flags -r and -b together} ],
        [ [qw( console nic )]     => q{Cannot use flags -r and -n together} ],
    )
);

p %forbidden_combinations; # from Data::Printer

p函数的输出是调度表。

代码语言:javascript
复制
{
    00101   "Cannot use flags -r and -v together",
    00110   "Cannot use flags -r and -n together",
    01100   "Cannot use flags -r and -d together",
    10100   "Cannot use flags -r and -b together"
}

正如您所看到的,我们已经对所有选项进行了排序,以便将它们作为密钥使用。这样,理论上你就可以建立各种组合,比如排他性的选项。

让我们看一下配置本身。

代码语言:javascript
复制
my %forbidden_combinations = (
    map { create_key( $_->[0] ) => $_->[1] } (
        [ [qw( console virtual )] => q{Cannot use flags -r and -v together} ],
        # ...
    )
);

我们使用数组引用列表。每个条目都在一行上,包含两条信息。使用胖逗号=>可以方便地阅读。第一部分,非常类似于散列中的键,是组合。这是一个不应该同时出现的字段列表。数组ref中的第二个元素是错误消息。我删除了所有重复出现的元素,比如换行符,以便更容易地更改错误的显示方式和位置。

这个组合配置列表周围的map通过我们的create_key函数运行这些选项,这将它转换成一个简单的位图样式字符串。我们将所有这些分配给映射的散列和错误消息。

create_key内部,我们检查是否使用数组引用作为其参数来调用它。如果是这样的话,调用就是构建表,然后我们将它转换为散列引用,这样我们就有了一个合适的映射来查找内容。我们知道,%OPTIONS总是包含所有存在的键,并且这些键都预先填充了所有计算为false的值。我们可以利用它将这些值的真实性转换为10,然后生成我们的密钥。

我们一会儿就会看到为什么这是有用的。

现在我们怎么用这个?

代码语言:javascript
复制
sub HelpMessage { exit; }; # as a placeholder

# set up OPTIONS
my %OPTIONS = (
    host          => q{},
    bash          => 0,
    nic           => 0,
    nicName       => q{},
    console       => 0,
    virtual       => 0,
    cmdb          => 0,
    policyid      => 0,
    showcompliant => 0,
    backup        => 0,
    backuphistory => 0,
    page          => q{},
);

# read options with Getopt::Long ...
$OPTIONS{console} = $OPTIONS{virtual} = 1;

# ... and check for wrong invocations
if ( exists $forbidden_combinations{ my $key = create_key($OPTIONS) } ) {
    warn "ERROR: $forbidden_combinations{$key}\n";
    HelpMessage;
}

我们现在需要做的就是从Getopt::Long获取$OPTIONS哈希引用,并通过create_key函数将其转换为映射字符串。然后,我们可以简单地查看我们的exists调度表中的键%forbidden_combinations,并显示相应的错误消息。

这种方法的优点

如果您想要添加更多参数,只需将它们包含在@opts中即可。在一个完整的实现中,它可能是由Getopt调用的配置自动生成的。钥匙会在引擎盖下改变,但是因为它是抽象的,所以你不必在意。

此外,这是容易理解的。撇开create_key不说,实际的调度表语法相当简洁,甚至具有文档性。

这种方法的缺点

只有一次调用,就会有很多编程生成。这肯定不是最有效的方法。

为了更进一步,您可以编写一些函数来自动生成特定场景中的条目。

我建议您看一看马克·杰森·多米尼克的好书中的第二章,它可以作为PDF免费使用。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/47299805

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档