Beefy Boxes and Bandwidth Generously Provided by pair Networks
Syntactic Confectionery Delight
 
PerlMonks  

Modules as executable scripts?

by graff (Chancellor)
on Jun 15, 2007 at 01:24 UTC ( [id://621378]=perlquestion: print w/replies, xml ) Need Help??

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

Every now and then, I'd like to implement something as a module, but also have it available as an executable script -- for example, I might want to "anonymize" zip codes in some way, keeping each zip code distinct (so I can keep track of addresses still group records that originally had the same zip code) without actually divulging the real zip codes. Such a function might be handy as a command-line tool (where @ARGV would be zip codes I just want to convert on a whim), and as a module (which could be used in a DBI or CGI script).

This flexibility is a common feature in Python -- you just add a few lines of code at the end of a "module" script to say, in effect, "if this file is being executed from the command line, invoke the class and/or object functions as follows, using the command-line args like so..."

So, if Python can do it, surely Perl can too, right? And so my question is: what would be a good way to do this?

My module-building skills don't get a lot of exercise, and I wouldn't presume to think I have a clue, but I came up with an example that seems to work, in a very simple-minded, unsophisticated way. Here's a little file I call "Runnable.pm":

#!/usr/bin/perl package Runnable; use Exporter qw/import/; @EXPORT_OK = qw/do_something/; sub do_something { return "did something with @_"; } if ( $0 =~ /Runnable.pm/ ) { print do_something( @ARGV ),"\n\n"; } 1;
I can "chmod +x" that file, run it with command-line args, and it prints "did something with ...(those args)". Now, here's a script that uses Runnable.pm as a module:
#!/usr/bin/perl use strict; use Runnable qw/do_something/; print "I'm hoping that this test program ".do_something( @ARGV )."\n";
Sure enough, that runs too, and it prints exactly what I was hoping to see. The only "extra details" I need to handle here are to make sure that Runnable.pm is in my shell's execution PATH, and to also make sure that its location shows up in @INC whenever I use it in another script.

Having the ".pm" extension on the command line when I run the module as a program seems a bit klunky and odd, but I guess there's no way around that if I want to use the same file as a module in other scripts in the "normal" way.

Does anyone see a problem with this, or a better way?

Replies are listed 'Best First'.
Re: Modules as executable scripts?
by friedo (Prior) on Jun 15, 2007 at 02:14 UTC

    I typically do this:

    package Runnable; sub do_something { ... } __PACKAGE__->do_something unless caller; 1;

    Then I can execute the module itself as if it were a script. This is great for command-line tools that need to use inheritance or other OO-ish stuff. For a current project, I have my entire Test::Class-based test-suite implemented this way, so the test classes can be run as if they were .t files, but they inherit parent class tests.

    Update: Here is a node that discusses this technique in more detail: How a script becomes a module

      For PAR compatibility I use:
      __PACKAGE__->do_something(@ARGV) if !caller() || caller() eq 'PAR';
Re: Modules as executable scripts?
by duff (Parson) on Jun 15, 2007 at 02:49 UTC

    I believe the canonical method is to use caller as friedo suggests, though I typically just do something like this (which, though I say "typically" isn't often :):

    #!/usr/bin/perl package Foo; # ... unless (caller) { # ... execute this code if we're run stand-alone } else { # ... do whatever module initialization here. } __END__
    You'll note that I didn't end my module with the traditional 1; because that's part of the module initialization. If the initialization succeeded it would return a true value. If you really didn't need to do any of that then leave off the else and just put a 1; after the conditional.
Re: Modules as executable scripts?
by Zaxo (Archbishop) on Jun 15, 2007 at 02:01 UTC

    You can test whether Runnable.pm was called from the command line by comparing $0 to __FILE__.

    I'm not convinced this is a good idea. I'd prefer to keep my modules distinct from my scripts and to only act like modules. That might save confusion on both the maintainer's and users' parts.

    After Compline,
    Zaxo

Re: Modules as executable scripts?
by aufflick (Deacon) on Jun 15, 2007 at 05:58 UTC
    Neat - I have a feeling I'll be doing this one day :)

    One suggestion for those who feel queazy about 'magic' like this is that you can easily kick off a sub in a package with something like:

    perl -MYour::Package -e run

    where run is the name of a sub that requires no args. Which approach is neater is in the eye of the beholder.

    I really like the comment from friedo that discussed using a similar approach to make a package that can be a standalone test script and also a base package for other tests.

      perl -MYour::Package -e run

      Note that this only works if Your::Package exports run by default, since code executed via -e runs in the main package.

        good point - completely correct.

        taking a look at the -M syntax in perlrun, a cute thing you could do is to implement (or override) import() in your package, and respond to triggers passed in:

        package DoIt; sub import { &run if( $_[0] eq 'run' ); }

        the following commandline:

        perl -MDoIt=run,arg1,arg2

        will call the DoIt::run sub with @_ set to qw(run arg1 arg2).

Re: Modules as executable scripts?
by dorko (Prior) on Jun 15, 2007 at 12:05 UTC
Re: Modules as executable scripts?
by amaguk (Sexton) on Jun 18, 2007 at 17:51 UTC
Re: Modules as executable scripts?
by gerases (Sexton) on Mar 16, 2017 at 17:08 UTC
    So no way to change to the "pm" extension so that the script could be named either with ".pl" or without any extension?

      use requires that the module's files have a ".pm" extension.

        It's true about "use", but "require" doesn't. And that could be the ticket for making modulinos less weird on the command line.
      "This flexibility is a common feature in Python ..."

      And who needs it? Here's what you cannot do in Python that you can in Perl and Ruby: run Python as an ad hoc filter. So there is that. If you are trying to say Python is better than Perl then you just failed.

      perl -pi.bak -e's/foo/bar/g' python.txt
      You can't do that in Python! But you can run a .pm file as a script if you are dumb enough to think that's a good thing.
        I think it will work through a symlink. I.e., /bin/something -> path_to_pm

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others taking refuge in the Monastery: (2)
As of 2024-03-19 06:01 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found