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

Dear Monks,

I would like to know of there is an alternative to subs

My main script General.pl uses a lot of different little scripts like prog1.pl , prog2.pl, ... prog15.pl which are at least more than one hundred lines each. And in General.pl I call:

my $cmd1 = "perl prog1.pl $arg_a $arg_b"; system ($cmd1); my $cmd2 = "perl prog2.pl $arg_a $arg_c"; system ($cmd2); ... my $cmd15 = "perl prog15.pl $arg_b $arg_d"; system ($cmd15);

I would like to know if I should change all the little programs into subs in General.pl or if I should keep it as it is now. Thanks a lot

Replies are listed 'Best First'.
Re: alternative way to subs
by Corion (Patriarch) on Apr 30, 2008 at 07:39 UTC

    One way would be to change all the small programs into modules, and wrap all their "main" code in a subroutine in that module:

    #!/usr/bin/perl -w # prog1.pl print "Hello";

    would become

    #!/usr/bin/perl -w # prog1.pl package Prog1; sub main { print "Hello @_"; }; main(@ARGV) if ! caller;
    if ($0 eq __FILE__) {
    main(@ARGV);
    };

    That way, you can use and run all parts from the master program and still run all parts separately as programs. This makes the migration easier:

    # main program: use strict; use Prog1; my @results = Prog1::main("steph_bow"); # equivalent to 'perl -w prog1 +.pm steph_bow' ...

    Of course, you will want to move some parameters from being passed as files to be passed as arguments within Perl, but those parts can go into the "main program" parts of each module.

    In the long run, you want to move away from the name main and give a more sensible name, like process_names or whatever, but it's hard to give a concrete example for a generic case :)

    Update: My code to detect whether a file is loaded as a module or a program failed for invocations that didn't use the bare filename. Fixed thanks to ysth!

Re: alternative way to subs
by rovf (Priest) on Apr 30, 2008 at 08:10 UTC
    Since your programs are obviously decoupled enough, that there is no problem to shell out via system() to use them, I would change this only if you find that you are running into performance problems (such as when doing the system call frequently in a loop). After all, you already have your prog*.pl and they work, so I would be reluctant to change things without compelling reason. Also, it makes the prog*.pl be useful as individual pieces callable from the commandline - an advantage which you would loose when turning them into a sub.
    In case you eventually think about adding a new prog134 to your existing set, you might, however, consider the following alternative:
    Write a module SupaDupa134.pm containing the code of your new piece of marvel as a sub mynewsub134(). Use this module in your main application, where you can call the sub. In addition, write a standalone Perl program prog134.pl which doesn't do much more than
    use SupaDupa134 qw(mynewsub134); mynewsub134(@ARGV);
    Doing this, you have all the advantages of a sub, plus a command line version for your own use, but still only one place to maintain the actual code.

    HTH,
    Ronald
    Ronald Fischer <ynnor@mm.st>
Re: alternative way to subs
by kyle (Abbot) on Apr 30, 2008 at 15:20 UTC

    The other monks have given you some good strategies for turning your programs into subs easily. I particularly like the modulino approach that Corion gave.

    If you do turn these separate programs into subs, I think it's important also to think about how this changes the functionality.

    • As subs, you don't have to worry about the arguments you pass. As programs, if you have shell metacharacters in those arguments, you have to escape them somehow.
    • As programs, errors don't affect the main program much. As subs, you might want to wrap in eval if you want to keep going after a sub dies.
    • Programs can leak memory without affecting the main program. If your sub leaks memory, the program leaks.
    • Getting data back from a program is a somewhat different (and more involved) process than getting data back from a sub—depending on what you're passing, of course.

    If what you have now works, fine. I'd be worried about the future. This way of doing subroutines is probably going to lead to grief at some point (probably because of how you pass data in and out), and I wouldn't want to perpetuate it any longer than necessary. That is, don't write new code this way. As I work on old code that does this, I'd probably migrate it away, but I wouldn't go out of my way to fix what's not (yet) broken.

