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

I am seeking to expand my understanding of the interplay between BEGIN and END blocks, strictures and scoping.

Assume that I have on my system a file C:/shb.

Let's call this Case 1:

# perl BEGIN { $shbstatus = (-f "C:/shb") ? 1 : 0; } END { $shbstatus ? print "shb identified\n" : print "shb was not identified\n"; } use warnings; print "XYZ\n"; print "$shbstatus\n";

I expect the output to be ...

XYZ 1 shb existed

... and indeed it is.

Case 2: I modify the variable declared inside the BEGIN block to make it private.

# perl BEGIN { my $shbstatus = (-f "C:/shb") ? 1 : 0; }

Since $shbstatus is now private to the BEGIN block, I expect the output to be ...

XYZ Use of uninitialized value in concatenation (.) or string at begin.pl +line 15. shb was not identified

... and so it is. C:/shb still exists; it just wasn't identified.

Case 3: I restore the variable declared inside the BEGIN block to global status, but apply strictures before the BEGIN block.

# perl use strict; BEGIN { $shbstatus = (-f "C:/shb") ? 1 : 0; }

Part of me says, "$shbstatus is inside a BEGIN block, which gets executed first, so it won't throw an error." Another part of me says, "No, use strict precedes the BEGIN block in the file, so $shbstatusmust be qualified." It turns out that the latter is correct, as the output is:

Global symbol "$shbstatus" requires explicit package name at begin.pl +line 4. BEGIN not safe after errors--compilation aborted at begin.pl line 5. shell returned 9

Case 4: But now I put use strict inside its own BEGIN block which precedes that containing $shbstatus.

Update: I should have included this for clarity:

# perl BEGIN { use strict; } BEGIN { $shbstatus = (-f "C:/shb") ? 1 : 0; }

Frankly, I wouldn't have expected the output in this case to be any different from Case 3. To my surprise, everything compiles and prints nicely:

XYZ 1 shb identified

... which implies that the global variable $shbstatus was not restricted by the use strict call inside the first BEGIN block.

Why not?

