1

I have pretty big perl script executed quite frequently (from cron).
Most executions require pretty short & simple tests.

How to split single file script into two parts with "part two" compiled based on "part 1" decision?


Considered solution:

  1. using BEGIN{ …; exit if …; } block for trivial test.
  2. two file solution with file_1 using require to compile&execute file_2.
    I would prefer single file solution to ease maintenance if the cost is reasonable.
AnFi
  • 10,493
  • 3
  • 23
  • 47
  • `if(condition) { ... }`? – el.pescado - нет войне Mar 05 '18 at 11:56
  • @el.pescado It fixes **execution** of the "code part". It does not fix **compilation** of the "code part". – AnFi Mar 05 '18 at 11:59
  • 2
    Can you quantify whether the compilation overhead is relevant? Is it really a 10k+ line script? Does the compilation/BEGIN phase (`perl -c ...`) really take multiple seconds? If not, how are you sure this kind of optimization is relevant? – amon Mar 05 '18 at 12:18
  • The script is intended to be operated so frequently and for so long (years) that IMHO it will make difference over full script lifetime. – AnFi Mar 05 '18 at 13:47

2 Answers2

2

First, you should measure how long the compilation really takes, to see if this "optimization" is even necessary. If it does happen to be, then since you said you'd prefer a one-file solution, one possible solution is using the __DATA__ section for code like so:

use warnings;
use strict;

# measure compliation and execution time
use Time::HiRes qw/ gettimeofday tv_interval /;
my $start;
BEGIN { $start = [gettimeofday] }
INIT  { printf "%.06f\n", tv_interval($start) }
END   { printf "%.06f\n", tv_interval($start) }

my $condition = 1; # dummy for testing
# conditionally compile and run the code in the DATA section
if ($condition) {
    eval do { local $/; <DATA>.'; 1' } or die $@;
}

__DATA__
# ... lots of code here ...
haukex
  • 2,973
  • 9
  • 21
  • 1
    The compilation take >100ms extra - the script uses pretty big external modules. I would like to include the feature with minimal code/script changes. – AnFi Mar 07 '18 at 19:58
0

I see two ways of achieving what you want. The simple one would be to divide the script in two parts. The first part will do the simple tests. Then, if you need to do more complicated tests you may "add" the second part. The way to do this is using eval like this:

<first-script.pl>
...
eval `cat second-script.pl`;
if( $@ ) {
  print STDERR $@, "\n";
  die "Errors in the second script.\n";
}

Or using File::Slurp in a more robust way:

eval read_file("second-script.pl", binmode => ':utf8');

Or following @amon suggestion and do:

do "second-script.pl";

Only beware that do is different from eval in this way:

It also differs in that code evaluated with do FILE cannot see lexicals in the enclosing scope; eval STRING does. It's the same, however, in that it does reparse the file every time you call it, so you probably don't want to do this inside a loop.

The eval will execute in the context of the first script, so any variables or initializations will be available to that code.

Related to this, there is this question: Best way to add dynamic code to a perl application, which I asked some time ago (and answered myself with the help of the comments provided and some research.) I took some time to document everything I could think of for anyone (and myself) to refer to.

The second way I see would be to turn your testing script into a daemon and have the crontab bit call this daemon as necessary. The daemon remains alive so any data structures that you may need will remain in memory. On the down side, this will take resources in a continuos way as the daemon process will always be running.

Javier Elices
  • 2,066
  • 1
  • 16
  • 25
  • 1
    `eval read_file($filename)` would usually be expressed as [`do $filename` (+ error handling)](https://perldoc.perl.org/functions/do.html), which has the immense advantage of using the correct file name and line numbers in error messages. – amon Mar 05 '18 at 12:21
  • 1
    I would not recommend `File::Slurp`, it has too many [issues](https://rt.cpan.org/Public/Dist/Display.html?Name=File-Slurp). – haukex Mar 05 '18 at 12:39
  • Sorry to pile on, but I see additional issues in regards to error handling: ``eval `cat second-script.pl`;`` - what e.g. if the file isn't found? Also, `eval ...; if ($@) {...}` [has issues](https://www.perlmonks.org/?node_id=1153468), and as already pointed out the whole thing can be replaced by `do FILE`, which however needs [its own error handling](http://perldoc.perl.org/functions/do.html). OTOH you make a good point in regards to using a daemon. – haukex Mar 05 '18 at 14:28
  • @haukex, I am trying to give options. `do` does not see lexicals from the enclosing scope, which may be the whole point of using `eval`. All options may be valid. The good thing about people commenting is that lots of ideas come up for someone having the same problem to use. – Javier Elices Mar 05 '18 at 18:04
  • I'm all for TIMTOWTDI :-) I wasn't saying the options are bad or should not have been presented, just that they require some modifications/additions to handle errors. – haukex Mar 05 '18 at 18:08