Re: alternative way to subs
by shmem (Chancellor) on Apr 30, 2008 at 12:53 UTC

    There are several ways to do that, as always... one elegant way of doing that is using the AutoLoader (as e.g. POSIX does it) together with Exporter. To use that, you have to make up a package file (say, General.pm)

    package General; use AutoLoader qw(AUTOLOAD); use Exporter; # functions your main script might want to import our @EXPORT_OK = qw( func1 func2 ... ); 1;

    and wrap the content of each file into a subroutine:

    original:

    #!/usr/bin/perl # file prog1.pl # main program my ($arg_a, $arg_b) = @ARGV; ; # ... more perl code # end main

    wrapped:

    #!/usr/bin/perl # file func1.al package General; # the function must be compiled into its package. sub func1 { # main program my ($arg_a, $arg_b) = @_; ; # ... more perl code # end main } unless (caller) { func1(@ARGV); } 1; # <-- important!

    The package file General.pm lives anywhere where perl can find it. Make a directory ./auto/General/ in the directory where General.pm lives and stuff the wrapped function files (*.al files) there. Write the file ./auto/General/autosplit.ix

    package General; # optionally put your subs here to predeclare them at package loading sub func1 ; 1; # last line, important

    That's it. Now you can use General.pm in your main script

    #!/usr/bin/perl use General qw(func1 otherfunc); # functions from General you want to +call directly my $result = func1($arg_a, $arg_b);

    and each function file will be compiled on demand, that is, only if you actually are calling its function.

    --shmem

    _($_=" "x(1<<5)."?\n".q·/)Oo.  G°\        /
                                  /\_¯/(q    /
    ----------------------------  \__(m.====·.(_("always off the crowd"))."·
    ");sub _{s./.($e="'Itrs `mnsgdq Gdbj O`qkdq")=~y/"-y/#-z/;$e.e && print}
      I'm not familiar with this strategy. Are you creating two packages named General? I see it declared twice.

        A package declaration just says "Unless I specifically say otherwise by qualifying them, any symbol table references (subroutine definitions, global (package) variable references via our, etc) I make should be understood to be going into this package until I say otherwise or the current scope ends". There's no written-in-stone correspondence between any given package and only one file. If you really wanted to you could have n different files all putting things into the same one package, or one file which puts subs in m different packages. Convention and normal usage tends to be one-package-per-file, but that's again convention not a requirement.

        The cake is a lie.
        The cake is a lie.
        The cake is a lie.

Re: alternative way to subs
by Codon (Friar) on Apr 30, 2008 at 16:55 UTC
    Taking the idea behind Corion's code, but working on how to do this without needing to modify the individual scripts, I came up with the idea of wrapping the code of each script into a subroutine in your General.pl. I've banged together a script that works (for some definition), but I'm sure could run afoul if you aren't careful or if your needs are not quite the same as the assumptions I made.
    #!/usr/bin/perl use strict; use warnings; my @modules = qw(1 2 3 4); my %sub; for my $module (@modules) { if ( -e "$module.pl" and -r "$module.pl" ) { local $/; open my $fh, '<', "$module.pl" or die "cannot open $module.pl: $ +!\n"; my $perl = <$fh>; close $fh; my $sub = sub { local @ARGV = @_; eval $perl; warn "$module failed: $@\n" if ($@); }; if ($@) { die "$module.pl failed to load: $@\n"; } $sub{$module} = $sub; } else { die "$module.pl does not exist or is not readable\n"; } } for my $sub ( @modules ) { warn "calling $sub\n"; $sub{$sub}->(); } print "finished calling all modules\n";
    I happen to have several scripts in my directory (1.pl, 2.pl, 3.pl, etc.) because I use them for writing test / proof of concept code. This works in that it wraps each file in a subroutine; it calls each subroutine; the work is done; output is returned as expected and errors are trapped and handled. This requires no modification to the existing scripts. If you need to pass arguments to the scripts, they can be passed to the subourtines and all is rosey.

    Ivan Heffner
    Sr. Software Engineer
    WhitePages.com, Inc.
Re: alternative way to subs
by leocharre (Priest) on Apr 30, 2008 at 13:42 UTC

    Let's see.. For each of these scripts progX.pl, we want to execute what's in them.. sort of.
    By your example, you are not passing args to code in prog&*.pl files???
    Thus the notion of "doing away with subs"?

    If so, you can "execute" the code by..

    # General.pl require './prog1.pl' require './prog2.pl'
    # prog1.pl print "I am from prog1.pl\n"; 1;
    # prog2.pl print "I am from prog2.pl\n"; 1;