Perl - copy variables value out of BEGIN block - perl

I have a simple script:
our $height = 40;
our $width = 40;
BEGIN {
GetOptions( 'help' => \$help,
'x=i' => \$width,
'y=i' => \$height) or die "No args.";
if($help) {
print "Some help";
exit 0;
}
print $width."\n"; #it is 10 when call with script.pl -x 10 -y 10
print $height."\n"; #it is 10 when call with script.pl -x 10 -y 10
#some other code which check installed modules
eval 'use Term::Size::Any qw( chars pixels )';
if ( $# ) {
if ( $# =~ /Cant locate (\S+)/ ) {
warn "No modules";
exit 2;
}
}
}
print $width."\n"; #but here is still 40 not 10
print $height."\n";#but here is still 40 not 10
I call this script with 2 parameters (x and y), for example: script.pl -x 10 -y 10. But the given values are not saved in variables $width and $height. I want change this variables by giving arguments. How can I copy given values or save them into $width and $height? Is it possible?
EDITED - I added some code to this example

The BEGIN clause is executed before normal code. When you declare $height and $width, you set them to 40 after you process the options.
Solution: process the options outside the BEGIN clause.

The problem is that declaration/definitions like our $height = 40 etc. are executed in two phases. The declaration is performed at compilation time, while the assignment is done at run time. That means something like
my $x = 0;
BEGIN {
$x = 1;
}
say $x;
will display 0, because $x is declared at compile time and set to 1 at compile time because of the BEGIN block. But it is then set to zero at run time.
All you need to do it change the declaration/definition to just a declaration. That way there is no run-time modification of the assignment made by the BEGIN block
use strict;
use warnings 'all';
use feature 'say';
my $xx;
BEGIN {
$xx = 1;
}
say $xx;
output
1
Note that there is no need for our. my is almost always preferable. And please don't use BEGIN blocks to execute significant chunks of code: they should be reserved for preparatory actions of comparable to loading the required modules before run time starts. No one expects a program to output help text if it won't compile, which is what you are trying to do.