(I should add that my goal here is to write a test file that first tests for the existence of a file on disk, then does one thing if the file is present (temporarily rename it) and a different thing if it is not (create it), then makes sure that the presence or absence of the file on the disk is unchanged at the end of the test notwithstanding any dies or croaks in the interim. I've read perlmod and perlsub but am still not entirely clear.)

Thanks in advance.

Jim Keenan

Replies are listed 'Best First'.
Re: BEGIN and END blocks, use strict and scoping
by ikegami (Patriarch) on Sep 23, 2005 at 20:32 UTC

    ... which implies that the global variable $shbstatus was not restricted by the use strict call inside the first BEGIN block.

    Why not?

    strict is block scoped, so its effect stops at the end of the BEGIN block.

    The following works:

    use strict; use warnings; my $shbstatus; BEGIN { $shbstatus = (-f "C:/shb") ? 1 : 0; } END { $shbstatus ? print "shb identified\n" : print "shb was not identified\n"; } print "XYZ\n"; print "$shbstatus\n";

    Update: Things happen in the following order:

    • use strict; is compiled.
    • use strict; is executed. [*]
    • use warnings; is compiled.
    • use warnings; is executed. [*]
    • my $shbstatus; is compiled, which creates $shbstatus.
    • BEGIN { ... } is compiled. Closes over $shbstatus [**].
    • BEGIN { ... } is executed.
    • END { ... } is compiled. Closes over $shbstatus [**].
    • print ... is compiled.
    • my $shbstatus; is executed, which does nothing.
    • print ... is executed.
    • END { ... } is executed.

    [*] It'll affect the current block (which is the current file) and nested blocks (which includes the BEGIN and END blocks, and sub definitions, loops, etc). It will *not* affect modules used/required by these blocks or subs called by these blocks.

    [**] When a sub closes over a variable, it means the variable will be available to the sub (including special subs BEGIN and END) when the sub is executed, even if the variable is no longer in scope. Search for documentation on closures for more detail.

      Thanks for the detailed response!
Re: BEGIN and END blocks, use strict and scoping
by perrin (Chancellor) on Sep 23, 2005 at 20:29 UTC
    Because the strict pragma is block-scoped and your BEGIN is a block.
Re: BEGIN and END blocks, use strict and scoping
by dragonchild (Archbishop) on Sep 24, 2005 at 02:59 UTC
    Note: In your "Case 3", you had an error in understanding. "use" statements are implicitly wrapped in a BEGIN block.

    As for your goal, why not do something like:

    #! perl use strict; my $filename = 'C:/shb'; my $action; if ( -f $filename ) { $action = 'rename'; } else { $action = 'create'; } END { if ($action eq 'rename') { } else { } }

    My criteria for good software:
    1. Does it work?
    2. Can someone else come in, make a change, and be reasonably certain no bugs were introduced?
      If I understand you correctly, the END block can make use of lexical variables defined in the file before the END block itself. This would obviate the need for using a BEGIN block to test for the presence/absence on disk of a given file. It would also mean that I could place use strict; in its customary position.

      The following little script should illustrate the point in various ways depending on whether the file present is indeed present and depending on whether the croak call is live or commented-out.

      Update: While the solution above guarantees that the testing process is (more) non-destructive, it does have one drawback, AFACIT. When the croak occurs earlier in the file, (1) the total number of tests run is reduced by the quantity lying between the croak and the END block and (2) the numbers associated with the tests inside the END block are no longer at the end of the range predicted by the plan.

      Revised example:

      ... produces (assuming that present was indeed present at the outset):

      1..7 ok 1 - original file no longer present ok 2 - hidden file present Trick is false at end.pl line 28 ok 3 - hidden file no longer present ok 4 - original file once again present # Looks like you planned 7 tests but only ran 4. # Looks like your test died just after 4.
      Tests 3 and 4 above are not the third and fourth as you would intuitively predict from eyeballing the file. You become more dependent on the croak message in locating the error within the file.

      Thanks!

      Jim Keenan

        Now, you move from having an issue with understanding how use and BEGIN work to an issue with how Test::More works. Modify your code as such:
        unless ( $trick ) { SKIP: { skip 3, "About to croak!\n"; } croak; } else { # Do your three other tests here. }
        You just have to let Test::More know what's going on with regards to your test plan. It's called a plan for a reason.

        My criteria for good software:
        1. Does it work?
        2. Can someone else come in, make a change, and be reasonably certain no bugs were introduced?
Re: BEGIN and END blocks, use strict and scoping
by Errto (Vicar) on Sep 23, 2005 at 23:05 UTC
    You can say something like this:
    BEGIN { require strict; strict->import; } ...other stuff...
    which will have the same effect as having said use strict at the top level of the program.
Re: BEGIN and END blocks, use strict and scoping
by Moron (Curate) on Sep 24, 2005 at 13:44 UTC
    Although in perl, you don't need the BEGIN block to achieve the stated goal. It would be different if you were using awk instead of perl, because in awk, the only place to put any code that must execute before the first line if input is read in is the BEGIN block.

    Not so with perl in which the BEGIN block is intended for code that must be executed before anything else, perl would otherwise select certain code for processing (such as use or require of an external file), earlier than is indicated by its position in the program. The BEGIN block overrides that feature for its contents.

    But the first line of code OUTSIDE any BEGIN block will still get executed before the file is opened, e.g.:

    #!/usr/bin/perl use strict; use warnings; my $file = '/path/file.dat'; my $exists = ( -e $file ); my $normal = ( -f $file ); $exists and (not $normal) and die "$file is unsuitable for this progra +m"; my $iocmd = $normal ? "<$file" : ">$file"; # until now we could check the existence of the file # and how to open it and we did not need BEGIN open my $fh, $iocmd or die "$!, for $file"; my @content = $normal ? <$fh> # only now is the file read in if exists :( "I am\n", "the default\n", "contents\n" ); $normal or print $fh @content; # or printed to otherwise close $fh or die "$!, for $file"; # now both the file and @content contain either tbe # program default or previously stored content depending on # whether the file existed.

    -M

    Free your mind