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

Dumb question - I'm trying to invoke a perl script like this:
test.pl DBI
and I want the script to "use" the module parameter that I give it(DBI) so that the script can then get the location of the module on the filesystem from the %INC hash. But this code:
#!/usr/bin/perl -w use strict; my $perl_module = shift; use $perl_module;
gets this error:
[hmerrill@merrill bin]$ ./test.pl DBI syntax error at ./test.pl line 4, near "use $perl_module" Execution of ./test.pl aborted due to compilation errors.
Like I said - it must be a stupid question, but I don't know what I'm missing. Any help is appreciated.

Replies are listed 'Best First'.
Re: module location
by broquaint (Abbot) on Nov 07, 2003 at 15:27 UTC
    Just do a require instead as use is a compile-time construct (as opposed to require which is done run-time)
    require( shift @ARGV );
    But as liz notes, that requires munging the module name for any module with a '::' seperator. Better yet, don't load the module at all and just make use of Module::Locate
    use Module::Locate 'locate'; my $path = locate $ARGV[0];
    HTH

    _________
    broquaint

      Tried installing Module::Locate using CPAN, but got these errors:
      Module-Locate-1/lib/ Module-Locate-1/lib/Module/ Module-Locate-1/lib/Module/Locate.pm Package seems to come without Makefile.PL. (The test -f "/root/.cpan/build/Module-Locate-1/Makefile.PL" returne +d false.) Writing one on our own (setting NAME to ModuleLocate) CPAN.pm: Going to build B/BR/BROQ/Module-Locate-1.tar.gz Checking if your kit is complete... Looks good Writing Makefile for ModuleLocate /usr/bin/perl "-Iblib/arch" "-Iblib/lib" Build.PL Build Can't locate Module/Build.pm in @INC (@INC contains: blib/arch blib/li +b /usr/lib/perl5/5.8.0/i386-linux-thread-multi /usr/lib/perl5/5.8.0 / +usr/lib/perl5/site_perl/5.8.0/i386-linux-thread-multi /usr/lib/perl5/ +site_perl/5.8.0 /usr/lib/perl5/site_perl /usr/lib/perl5/vendor_perl/5 +.8.0/i386-linux-thread-multi /usr/lib/perl5/vendor_perl/5.8.0 /usr/li +b/perl5/vendor_perl /usr/lib/perl5/5.8.0/i386-linux-thread-multi /usr +/lib/perl5/5.8.0 .) at Build.PL line 1. BEGIN failed--compilation aborted at Build.PL line 1. make: *** [Build] Error 2 make: *** Waiting for unfinished jobs.... make: *** Waiting for unfinished jobs.... make: *** Waiting for unfinished jobs.... cp lib/Module/Locate.pm blib/lib/Module/Locate.pm /usr/bin/make -j3 -- NOT OK Running make test Can't test without successful make Running make install make had returned bad status, install seems impossible
      I was just going to try it, so it's not a big deal, but I thought I would let you know.
        Thanks for the heads-up, I've hopefully fixed that bug with the latest version (in anticipation of someone trying to install it ;). It also comes with a few other changes which should be useful to the folks who want to do module probing such as is_mod_loaded() and is_pkg_loaded().
        HTH

        _________
        broquaint

