Can variable declarations be placed in a common script

Viewed 134

Before I start, the whole 'concept' may be technically impossible; hopefully someone will have more knowledge about such things, and advise me.

With Perl, you can "declare" global variables at the start of a script via my / our thus:

my ($a,$b,$c ..)

That's fine with a few unique variables. But I am using about 50 of them ... and the same names (not values) are used by five scripts. Rather than having to place huge my( ...) blocks at the start of each file, I'm wondering if there is a way to create them in one script. Note: Declare the namespace, not their values.

I have tried placing them all in a single file, with the shebang at the top, and a 1 at the bottom, and then tried "require", "use" and "do" to load them in. But - at certain times -the script complains it cannot find the global package name. (Maybe the "paths.pl" is setting up the global space relative to itself - which cannot be 'seen' by the other scripts)

Looking on Google, somebody suggested setting variables in the second file, and still setting the my in the calling script ... but that is defeating the object of what I'm trying to do, which is simply declare the name space once, and setting the values in another script

** So far, it seems if I go from a link in an HTML page to a perl script, the above method works. But when I call a script via XHTMLRequest using a similar setup, it cannot find the $a, $b, $c etc within the "paths" script

HTML
<form method="post" action="/cgi-bin/track/script1.pl>
<input type="submit" value="send"></form>

Perl: (script1.pl)
#shebang
require "./paths.pl"
$a=1;
$b="test";
print "content-type: text/html\n\n";
print "$a $b";

Paths.pl
our($a,
$b,
$c ...
)1;

Seems to work OK, with no errors. But ...
# Shebang
require "./paths.pl"
XHTMLREQUEST script1.pl

Now it complains it cannot find $a or $b etc as an "explicit package" for "script1.pl"

Am I moving into the territory of "modules" - of which I know little. Please bear in mind, I am NOT declaring values within the linked file, but rather setting up the 'global space' so that they can be used by all scripts which declare their own values.

(On a tangent, I thought - in the past - a file in the same directory could be accessed as "paths.pl" -but it won't accept that, and it insists on "./" Maybe this is part of the problem. I have tried absolute and relative paths too, from "url/cgi-bin/track/" to "/cgi-bin/track" but can't seem to get that to work either)

I'm fairly certain it's finding the paths file as I placed a "my value" before the require, and set a string within paths, and it was able to print it out.

2 Answers

First, lexical (my) variables only exist in their scope. A file is a scope, so they only exist in their file. You are now trying to work around that, and when you find yourself fighting the language that way, you should realize that you are doing it wrong.

You should move away from declaring all variables in one go at the top of a program. Declare them near the scope you want to use them, and declare them in the smallest scope possible.

You say that you want to "Set up a global space", so I think you might misunderstand something. If you want to declare a lexical variable in some scope, you just do it. You don't have to do anything else to make that possible.

Instead of this:

my( $foo, $bar, $baz );

$foo = 5;
sub do_it { $bar = 9; ... }
while( ... ) { $baz = 6; ... }

Declare the variable just where you want them:

my $foo = 5;
sub do_it { my $bar = 9; ... }
while( ... ) { my $baz = 6; ... }

Every lexical variable should exist in the smallest scope that can tolerate it. That way nothing else can mess with it and it doesn't retain values from previous operations when it shouldn't. That's the point of them, after all.

When you declare them to be file scoped, then don't declare them in the scope that uses them, you might have two unrelated uses of the same name conflicting with each other. One of the main benefits of lexical variables is that you don't have to know the names of any other variables in scope or in the program:

my( $foo, ... );

while( ... ) {
   $foo = ...;
   do_something();
   ...
   }

sub do_something {
   $foo = ...;
   }

Are those uses of $foo in the while and the sub the same, or do they accidentally have the same name? That's a cruel question to leave up to the maintenance program.

If they are the same thing, make the subroutine get its value from its argument list instead. You can use the same names, but since each scope has it's own lexical variables, they don't interfere with each other:

while( ... ) {
   my $foo = ...;
   do_something($foo);
   ...
   }

sub do_something {
   my( $foo ) = @_;
   }   

See also:

You say you aren't doing what I'm about to explain, but other people may want to do something similar to share values. Since you are sharing the same variable names across programs, I suspect that this is actually what it going on, though.

In that case, there are many modules on CPAN that can do that job. What you choose depends on what sort of stuff you are trying to share between programs. I have a chapter in Mastering Perl all about it.

You might be able to get away with something like this, where one module defines all the values and makes them available for export:

# in Local/Config.pm
package Local::Config;
use Exporter qw(import);
our @EXPORT = qw( $foo $bar );        
our $foo = 'Some value';
our $bar = 'Different value';
1;

To use this, merely load it with use. It will automatically import the variables that you put in @EXPORT:

# in some program
use Local::Config;

We cover lots of this sort of stuff in Intermediate Perl.

What you want to do here is a form of boilerplate management. Shoving variable declarations into a module or class file. This is a laudable goal. In fact you should shove as much boilerplate into that other module as possible. It makes it far easier to keep consistent behavior across the many scripts in a project. However shoving variables in there will not be as easy as you think.

First of all, $a and $b are special variables reserved for use in sort blocks so they never have to be declared. So using them here will not validate your test. require always searches for the file in @INC. See perlfunc require.

To declare a variable it has to be done at compile time. our, my, and state all operate at compile time and legalize a symbol in a lexical scope. Since a module is a scope, and require and do both create a scope for that file, there is no way to have our (let alone my and state) reach back to a parent scope to declare a symbol.

This leaves you with two options. Export package globals back to the calling script or munge the script with a source filter. Both of these will give you heartburn. Remember that it has to be done at compile time.

In the interest of computer science, here's how you would do it (but don't do it).

#boilerplate.pm
use strict;
use vars qw/$foo $bar/;
1;
__END__

#script.pl
use strict;
use boilerplate;
$foo = "foo here";

use vars is how you declare package globals when strict is in effect. Package globals are unscoped ("global") so it doesn't matter what scope or file they're declared in. (NB: our does not create a global like my creates a lexical. our creates a lexical alias to a global, thus exposing whatever is there.) Notice that boilerplate.pm has no package declaration. It will inherit whatever called it which is what you want.

The second way using source filters is devious. You create a module that rewrites the source code of your script on the fly. See Filter::Simple and perlfilter for more information. This only works on real scripts, not perl -e ....

#boilerplate.pm
package boilerplate;
use strict; use diagnostics;
use Filter::Simple;

my $injection = '
our ($foo, $bar);
my ($baz);
';

FILTER { s/__FILTER__/$injection/; }

__END__

#script.pl
use strict; use diagnostics;
use boilerplate;
__FILTER__
$foo = "foo here";

You can make any number of filtering tokens or scenarios for code substitution. e.g. use boilerplate qw/D2_loadout/;

These are the only ways to do it with standard Perl. There are modules that let you meddle with calling scopes through various B modules but you're on your own there. Thanks for the question!

HTH

Related