views:

137

answers:

5

I'm new to Perl, and I'm updating an old Perl website. Every .pl file seems to have this line at the top:

do "func.inc";

So I figured I could use this file to tag on a subroutine for global use.

func.inc

#!/usr/bin/perl
sub foobar
{
    return "Hello world";
}

index.pl

#!/usr/bin/perl
do "func.inc";
print "Content-type: text/html\n\n";
print foobar();

However, I get this error:

Undefined subroutine &main::foobar called at /path/to/index.pl line 4.

Both files are in the same directory, and there's tones of subs in func.inc already which are used throughout the website. However, the script works in the Linux production environment, but does not work for my Windows 7 dev environment (I'm using ActivePerl).

Update:

It looks like the file is not being included; the sub works if the file is included using an absolute path...

do "C:/path/to/func.inc";

... so it looks like relative paths don't work for my local dev environment, but they work in the production environment through. But this is no good for me, because the absolute path on my dev machine will not work for the live server.

How do I get do to work using a relative path on my Windows 7 dev machine?

Update 2:

I was using the Perl -T switch. Unfortunately this removes "." from @INC, and so stops us from using relative paths for do. I removed this switch and the old code is working now. I'm aware that this is not good practice, but unfortunately I'm working with old code, so it seems that I have no choice.

+1  A: 

I would first check if the file was actually loaded, the documentation for do mentions that it updates %INC if the file was found. There is also more information in the documentation.

adamse
How do I check if `%INC` was updated? I'm a Perl beginner.
nbolton
You don't want to check %INC. Don't use do. Use require instead.
brian d foy
@brian Thanks, I will make sure I avoid `do` for any new code I write!
nbolton
+3  A: 

You use the subroutine the same way that you'd use any other subroutine. It doesn't matter that you loaded it with do. However, you shouldn't use do for that. Check out the "Packages" chapter in Intermediate Perl for a detailed explanation of loading subroutines from other files. In short, use require instead.

See the documentation for do. You need to have func.inc (which you can also just call func.pl since pl is "perl library") in one of the directories where Perl will look for libraries. That might be different than the directory that has index.pl. Put func.inc in @INC somewhere, or add its directory to @INC. do also doesn't die if it can't load the file, so it doesn't tell you that it failed. That's why you shouldn't use do to load libraries. :)

brian d foy
Ah, I see. I will use packages for anything I implement myself, but I'm not sure it'd be wise to re-implement this existing system using packages, since I'm a Perl beginner.
nbolton
This has nothing to do with packages. You're a bit behind the curve, so you might want to catch up by reading _Learning Perl_ and _Intermediate Perl_. I explain all of this stuff in great detail.
brian d foy
@brian Are these the correct links? http://learn.perl.org/ and http://oreilly.com/catalog/9780596102067
nbolton
Well, those are the titles of my books. Buy them wherever you like. :) The learn.perl.org is not the website for my book.
brian d foy
@brian Thanks for clearing that up, I just wanted to make sure. And it's just as well I asked because I was going to assume that website was yours! ;-)
nbolton
A: 

make sure you have func.inc in the correct path.

do "func.inc" 

means you are saying func.inc is in the same path as your perl script. check the correct path and then do this

do "/path/func.inc"
ghostdog74
+2  A: 

Making sure the path is correct, use:


