简体   繁体   中英

Perl - copy variables value out of BEGIN block

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 .


The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM