The Perl You Need To Know - Part 3
by Stas Bekman
|
Pages: 1, 2, 3
use(), require(), do(), %INC and @INC Explained
The @INC Array
@INC is a special Perl variable that is the equivalent to the
shell's PATH variable. Whereas PATH contains a list of
directories to search for executables, @INC contains a list of
directories from which Perl modules and libraries can be loaded.
When you use(), require() or do() a filename or a module, Perl gets a
list of directories from the @INC variable and searches them for
the file it was requested to load. If the file that you want to load
is not located in one of the listed directories, then you have to tell Perl
where to find the file. You can either provide a path relative to one
of the directories in @INC, or you can provide the full path to the
file.
The %INC Hash
%INC is another special Perl variable that is used to cache the
names of the files and the modules that were successfully loaded and
compiled by use(), require() or do() statements. Before attempting to
load a file or a module with use() or require(), Perl checks whether
it's already in the %INC hash. If it's there, then the loading and
therefore the compilation are not performed at all. Otherwise, the file
is loaded into memory and an attempt is made to compile it. do() does
unconditional loading -- no lookup in the %INC hash is made.
If the file is successfully loaded and compiled, then a new key-value pair
is added to %INC. The key is the name of the file or module as it
was passed to the one of the three functions we have just mentioned.
If it was found in any of the @INC directories except ".",
then the value is the full path to it in the file system.
The following examples will make it easier to understand the logic.
First, let's see what are the contents of @INC on my system:
% perl -e 'print join "\n", @INC'
/usr/lib/perl5/5.00503/i386-linux
/usr/lib/perl5/5.00503
/usr/lib/perl5/site_perl/5.005/i386-linux
/usr/lib/perl5/site_perl/5.005
.
Notice that . (current directory) is the last directory in the list.
Now let's load the module strict.pm and see the contents of %INC:
% perl -e 'use strict; print map {"$_ => $INC{$_}\n"} keys %INC'
strict.pm => /usr/lib/perl5/5.00503/strict.pm
Since strict.pm was found in /usr/lib/perl5/5.00503/ directory
and /usr/lib/perl5/5.00503/ is a part of @INC, %INC includes
the full path as the value for the key strict.pm.
Now let's create the simplest module in /tmp/test.pm:
test.pm
-------
1;
It does nothing, but returns a true value when loaded. Now let's load it in different ways:
% cd /tmp
% perl -e 'use test; print map {"$_ => $INC{$_}\n"} keys %INC'
test.pm => test.pm
Since the file was found relative to . (the current directory), the
relative path is inserted as the value. If we alter @INC by adding
/tmp to the end:
% cd /tmp
% perl -e 'BEGIN{push @INC, "/tmp"} use test; \
print map {"$_ => $INC{$_}\n"} keys %INC'
test.pm => test.pm
Here we still get the relative path, since the module was found first
relative to ".". The directory /tmp was placed after . in the
list. If we execute the same code from a different directory, then the
"." directory won't match,
% cd /
% perl -e 'BEGIN{push @INC, "/tmp"} use test; \
print map {"$_ => $INC{$_}\n"} keys %INC'
test.pm => /tmp/test.pm
so we get the full path. We can also prepend the path with unshift(),
so it will be used for matching before "." and therefore we will
get the full path as well:
% cd /tmp
% perl -e 'BEGIN{unshift @INC, "/tmp"} use test; \
print map {"$_ => $INC{$_}\n"} keys %INC'
test.pm => /tmp/test.pm
The code:
BEGIN{unshift @INC, "/tmp"}
can be replaced with the more elegant:
use lib "/tmp";
Which is almost equivalent to our BEGIN block and is the
recommended approach.
These approaches to modifying @INC can be labor intensive, since if
you want to move the script around in the file-system, then you have to
modify the path. This can be painful, for example, when you move your
scripts from development to a production server.
There is a module called FindBin that solves this problem in the
plain Perl world, but, unfortunately, it won't work under mod_perl,
since it's a module, and as any module, it's loaded only once. So the
first script using it will have all the settings correct, but the rest
of the scripts will not if they're in a different directory from the
first.
For the sake of completeness, I'll present this module anyway.
If you use this module, then you don't need to write a hard-coded path. The following snippet does all the work for you (the file is /tmp/load.pl):
load.pl
-------
#!/usr/bin/perl
use FindBin ();
use lib "$FindBin::Bin";
use test;
print "test.pm => $INC{'test.pm'}\n";
In the above example, $FindBin::Bin is equal to /tmp. If we move
the script somewhere else... e.g. /tmp/x in the code above
$FindBin::Bin equals /home/x.
% /tmp/load.pl
test.pm => /tmp/test.pm
This is just like use lib except that no hard-coded path is
required.
You can use this workaround to make it work under mod_perl.
do 'FindBin.pm';
unshift @INC, "$FindBin::Bin";
require test;
#maybe test::import( ... ) here if need to import stuff
This has a slight overhead, because it will load from disk and
recompile the FindBin module on each request. So it may not be
worth it.

