1

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

Developus
  • 1,400
  • 2
  • 14
  • 50

3 Answers3

2

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.

shawnhcorey
  • 3,545
  • 1
  • 15
  • 17
  • I know. But what if user has not installed modules (`eval...`) and I want to show help (-h) always? When I process option after begin block, I will get "No modules" message all the time. When I process option in begin block, when I pass -h otpion I'll get first "Some help" message instead of "No modules". Is it possible to copy values from begin block outside? – Developus Dec 10 '16 at 17:48
  • 2
    Why bother doing that in `BEGIN` either? If you really need custom help with module installs, then you can load modules using `require` at runtime. – Sobrique Dec 10 '16 at 17:53
  • use `require` instead of `use`? – Developus Dec 10 '16 at 21:23
2

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.

Borodin
  • 126,100
  • 9
  • 70
  • 144
0

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.


zdim
  • 64,580
  • 5
  • 52
  • 81
  • Looks good, but I get an error stack trace if module is not installed. The idea is if module is not installed I want to show inform message that module is not installed and exit. But if I run a program with -h option I want to show a help message even if module is not installed. Is it possible? – Developus Dec 11 '16 at 16:01
  • In the first example, are you sure `$ok_Term_Size_Any` will be set after BEGIn block? I think it will have default value or error will be thrown because it is not initialized. – Developus Dec 11 '16 at 16:23
  • *"your code to process the options runs before the variables are assigned 40"* This doesn't explain why the `BEGIN` block is executed before `40` is assigned to the variable. The assignment precedes the `BEGIN` block and isn't in a `BEGIN` block itself? – Borodin Dec 11 '16 at 20:20
  • @Borodin I don't see how it doesn't explain it ...? The processing of options runs first (before the runtime-assignment), because it is in a `BEGIN` block, while the assignment to `40` happens in the run phase, which comes later. The first sentence establishes this. Are you saying that the wording is unclear? – zdim Dec 11 '16 at 23:47
  • 1
    @allocer I don't understand. Try this `my $ok_NoMod; BEGIN { eval 'use NoMod'; $ok_NoMod = 1 unless $@ }; if ($ok_NoMod) { say "Have NoMod" } else { say "No 'NoMod' }`. Add warnings and strict. It says `'No NoMod'` and if I use a module that I do have (instead of this "_NoMod_") then it says `'Have ...'`. This is what you need, no? – zdim Dec 11 '16 at 23:52
  • @zdim: On the face of it, `use strict; my $var = 42; BEGIN { $var = 84 }; print $var;` should either die in the `BEGIN` block because there is no such variable, or print `84`. Neither of these happens, and you haven't explained why. I'm sorry, I know it's easy to overlook things that gave become "obvious", and your answer is otherwise a great one. – Borodin Dec 11 '16 at 23:57
  • @Borodin Oh, I see what you mean. I thought that the sentence after the code block was a good place to do it. But, you are right, it should be explained upfront. I'll amend the first paragraph. – zdim Dec 12 '16 at 00:00
  • @zdim: That sentence doesn't address the phase when the *assignment* occurs, or that it is different from when the *declaration* happens. That concept is the core of the answer. – Borodin Dec 12 '16 at 00:02
  • @Borodin I edited, but will check again in a few minutes. Apparently it is all too easy to mess up the business of what things are explained and when. It wasn't done well at all -- when I looked it now. It seemed just fine last night. Thank you. – zdim Dec 12 '16 at 00:20
  • @allocer I see your second comment only now, for some reason. If the module loaded fine (and so `$@` is empty) the flag is set -- the variable is assigned `1`. If it didn't load (so `$@` has stuff) the flag isn't set -- it stays `undef`. So you can use it later as a flag, `if ($ok_Term_Size_Any) ...`. So you can decide what messages to print, etc. – zdim Dec 12 '16 at 09:12
  • Guys, thanks for help. It helped me with first example. Sorry for my stupid questions ;) – Developus Dec 12 '16 at 15:51