#!/usr/bin/perl 
require("func.inc");
print "Content-type: text/html\n\n"; 
print foobar(); 
drlouie - louierd
I was using the `-T` switch - doh! You're right though, the code should be using `require` as this checks that Perl can actually find the file instead of failing silently.
nbolton
Please, in every question, show us the real code that shows the problem. You have to show us exactly what you are doing.
brian d foy
Read the docs to see how `do` lets you know about failure. `-T` is a great safety mechanism. Even if you think you know what you're doing, you shouldn't disable it.
Greg Bacon
@brian I do apologise; being a new to Perl I did not realise it was important to describe exactly how I was running the script. I will make sure to think more carefully about this sort of stuff in future.
nbolton
@gbacon Thanks for the tip. However, I need my development environment to match the production environment, and since the production environment does not use `-T`, then it looks like I cannot (unless I want to spend my entire weekend fixing the old smelly code, which I don't). For any new code I write, or any new deployments I absolutely assure you that I will use `-T`! :)
nbolton
When switching `require` (yes, the better choice, or, better still, go with `use`) for `do` in the production environment, be prepared for breakage in case some code is inadvertently depending on silent failure.
Greg Bacon
+6  A: 

The perlfunc documentation for do reads

  • do EXPR
    Uses the value of EXPR as a filename and executes the contents of the file as a Perl script.

    do 'stat.pl';
    

    is just like

    eval `cat stat.pl`;
    

    except that it's more efficient and concise, keeps track of the current filename for error messages, searches the @INC directories, and updates %INC if the file is found.

So to see all this in action, say C:\Cygwin\tmp\mylib\func.inc looks like

sub hello {
  print "Hello, world!\n";
}

1;

and we make use of it in the following program:

#!/usr/bin/perl

use warnings;
use strict;

# your code may have unshift @INC, ...
use lib "C:/Cygwin/tmp/mylib";

my $func = "func.inc";

do $func;

# Now we can just call it. Note that with strict subs enabled,
# we have to use parentheses. We could also predeclare with
# use subs qw/ hello /; 
hello();

# do places func.inc's location in %INC
if ($INC{$func}) {
  print "$0: $func found at $INC{$func}\n";
}
else {
  die "$0: $func missing from %INC!";
}

Its output is

Hello, world!
./prog: func.inc found at C:/Cygwin/tmp/mylib/func.inc

As you've observed, do ain't always no crystal stair, which the do documentation explains:

If do cannot read the file, it returns undef and sets $! to the error. If do can read the file but cannot compile it, it returns undef and sets an error message in $@. If the file is successfully compiled, do returns the value of the last expression evaluated.

To check all these cases, we can no longer use simply do "func.inc" but

unless (defined do $func) {
  my $error = $! || $@;
  die "$0: do $func: $error";
}

Explanations for each case are below.

do cannot read the file

If we rename func.inc to nope.inc and rerun the program, we get

./prog: do func.inc: No such file or directory at ./prog line 12.

do can read the file but cannot compile it

Rename nope.inc back to func.inc and delete the closing curly brace in hello to make it look like

sub hello {
  print "Hello, world!\n";

1;

Running the program now, we get

./prog: do func.inc: Missing right curly or square bracket at C:/Cygwin/tmp/mylib/func.inc line 4, at end of line
syntax error at C:/Cygwin/tmp/mylib/func.inc line 4, at EOF

do can read the file and compile it, but it does not return a true value.

Delete the 1; at the end of func.inc to make it

sub hello {
  print "Hello, world!\n";
}

Now the output is

./prog: do func.inc:  at ./prog line 13.

So without a return value, success resembles failure. We could complicate the code that checks the result of do, but the better choice is to always return a true value at the end of Perl libraries and modules.

Note that the program runs correctly even with taint checking (-T) enabled. Try it and see! Be sure to read Taint mode and @INC in perlsec.

Greg Bacon
Hmm, I don't know what to say... When I remove `-T`, it works. When I add `-T` back it complains that the file does not exist. Maybe this is a Windows 7/ActivePerl problem?
nbolton
See the docs linked at the end of my answer: "When the taint mode (-T) is in effect, the '.' directory is removed from @INC, and the environment variables PERL5LIB and PERLLIB are ignored by Perl." Did you try running the program from my answer with and without `-T`?
Greg Bacon
Rather than dropping `-T`, would it be better to use `-T -I.`? Or does this defeat the purpose?
nbolton
Notice that my code uses `use lib "/absolute/path/to/lib";`. Relative paths such as `"."` are unsafe because they could allow your code to be tricked into running arbitrary code. The linked "Taint mode and `@INC`" section recommends `perl -T -Mlib=/foo program`. Running this way in development allows you to supply a slightly different environment without having to change the production code until you're ready.
Greg Bacon