All BEGIN blocks are executed in the compile phase, as soon as possible (right after they're parsed) -- before the run phase even starts. See this in perlmod and see this "Effective Perler" article. Also, the declaration part of my $x = 1; happens in the compile phase as well, but the assignment is done at runtime.
Thus the $height and $weight are declared, then your code to process the options runs in its BEGIN block, and once the interpreter gets to the run phase then the variables are assigned 40, overwriting whatever had been assigned in that BEGIN block.
Thus a way around that is to only declare these variables, without assignment, before the BEGIN block, and assign that 40 after the BEGIN block if the variables are still undefined (I presume, as default values).
However, it is better not to process options, or do any such extensive work, in a BEGIN block.
Here are a couple of other ways to do what you need.
Loading the module during compilation is fine for your purpose, as long as you know at runtime whether it worked. So load it as you do, under eval in a BEGIN block, so that you can set a flag for later use (conditionally). This flag need be declared (without assignment) before that BEGIN block.
my $ok_Term_Size_Any;
BEGIN {
eval 'use Term::Size::Any qw(chars pixels)';
$ok_Term_Size_Any = 1 unless $#;
};
# use the module or else, based on $ok_Term_Size_Any
The declaration happens at compile time, and being in BEGIN so does the assignment -- under the conditional if not $#. If that condition fails (the module couldn't be loaded) the assignment doesn't happen and the variable stays undefined. Thus it can be used as a flag in further processing.
Also: while the rest of the code isn't shown I can't imagine a need for our; use my instead.
NOTE Please consult this question for subtleties regarding the following approach
Alternatively, load all "tricky" modules at runtime. Then there are no issues with parsing options, what can now be done normally at runtime as well, before or after those modules, as suitable.
The use Module qw(LIST); statement is exactly
BEGIN {
require Module;
Module->import(LIST);
};
See use. So to check for a module at runtime, before you'd use it, run and eval that code
use warnings 'all';
use strict;
eval {
require Module;
Module->import( qw(fun1 fun2 ...) );
};
if ($#) {
# Load an alternative module or set a flag or exit ...
};
# use the module or inform the user based on the flag
Instead of using eval (with the requisite error checking), one can use Try::Tiny but note that there are issues with that, too. See this post, also for a discussion about the choice. The hard reasons for using a module instead of eval-and-$# have been resolved in 5.14.

Related

How can I stop code from being run in the compiler phase?

This question requires an understanding of compiler phase, vs the BEGIN block. From Programming Perl: 3rd Edition - Page 467
It's also important to understand the distinction between compile phase and compile time, and between run phase and run time. A typical Perl program gets one compile phase, and then one run phase. A "phase" is a large-scale concept. But compile time and run time are small-scale concepts. A given compile phase does mostly compile-time stuff, but it also does some run-time stuff via BEGIN blocks. A given run phase does mostly run-time stuff, but it can do compile-time stuff through operators like eval STRING.
Let's take very a simple example
sub complex_sub {
die 'code run';
}
sleep 5;
print 'good';
use constant FOO => complex_sub();
if the above is run as-is, then complex_sub from the users perspective is run in the compiler phase. However, with slight modifications I can have..
# Bar.pm
package Bar {
use constant FOO => main::complex_sub();
}
# test.pl
package main {
sub complex_sub {
die 'code run';
}
sleep 5;
print 'good';
require Bar;
}
In the above code complex_sub is run in the execution phase. Is there anyway to differentiate these two cases from the perspective of complex_sub to enable the top syntax, but to prohibit the bottom syntax.
Use the ${^GLOBAL_PHASE} variable. It contains "START" in the first case, but "RUN" in the second one.
# RUN
perl -wE'say ${^GLOBAL_PHASE}'
# START
perl -wE'BEGIN {say ${^GLOBAL_PHASE}}'
# RUN
perl -wE'eval q{BEGIN {say ${^GLOBAL_PHASE}}}'
See perlvar for details.
You could do:
#!/usr/bin/env perl
use Const::Fast;
sub complex_sub {
die 'code run';
}
sleep 5;
print 'good';
const my $FOO => complex_sub();
Which outputs:
<5 second pause>
code run at /tmp/so1.pl line 6.
good
This works because though the lexical variable is declared at compile time it isn't set till run time.

How to define an environment variable before loading modules?

I use the AnyEvent::DNS module.
I want to disable IPv6, so that the resolver only makes a request for A record.
AnyEvent::DNS, uses the environment variable $ENV{PERL_ANYEVENT_PROTOCOLS}
But setting the variable does not work; the resolver still sends two requests A and AAAA
Code from AnyEvent::DNS:
our %PROTOCOL; # (ipv4|ipv6) => (1|2), higher numbers are preferred
BEGIN {
...;
my $idx;
$PROTOCOL{$_} = ++$idx
for reverse split /\s*,\s*/,
$ENV{PERL_ANYEVENT_PROTOCOLS} || "ipv4,ipv6";
}
How to define an environment variable before loading modules?
Since the code that checks the environment variable is in a BEGIN block, it will be run immediately once the Perl compiler reaches it.
When Perl starts compiling your script, it checks for use statements first. So when you use AnyEvent::DNS, Perl loads that module and parses the file. BEGIN blocks are executed at that stage, while code in methods will only be compiled, not executed.
So if you have something like the following, the code you showed above will be run before you even set that variable.
use strict;
use warnings;
use AnyEvent::DNS;
$ENV{PERL_ANYEVENT_PROTOCOLS} = 'ipv4';
...
There are two ways you can circumvent that.
You can put the assignment in your own BEGIN block before you load AnyEvent::DNS. That way it will be set first.
use strict;
use warnings;
BEGIN {
$ENV{PERL_ANYEVENT_PROTOCOLS} = 'ipv4';
}
use AnyEvent::DNS;
Alternatively, you can just call your program with the environment variable set for it from the shell.
$ PERL_ANYEVENT_PROTOCOLS=ipv4 perl resolver.pl
The second one is more portable, in case you later want it to do IPv6 after all.
Read more about BEGIN in perlmod.

How can a Perl subroutine report the line that called it?

I am writing a Perl pipeline, a script that calls various other programs and manages passing data from one to the other. The script (call it pipeline.pl) and the various sub-scripts it manages all share a list of common subroutines defined in subroutines.ph and included via a require subroutines.ph directive.
One of these is a function whose job is to exit printing an error message (the actual subroutine also does some other jobs, but they're not relevant here; no, I am not reinventing die()):
## subroutines.ph
sub errorDie
{
my ($errMsg) = #_;
## various other cleanup tasks here
die($errMsg);
}
1;
And, in pipeline.pl:
#!/usr/bin/perl
require 'subroutines.ph';
errorDie("foo")
Running the script above results in:
foo at subroutines.ph line 5.
Is it possible to have it instead report something like:
foo at pipelines.pl line 4.
So, instead of reporting the line the die() was found on, it should report the line of the original script where the errorDie subroutine was called from. I know I can do this by including the line in the $errMsg variable, but that is fragile and cumbersome. Can this be done automatically? Can a subroutine defined in an external file detect where it was called from?
There is caller , to do this:
https://perldoc.perl.org/functions/caller.html
my ($package, $filename, $line) = caller;
gives you the information you need.
However, as you are talking about debugging generally, you can get a complete backtrace from carp, as mentioned already.
That's the point of Carp's croak.
Pkg.pm:
package Pkg;
use Carp qw( croak );
sub some_func {
my ($cmd, $param) = #_;
$cmd eq 'encode' || $cmd eq 'decode'
or croak("Invalid command \"$cmd\"");
# ...
}
1;
a.pl:
use Pkg;
Pkg::some_func('foo', 'bar');
Output:
Invalid command "foo" at a.pl line 3.

understanding perl behaviour when loading a module using use keyword within if block

Until now I assumed that use keyword loads the module during compile time and require loads the module during runtime. If that's true then loading a module ( using use ) within a if block, should fail as the block gets executed during runtime!
But I tried testing this using the below code. And the output tells me I am wrong.
#!/usr/bin/perl
my $file = '/home/chidori/dummy.txt';
if ( $file ) {
use File::Basename;
my $base_filename = basename($file);
print "File basename is $base_filename\n";
}
else {
print "Nothing to display\n";
}
Output
chidori#ubuntu:~$ ./moduletest.pl
File basename is dummy.txt
This behavior is implicitly documented in the POD for use.
Because "use" takes effect at compile time, it doesn't respect
the ordinary flow control of the code being compiled. In
particular, putting a "use" inside the false branch of a
conditional doesn't prevent it from being processed.
The use statment is part of the parse tree of your code. It is executed during the compile phase of perl. The Perl compile won't add the use statement to the parse tree. This means that it won't be executed during the run time of the program.
If you are really curious how the code is parsed, then you can inspect the parse tree with B::Deparse.
use Foo;
is basically the same thing as
BEGIN {
require Foo;
import Foo;
}
It gets executed as soon as it's compiled, not when the program is eventually run. As such, it's not subject to conditionals and loops.
It doesn't make much sense to place use File::Basename; inside a block, but it does make sense for other uses of BEGIN and use. It makes sense for lexical pragmas, for example.
use warnings; # Changes compiler settings.
$x; # Warns.
{
no warnings; # Changes compiler settings.
$x; # Doesn't warn.
{
use warnings; # Changes compiler settings.
$x; # Warns.
}
$x; # Doesn't warn.
}
$x; # Warns.
1;

BEGIN block and variable declaration

Is it valid perl to set a variable in a BEGIN block, but declare the variable outside the BEGIN block?
#!/usr/bin/env perl
use strict;
use warnings;
use 5.10.0;
my $var;
BEGIN{ $var = 10 }
say $var;
Yes, it's valid. In fact, you must do it that way, or $var would be local to the BEGIN block and not available in the rest of your program. To quote perlsub:
A my has both a compile-time and a run-time effect. At compile time, the compiler takes notice of it. ... Actual initialization is delayed until run time, though, so it gets executed at the appropriate time, such as each time through a loop, for example.
The compile-time effect is why you can access the variable in the BEGIN block. Be aware that any initialization on the my will take place after the BEGIN block is evaluated (and thus will overwrite any value the BEGIN might set.)
Yes, but you might want to be careful with this pattern, because something very similar will work differently than you might expect:
my $var = 5;
BEGIN { $var = 10 }
say $var; # 5