Re: module location
by liz (Monsignor) on Nov 07, 2003 at 15:29 UTC
    That's because the assigment of $perl_module doesn't happen until runtime, while the use happens at compile time.

    You basically have two options:

    Move assignement to compile time
    my $perl_module; BEGIN {$perl_module = shift}; eval "use $perl_module";

    Move loading of module to runtime
    (my $filename = "$perl_module.pm") =~ s#::#/#g; require $filename; $perl_module->import;

    The first has the disadvantage that you need to eval a string. If you can't trust the parameter input, that may be a security issue.

    The latter has the disadvantage that you must convert the module name to a filename. But you need the original module name to be able to do the import().

    Hope this helps.

    Liz

      This is driving me nuts - I don't understand why this doesn't work:
      #!/usr/bin/perl -w use strict; my $perl_module; BEGIN { my $ct_args = @ARGV; if ($ct_args != 1) { print "\nUsage: pwhich_short <perl module name>\n\n"; print "Example: pwhich_short DBI\n\n"; exit; } $perl_module = shift; }; eval "use $perl_module" or die "Can't find $perl_module: $!\n";
      This code gets this error:
      Can't find DBI:
      That's it. Does this work for you? Any ideas?

      Thanks.
        I think you want to replace:
        eval "use $perl_module" or die "Can't find $perl_module: $!\n";
        by:
        eval "use $perl_module"; die "Can't find $perl_module: $@\n" if $@;

        Eval just returns whatever was returned by use, which to my knowledge is undefined (please someone correct me if I'm wrong).

        You need to check whether $@ is non-empty after the eval().

        Liz

Re: module location
by ferrency (Deacon) on Nov 07, 2003 at 15:24 UTC
    "use" happens at compile time, but the $perl_module variable isn't initialized until run time, after the "use" statement has already executed. Actually you can't use a variable package name, but you can require it. Try this:

    #!/usr/bin/perl -w use strict; my $perl_module = shift; eval {require $perl_module} or die "Can't find $perl_module!";
    Alan
      I thought you had it, but not I get this error:
      Can't find DBI! at ./test.pl line 4.
      Here is the absolute path to the DBI.pm file:
      /usr/lib/perl5/vendor_perl/5.8.0/i386-linux-thread-multi/DBI.pm
      and the @INC path shows this in 'perl -V':
      @INC: /usr/lib/perl5/5.8.0/i386-linux-thread-multi /usr/lib/perl5/5.8.0 /usr/lib/perl5/site_perl/5.8.0/i386-linux-thread-multi /usr/lib/perl5/site_perl/5.8.0 /usr/lib/perl5/site_perl /usr/lib/perl5/vendor_perl/5.8.0/i386-linux-thread-multi /usr/lib/perl5/vendor_perl/5.8.0 /usr/lib/perl5/vendor_perl /usr/lib/perl5/5.8.0/i386-linux-thread-multi /usr/lib/perl5/5.8.0
      What am I missing? Thanks for your help!
        Well, there are a few solutions to this.

        My bug was: require DBI looks for "DBI.pm" because DBI is a bareword; but this does not work if you put the bareword in a variable and require that instead.

        One simple solution is to just:

        ./test.pl DBI.pm
        If you don't like that, you can change the eval to a string eval, which will interpolate the package name into a bareword before require sees it:

        #!/usr/bin/perl -w use strict; my $perl_module = shift; eval "require $perl_module" or die "Can't find $perl_module!";
        This would be the more flexible solution, since it would handle things like ./test.pl DBD::PgSQL (assuming that makes sense... basically, any module with ::'s in it).

        Alan

Re: module location
by BrowserUk (Patriarch) on Nov 07, 2003 at 16:22 UTC

    I don't suppose that

    perl -mDBI test.pl

    would meet your needs?


    Examine what is said, not who speaks.
    "Efficiency is intelligent laziness." -David Dunham
    "Think for yourself!" - Abigail
    Hooray!
    Wanted!

      It would, but how in test.pl can I tell that 'DBI' was specified on the command line? test.pl needs to know that.

      I have to say I like your signature quote - "efficiency is intelligent laziness". I'm going to remember that one ;)

        The answer to that would depend on why the script needs to know that. Ie. What will the script do different?

        I guess I'm having trouble trying to conceive of why you would make the module a command line argument rather than having it inside the script. I originally thought that you might have two or modules that offer the same interface, but different implementation. This would mean that the rest of the script wouldn't care which of the alternative modules was loaded, it would just use the exported entrypoints from whichever one was loaded.


        Examine what is said, not who speaks.
        "Efficiency is intelligent laziness." -David Dunham
        "Think for yourself!" - Abigail
        Hooray!
        Wanted!

        It would, but how in test.pl can I tell that 'DBI' was specified on the command line? test.pl needs to know that.
        Yes, and this is Perl ;)
        perl -MDBI -le'print for keys %INC' Exporter.pm Carp.pm strict.pm vars.pm Config.pm warnings/register.pm warnings.pm DynaLoader.pm Exporter/Heavy.pm DBI.pm AutoLoader.pm
Re: module location
by jZed (Prior) on Nov 07, 2003 at 16:25 UTC
    May I back up a minute and ask why you are wanting use of DBI to be conditional? Since DBI can work with or without compilation (see docs for DBI::PurePerl), under what circumstance would you want to not use it? I'm not saying there are no such circumstances, I'm just curious.
      DBI was just an example of a module name. The script I'm working on will take in the name of any perl module and will tell you
      1. if the module is installed on the system 2. if it is installed, what version ($VERSION) it is, and 3. if it is installed, exactly where on the system it resides (absolute file name)
      It's not rocket science - I'm sure others (like Broquaint above with Module::Locate) have probably already done this, but I'll post the code when I'm done.
        Well, for DBI, this tells you all three of those things:

        perl -MDBI=9999

        But good luck on a more general solution, seems like there have been some good suggestions for you.
        use Module::Locate 'locate'; die "Usage: $0 MODULE\n" unless @ARGV; my $mod = shift @ARGV; my $path; if(eval{ require( $path = locate $mod ) }) { die "$mod missing version number\n" unless $mod->VERSION; print "$mod version ", $mod->VERSION, " installed at $path\n"; } else { die "$mod not installed\n"; }
        HTH

        _________
        broquaint

Re: module location
by hmerrill (Friar) on Nov 07, 2003 at 17:33 UTC
    Ok, here's the code - it's not the cleanest code, but it seems to work for the relatively few modules I've tried it with. I call it 'pwhich' which functions sort of like the 'which' command on Linux:
    #!/usr/bin/perl -w use strict; my $perl_module; BEGIN { my $ct_args = @ARGV; if ($ct_args != 1) { print "\nUsage: pwhich <perl module name>\n\n"; print "Example: pwhich DBI\n\n"; exit; } $perl_module = shift; }; eval "use $perl_module"; die "Can't find Perl module $perl_module!\n" if $@; my $inc_key = "$perl_module.pm"; $inc_key =~ s!::!/!g; print "inc_key = $inc_key\n"; my $abs_filename = $INC{$inc_key}; print "\n"; print "Module: $perl_module\n"; print "Location: $abs_filename\n"; open(IN,"<$abs_filename") || die "Can't open $abs_filename!"; my $version = ""; while (<IN>) { if (/\s*\$[\w:]*VERSION\s*=[ "']*([0-9_\.]+)/i) { $version = $1; last; } } close(IN); print "Version: $version\n\n";
Re: module location
by jZed (Prior) on Nov 07, 2003 at 16:55 UTC
    Perhaps this snippet from DBD::CSV's test.pl will help:

    my $v; eval { require DBI; $v->{DBI}= $DBI::VERSION; require SQL::Statement; $v->{SQL}= $SQL::Statement::VERSION; require Text::CSV_XS; $v->{CSV}= $Text::CSV_XS::VERSION; require DBD::CSV; $v->{DBD}= $DBD::CSV::VERSION; }; if ($@) { print "\n\nYOU ARE MISSING REQUIRED MODULES:\n\n"; print " DBI\n" unless $v->{DBI}; print " SQL::Statement\n" unless $v->{SQL}; print " Text_CSV\n" unless $v->{CSV}; exit; } print "USING:\n"; printf " %-20s %s\n",'OS', $^O; printf " %-20s %s\n",'Perl', $]; printf " %-20s %s\n",'DBD::CSV', $v->{DBD}; printf " %-20s %s\n",'DBI', $v->{DBI}; printf " %-20s %s\n",'SQL::Statement', $v->{SQL}; printf " %-20s %s\n",'Text::CSV_XS', $v->{CSV};