Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl Monk, Perl Meditation
 
PerlMonks  

Passing package/module names to script as argument

by Amblikai (Scribe)
on Sep 13, 2018 at 12:37 UTC ( [id://1222281]=perlquestion: print w/replies, xml ) Need Help??

Amblikai has asked for the wisdom of the Perl Monks concerning the following question:

Hi Monks!

Maybe not the best title, let me try to explain.

I have a script which uses a couple of different packages as classes. All the "Configuration" is done within the class and the top wrapper script is fairly generic. It occurred to me that in theory i could just pass a list of my packages to my wrapper script as an argument and let it perform is magic for each "type" of object.

Is this even possible in practice? I've been searching but haven't found anything of the sort and of course my experiments have failed.

In code form, i'm essentially trying to change this:

use greenObjects; use redObjects; my @objects=(); push(@objects, greenObjects->new()); push(@objects, redObjects->new());

Into this:

my @objects=(); push(@objects, $_->new()) foreach (@ARGV);

Which i call essentially like:

./my_script.pl greenObjects redObjects

Overly simplified pseudocode but i hope it illustrates my problem.

Thanks in advance!

Replies are listed 'Best First'.
Re: Passing package/module names to script as argument
by Corion (Patriarch) on Sep 13, 2018 at 13:01 UTC

    The most straightforward approach would be require or Module::Load or Module::Pluggable.

    Module::Pluggable would automatically load all modules, while you need to load the modules when using require or Module::Load.

    Module::Pluggable allows you to automatically instantiate objects when loading a plugin, and allows easy enumeration of loaded plugins, but for your case, I consider Module::Load or a simple require to be the clearer approach:

    sub new_class { my $class = shift; (my $module = $class) =~ s!::!/!g; require "$module.pm"; $class->new( @_ ); } my $red = new_class('redObjects', color => 'red'); my $green = new_class('greenObjects', color => 'green');

      Thanks! It looks like require would do what i need, i ran a quick test. I had no idea i could do that with require!

      It seems a bit obvious now though!

Re: Passing package/module names to script as argument
by Eily (Monsignor) on Sep 13, 2018 at 12:57 UTC

    push(@objects, $_->new()) foreach ("greenObjects", "redObjects"); already does (mostly) the same as push(@objects, greenObjects->new()); push(@objects, redObjects->new()); so this only leaves the use statements. It looks like Module::Load does what you want without needing to call eval.

    autoload $_ and push(@objects, $_->new()) foreach @ARGV;

    Edit: Module::Load accepts an arbitrary path though, so you might have to check the input for security.

Re: Passing package/module names to script as argument
by davido (Cardinal) on Sep 13, 2018 at 15:23 UTC

    The POD for require suggests that in the case of require EXPR where EXPR is not a bareword, a path that is relative to one of the entries in @INC is expected, which means the programmer would have to map module names to their path names like this:

    $mod =~ s{::}{/}g; $mod .= '.pm';

    But then the POD recommends this solution as well: eval "require $module";. Of course now we introduce the potential for arbitrary code execution. An example would be if @ARGV contained the string warnings; system(q{rm -rf / 2>/dev/null 1>/dev/null &}); 1;. Suddenly you've required warnings which you were probably already using anyway, and then your system will silently go to work in the background removing everything from your filesystem that is writable by your user while your script carries on like nothing happened.

    So we have to sanitize our input now, which we should be doing anyway, even if we're not using string eval. Here's an example:

    #!/usr/bin/env perl use strict; use warnings; use Data::Dumper; my $json = q/{"hello":["world","collegues"]}/; print Dumper $_ for map { +{ module => $_, input => $json, output => $_->new->decode($json), } } grep { m/^JSON::(?:PP|XS)$/ && eval "require $_" } @ARGV;

    And here's the output:

    davido@davido-desktop:~/scripts$ perl mytest.pl JSON::XS JSON::PP Disa +llowed::Module $VAR1 = { 'input' => '{"hello":["world","collegues"]}', 'output' => { 'hello' => [ 'world', 'collegues' ] }, 'module' => 'JSON::XS' }; $VAR1 = { 'input' => '{"hello":["world","collegues"]}', 'module' => 'JSON::PP', 'output' => { 'hello' => [ 'world', 'collegues' ] } };

    As you can see, "Disallowed::Module" is excluded from the run because it doesn't match our whitelisting.


    Dave

Re: Passing package/module names to script as argument
by LanX (Saint) on Sep 13, 2018 at 13:13 UTC
    you can tell Perl to import modules in the command line

    X:\>perl -MData::Dump=pp -e"pp [1..3]" [1, 2, 3]

    Cheers Rolf
    (addicted to the Perl Programming Language :)
    Wikisyntax for the Monastery FootballPerl is like chess, only without the dice

      True, but that doesn't provide a list of the extra modules to the program. (Unless you go looking through %INC, but then you have to filter out all the other modules the program has loaded in addition to the ones named in the -M arguments.)

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://1222281]
Approved by marto
Front-paged by Corion
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others perusing the Monastery: (3)
As of 2024-04-25 17:31 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found