Beefy Boxes and Bandwidth Generously Provided by pair Networks
Think about Loose Coupling

Technique for executable modules

by tlhackque (Beadle)
on Feb 04, 2019 at 15:31 UTC ( #1229359=perlmeditation: print w/replies, xml ) Need Help??

Over the years, I've seen a number of techniques used to enable a Perl module to have a dual-life as as a command. These range from commented out mainline code (used for testing) to requiring the command line user to invoke a 'run' function. And include invoking 'caller' to guess the mode, inspecting $0, or even '$Pkg::FooMode='module'; require Pkg::Foo;'.

None has seemed entirely satisfactory: there always seem to be either functional or esthetic compromises. Often with some burden on the end user(s) as well as the author.

Here is a technique that, with minimal one-time setup, hides the ugliness from all users, yet has minimal impact on the developer.

Assume that your script is to be installed in /opt/sbin, and that your Perl library includes /usr/lib/perl5/site_perl/5.99.0/.

Your script looks like:

#!/usr/bin/perl package TL::MyPackage; use warnings; use strict. our $VERSION = 1.0; sub new { my $class = shift; my $obj = [ @_ ]; return bless $obj, $class; } ... package main; use warnings; use strict; unless( __FILE__ =~ /\.pm$/ ) { # Skip if loaded as a module # Command-line interface require Getopt::Long; Getopt::Long->import( qw/GetOptions :config bundling/ ); my $verbose; GetOptions( 'verbose|v!' => \$verbose ) or die "Command error\n"; my $foo = TL::MyPackage->new( $verbose, ... ); print $foo->rub( 'lamp' ); ... exit(); } 1;
And the installation looks like:
mkdir -p /opt/sbin cp -p mycommand /opt/sbin/ mkdir -p /usr/lib/perl5/site_perl/5.99.0/TL ln -s ../../../../../../opt/sbin/mycommand \ /usr/lib/perl5/site_perl/5.99.0/TL/

This allows the user to treat the module as an ordinary command - no CamelCase name, 'funny' .pm extension, or '-e run()' to remember (or wrap in another script). No worries for the module author about strange ways it might be loaded, forgetting to shut off test code before release, or having 'shift' default to '@_' instead of '@ARGV'. (S)he can, of course, still provide a 'run' function if desired, but it's not required. And of course, lazy loading the command-only modules reduces the dual-life expense when used as a pure module.

There is no unusual magic about the locations chosen for this explanation: the .pm symlink goes in the library where 'use' & 'require' can find it, and the executable goes in PATH. Of course, you can invert the direction of the symlink if you think of the module as primary and the executable as secondary. TMTOWTDI; tastes vary. Note that File::Spec->abs2rel is a convenient way to generate the symlink in an installation script. You do want to use a relative target in case the file is on a mountpoint.

Replies are listed 'Best First'.
Re: Technique for executable modules
by tobyink (Canon) on Feb 10, 2019 at 17:05 UTC
    unless( __FILE__ =~ /\.pm$/ ) { # Skip if loaded as a module ... }

    Better way:

    sub main { ... } main unless caller;

    Your symlink idea will still work.

Re: Technique for executable modules
by Laurent_R (Canon) on Feb 06, 2019 at 16:48 UTC

Log In?

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

How do I use this? | Other CB clients
Other Users?
Others about the Monastery: (7)
As of 2022-01-20 17:06 GMT
Find Nodes?
    Voting Booth?
    In 2022, my preferred method to securely store passwords is:

    Results (57 votes). Check out past polls.