如何插值变量以从模块调用 Perl 函数?

How do I interpolate variables to call a Perl function from a module?

提问人:Sanjay 提问时间:10/16/2008 最后编辑:MatSanjay 更新时间:9/16/2012 访问量:8325

问:

要求是从命令行参数传递模块名称和函数名称。 我需要在程序中获取命令行参数,并且需要从该模块调用该函数

例如,使用 2 个参数调用 try.pl 程序: MODULE1(模块名称) Display(函数名称)

 perl try.pl MODULE1 Display 

我想要这样的事情,但它不起作用,请指导我:

use $ARGV[0];
& $ARGV[0]::$ARGV[1]();
Perl 变量 插值

评论

1赞 Robert P 10/17/2008
老实说,可能有一种比将模块名称和函数作为文本传递更好的方法来解决问题。事实上,可能有很多方法。也许如果我们看到这个设计决策背后的原因,就会出现一个更好的解决方案。

答:

7赞 Yanick 10/17/2008 #1

有很多方法可以做到这一点。其中之一是:

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

my ( $package, $function ) = @ARGV;

eval "use $package; 1" or die $@;

$package->$function();  

请注意,该函数的第一个参数将是$package。

4赞 Leon Timmermans 10/17/2008 #2

假设模块导出函数,这应该做:

perl -Mmodule -e function

评论

0赞 friedo 10/17/2008
仅当相关模块默认将“函数”导出到主命名空间时,这才有效。如果是可选导出,则必须使用 -Mmodule=function,如果根本没有导出,则必须使用完全限定的名称或方法调用
9赞 cjm 10/17/2008 #3

假设该函数不是类方法,请尝试以下操作:

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

my ( $package, $function ) = @ARGV;

eval "use $package (); ${package}::$function()";
die $@ if $@;

请记住,这种技术对代码注入是开放的。(参数可以很容易地包含任何 Perl 代码,而不是模块名称。

评论

1赞 Tanktalus 10/17/2008
只有当用户在执行过程中(例如,通过网络,例如 CGI 脚本)进行更改时,注入才是一个问题。从命令行来看,这不是问题,因为它仍然以您的身份运行。
2赞 2 revsAxeman #4

根据 Leon 的说法,如果 perl 模块没有导出它,你可以这样调用它

perl -MMyModule -e 'MyModule::doit()'

前提是潜艇在该包中。

如果它一直导出 sub (in ),那么 Leon 将起作用:@EXPORT

perl -MMyModule -e doit

如果它是可选的导出 (in ),那么您可以这样做。@EXPORT_OK

perl -MMyModule=doit -e doit

但是第一个在将 sub 定义为包的任何情况下都有效,我可能会使用那个而不是最后一个。

3赞 Robert P 10/17/2008 #5

如果你想确保你的perl脚本是安全的(或者至少,防止你自己不小心做了一些愚蠢的事情),我会避免对传递给脚本的数据进行任何形式的评估,而至少没有某种检查。但是,如果你无论如何都在做某种检查,并且你最终显式检查了输入,你不妨显式地拼出你想要调用的女巫方法。您可以使用“已知良好”方法设置哈希值,从而记录您想要可调用的所有内容,同时保护自己。

my %routines = (
    Module => {
        Routine1 => \&Module::Method,
        Routine2 => \&Module::Method2, 
    },
    Module2 => { 
        # and so on
    },
);

my $module  = shift @ARGV;
my $routine = shift @ARGV;

if (defined $module
    && defined $routine
    && exists $routines{$module}            # use `exists` to prevent 
    && exists $routines{$module}{$routine}) # unnecessary autovivication
{
    $routines{$module}{$routine}->(@ARGV); # with remaining command line args
}
else { } # error handling

作为此方法的一个巧妙的副作用,您可以简单地循环访问可用于任何类型的帮助输出的方法:

print "Available commands:\n";
foreach my $module (keys %routines)
{
    foreach my $routine (keys %$module)
    {
        print "$module::$routine\n";
    }
}  
2赞 JDrago 10/17/2008 #6

总是像这样启动你的Perl:

use strict;
use warnings 'all';

然后这样做:

no strict 'refs';
my ($class, $method) = @_;
(my $file = "$class.pm") =~ s/::/\//g;
require $file;
&{"$class\::$method"}();

无论你做什么,都尽量不要评价“$string”。

2赞 Axeman 10/17/2008 #7

好吧,对于您修改后的问题,您可以这样做:

use strict;
use warnings;

{
    no strict;
    use Symbol qw<qualify>;
    my $symb = qualify( $ARGV[1], $ARGV[0] );
    unless ( defined &{$symb} ) { 
        die "&$ARGV[1] not defined to package $ARGV[0]\::";
    }
    &{$symb};
}

由于是在命令行上指定它,因此从命令行包含的最简单方法是标志。-M

perl -MMyModule try.pl MyModule a_subroutine_which_does_something_cool

但你总是可以

eval "use $ARGV[0];"; 

但这极易受到注射的影响:

perl try.pl "Carp; `do something disastrous`;" no_op

评论

0赞 ysth 10/17/2008
想想孩子们......如何让它 : 'ekho haltfermat c:
1赞 gpojd 10/21/2008 #8

我会使用 UNIVERSAL::require。它允许您要求或使用变量中的模块。因此,您的代码将更改为如下所示:

use UNIVERSAL::require;

$ARGV[0]->use or die $UNIVERSAL::require::ERROR;
$ARGV[0]::$ARGV[1]();

免责声明:我没有测试该代码,我同意 Robert P 的评论,即可能有比将这些作为命令行参数传递更好的解决方案。