2

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.

brian d foy
  • 129,424
  • 31
  • 207
  • 592
Cristofayre
  • 121
  • 1
  • 11
  • Is the second `require "./paths.pl"` javscript code? The first one looks like it is inside the Perl script `script1.pl` – Håkon Hægland Dec 18 '20 at 20:06
  • 1
    *"XHTMLREQUEST script1.pl"* : what is XHTMLREQUEST ? Did you mean `XMLHttpRequest` ? – Håkon Hægland Dec 18 '20 at 22:26
  • 1
    Could you remove the web stuff and just leave the code an question about Perl? It's confusing two unrelated issues. – lordadmira Dec 18 '20 at 22:34
  • *"when I call a script via XHTMLRequest ..."* : What status code did the request return ? Looks like it could be the problem described here: [Doesn't Perl include current directory in @INC by default?](https://stackoverflow.com/q/46549671/2173773) – Håkon Hægland Dec 18 '20 at 23:20
  • 1
    "_Am I moving into the territory of "modules"_" --- yeah; that's what you need to do. For a direct answer see [this post](https://stackoverflow.com/a/4548155/4653379), while I'd rather nudge you to go the way of [this post](https://stackoverflow.com/a/41880594/4653379) instead (which also has a direct answer, as well) – zdim Dec 19 '20 at 05:51
  • No both are perl; it was just the way I wrote it. Whoops, I meant XHTMLRequest; (I wrote it as I was 'clocking off' for the evening !!) I included the web stuff as that's when the perl works OK with "include paths.pl".The HTML 'calls' a perl script to display page. That's OK. Within that page, it jumps to an AJAX request to show data on page. That's when the Error 500 / "$x requires explitcit package ... did you forget to declare..." This morn, I tried the XHTMLRequest with a full URL rather than just the script name, but same prob – Cristofayre Dec 19 '20 at 08:53
  • 2
    In essence, instead of writing `my $foo = "bar"` you just want to write `$foo = "bar"` in your program file. What is the point of that? If someone were to read your code, they would have to scan through another file to see where the variables were declared. I fail to see the value here. Variables should be declared as close to where they are being used as possible, and in as small lexical scope as possible. If you are putting 50 variable declarations at the top of your program, it is more likely you are not exercising best practice, and you should reconsider how you write code. – TLP Dec 19 '20 at 11:01

2 Answers2

4

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.

brian d foy
  • 129,424
  • 31
  • 207
  • 592
  • Another long detailed post, (to which than you) Local scopes make sense, but the reason I didn't use them is because a variable may be set by one function, and then used again by another function much further down, (say set line 50, used again at line 700 etc) I gave up trying to work that way; would take too long to sort. I really wanted it to set "http" or "https" or a folder name. In the end, to solve that, I used $ENV{'HTTP_REFERER'} and broke it into component parts, so it will use "http" offline, and "https" when server sends online. (Now gonna read response again) – Cristofayre Dec 19 '20 at 16:14
  • Just to give a simple example. Customer has a "$directory" value. I might load their data at the top of the script, process it, and then save it via a function at line 500. So (I think) it has GOT to be declared at the start. Similarly, the data it pulls in is split into array, and each array named to avoid confusion "$name=$tmp[0]"; When that is saved out at line 500, ($tmp[0]=$name, @customer= join "|", @tmp) it cannot use the local scope function, so $name HAS to be global. (That is my understanding) – Cristofayre Dec 19 '20 at 16:22
  • 1
    Yeah, that's why I say you want a configuration solution, and to have arguments to subroutines when they need data rather than accessing globals. It sounds like you are making spaghetti code. – brian d foy Dec 19 '20 at 16:54
  • It would help people immensely if you put that sort of information in your question. – brian d foy Dec 19 '20 at 16:54
1

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

lordadmira
  • 1,807
  • 5
  • 14
  • Not read the full reply yet, but just to say, the $a and $b were just token variables; appreciate those are actually part of the sort routine. – Cristofayre Dec 19 '20 at 08:30
  • 1
    Now I've read the full reply - IT'S A FANTASTIC REPLY. It explains everything in clear steps as to why - what I'm proposing is (almost) a non-starer; I'll just have to keep the block of my ($day,$month,$year,$page ... etc); at the top of each script. I just thought it would be "tidy" to lump the vars in one place rather than dotted around. (The original reasoning was - in my offline tests - I am using "http" whereas online it's "https:", and I didn't want to remember to wade through 10 scripts to find all occurances!! – Cristofayre Dec 19 '20 at 08:39
  • 1
    I think what I might do is this: my ($site_url, $folder); require "paths.pl;" my ($date,$month,$year ...) ** The first loads the two 'globals' used by all scripts, and the my all the local scope. You said "boilerplate.pm" method should work, but not to use it. So I ain't even gonna try !! Again, thanks for the FANTASTIC response. Very enlightening – Cristofayre Dec 19 '20 at 08:43
  • 1
    Please don't resort to source filters to make it easier to do things you shouldn't be doing anyway. – brian d foy Dec 19 '20 at 14:43
  • BTW, if disparate declaring and using of variables is becoming an issue and your subroutine calls are having endless lists of arguments that you can't remember, it's time to start using objects. That way you keep all the data and functions (called methods) together in one place and you don't have to remember where they are anymore. – lordadmira Dec 19 '20 at 20:50
  • 1
    If your subroutine calls are having endless lists of arguments, it might be time for objects. It might also be time for smaller subroutines. Merely creating a "God" object doesn't solve the solution of complicated code. – brian d foy Dec 20 '20 at 06:30
  • I was using hyperbole, but you are right. I did work with a guy however who had 15 arguments on his sub calls because as he added new features to the framework, the values had to be passed through all of the subroutines to get where they were used. All to avoid globals. Crazy! – lordadmira Dec 20 '20 at 08:45
  • Hallo? Terminology I'm totally new to. Objects?? Methods?? (I'm just about able to understand calls to functions at this time! In essense, the function uses global variable, sets that global variable, so that said value can be seen by the next function loading a global. Is that the wrong approach?) (if you like, writing a note on a piece of paper, which is then read by someone else, who adds to the note, which is then read by another person) – Cristofayre Dec 20 '20 at 10:48
  • Er, right .... I'll leave objects to the "heavy duty" programmers ! Way beyond my scope of "classes" and "blessing" Maybe if I get a month or two to try and break it down into small chunks! – Cristofayre Dec 20 '20 at 11:04
  • @Cristofayre Don't worry, you'll get there. – lordadmira Dec 20 '20 at 19:48