tags:

views:

91

answers:

6

How can you get current script directory in Perl?

This has to work even if the script is imported from another script (require).

This is not the current directory

Example:

#/aaa/foo.pl
require "../bbb/foo.pl"

#/bbb/bar.pl
# I want to obtain my directory (`/bbb/`)
print($mydir)

The script foo.pl could be executed in any ways and from any directory, like perl /aaa/foo.pl, or ./foo.pl.

A: 

Let's say you're looking for script.pl. You may be running it, or you may have included it. You don't know. So it either lies in the %INC table in the first case or as $PROGRAM_NAME (aka $0) in the second.

use strict;
use warnings;
use English qw<$PROGRAM_NAME>;    
use File::Basename qw<dirname>;
use File::Spec;
use List::Util qw<first>;

# Here we get the first entry that ends with 'script.pl'
my $key  = first { defined && m/\bscript\.pl$/ } keys %INC, $PROGRAM_NAME;
die "Could not find script.pl!" unless $key;
# Here we get the absolute path of the indicated path.
print File::Spec->rel2abs( dirname( $INC{ $key } || $key )), "\n";

Link to File::Basename, File::Spec, and List::Util

Axeman
When doing `do '/some/other/file.pl'` (or `require`, or whatever), `$0`/`$PROGRAM_NAME` won't be updated. This is what I believe the OP meant with "imported from another script".
rafl
@rafl, I've take care of that case by looking up in `%INC`.
Axeman
I guess it now works for require, but still not for `do`. If you found a way to fix that, I guess there'd still be various corner-cases. This is fiddly logic that at least I try to avoid unless absolutely necessary, which I suppose in the OP's circumstances it isn't.
rafl
@rafl, I tested through it through a `do` statement
Axeman
Oh, sorry. I thought `do` wouldn't update `%INC`. Try with having both `script.pl` and `/some/path/script.pl` as keys in `%INC`, i.e. two files with the same basename but in different directories, then. :-)
rafl
+3  A: 

What people usually do is

use FindBin '$Bin';

and then use $Bin as the base-directory of the running script. However, this won't work if you do things like

do '/some/other/file.pl';

and then expect $Bin to contain /some/other/ within that. I'm sure someone thought of something incredibly clever to work this around and you'll find it on CPAN somewhere, but a better approach might be to not include a program within a program, but to use Perl's wonderful ways of code-reuse that are much nicer than do and similar constructs. Modules, for example.

Those generally shouldn't care about what directory they were loaded from. If they really need to operate on some path, you can just pass that path to them.

rafl
This does not work when the script is imported (require).
Sorin Sbarnea
Yes, it doesn't, for exactly the reasons I described. And this isn't usually a problem if you rethink your approach, as I also described.
rafl
A: 

I use this snippet very often:

   use Cwd qw(realpath);
   use File::Basename;

   my $cwd = dirname(realpath($0));

This will give you the real path to the directory containing the currently running script. "real path" means all symlinks, "." and ".." resolved.

Jonathan Swartz
+1  A: 

Sorry for the other 4 responses but none of them worked, here is a solution that really works.

In below example that adds the lib directory to include path the $dirname will contain the path to the current script. This will work even if this script is included using require from another directory.

BEGIN {
   use File::Spec;
   use File::Basename;
   $dirname = dirname(File::Spec->rel2abs( __FILE__ )) . "/lib/";
}
use lib $dirname;
Sorin Sbarnea
I'm curious why the other things aren't working. Can you explain what else you have going on? Is there something you're doing before this bit of code?
brian d foy
Well, you didn't tell us that file itself was in play. Of course `__FILE__` works.
Axeman
+1  A: 

From perlfaq8's answer to How do I add the directory my program lives in to the module/library search path?


(contributed by brian d foy)

If you know the directory already, you can add it to @INC as you would for any other directory. You might if you know the directory at compile time:

use lib $directory;

The trick in this task is to find the directory. Before your script does anything else (such as a chdir), you can get the current working directory with the Cwd module, which comes with Perl:

BEGIN {
    use Cwd;
    our $directory = cwd;
    }

use lib $directory;

You can do a similar thing with the value of $0, which holds the script name. That might hold a relative path, but rel2abs can turn it into an absolute path. Once you have the

BEGIN {
    use File::Spec::Functions qw(rel2abs);
    use File::Basename qw(dirname);

    my $path   = rel2abs( $0 );
    our $directory = dirname( $path );
    }

use lib $directory;

The FindBin module, which comes with Perl, might work. It finds the directory of the currently running script and puts it in $Bin, which you can then use to construct the right library path:

use FindBin qw($Bin);

You can also use local::lib to do much of the same thing. Install modules using local::lib's settings then use the module in your program:

 use local::lib; # sets up a local lib at ~/perl5

See the local::lib documentation for more details.

brian d foy
Thanks for the effort put on this question but none of these methods are working because the only way to get the current file path is by using the `__FILE__` - `cwd`, `$0` and `$Bin` are not returning the right thing.
Sorin Sbarnea
Canyou explain that more? Once you have the location, no matter how you get it, you can construct the path to put into @INC. And, I didn't put that much work into this because I pulled it right out of the Perl docs.
brian d foy
+1  A: 

See Dir::Self CPAN module. This adds pseudo-constant __DIR__ to compliment __FILE__ & __LINE__.

use Dir::Self;

use lib __DIR__ . '/lib'; 

/I3az/

draegtun
Thanks, but have you wondered that I was planning to keep *all* Perl modules inside `/lib/`? ... this would include this module. I already found a solution, check my own answer - if you find a better one I will pick it.
Sorin Sbarnea
@Sorin Sbarnea: You need to specify your requirement clearly to avoid us *wondering* down the wrong alley(s) :) Put `use FindBin '$Bin'; use lib "$Bin/lib';` in your main program(s) then use `__DIR__` elsewhere when required (in modules).
draegtun