44

I have a dir called foo, and in that I have lib and bin. The scripts in bin need stuff in lib. I do something like this:

#!perl
use strict;
use warnings;
use lib '../lib';
use Foo; # <-- comes from lib

But that means I have to be in the bin dir to run the script. Surely there is a better way. What's the Right Way to do this?

Kevin Panko
  • 8,356
  • 19
  • 50
  • 61
Frew Schmidt
  • 9,364
  • 16
  • 64
  • 86

8 Answers8

55

The standard FindBin module does what you want.

use FindBin;
use lib "$FindBin::Bin/../lib";

perldoc FindBin for more.

Martin Ba
  • 37,187
  • 33
  • 183
  • 337
Michael Cramer
  • 5,080
  • 1
  • 20
  • 16
  • According to [PerlMonks](http://www.perlmonks.org/) one should avoid FindBin: “[FindBin is broken (RE: How do I get the full path to the script executing?)](http://www.perlmonks.org/?node_id=41213)”. – Though, admittedly, that posting is over 10 years old (from the year 2000) and I am not fully sure that it is still accurate. – zrajm Sep 28 '13 at 01:03
  • Awesome! It's the only solution that works out of the box. I tried big guns like Begin {push @INC, "$mypath"}, and they never worked smoothly. Thank you! – kakyo Mar 06 '14 at 15:24
  • `FindBin` may not work well with `perl` bundled in `MSYS` or `MinGW` on Windows platform. You can type `where perl` on cmd to see if you use such kind of perl, use ActivePerl instead. – ifocus Oct 28 '20 at 06:40
5

Parse out the complete path to your .pl via __FILE__ and and tack the ../lib on the end or pop off the last element of split(/\//,__FILE__) and add /lib to that.

Kevin Panko
  • 8,356
  • 19
  • 50
  • 61
Trey
  • 11,032
  • 1
  • 23
  • 21
4

I generally use this technique. Its sadly inspired from my PHP days:

Its handy in situations where you know where a given file will be relative to the current one, and aren't sure of the entry points it may be called in or the surrounding environment at calltime.

However, I would generally use this technique only for test scripts which need dummy libraries for emulating things.

use File::Basename ();
use Cwd            ();
my $base_dir;
my $relative_path; 
BEGIN {
    $relative_path = '../../' # Path to base of project relative to the current file
    $base_dir = Cwd::realpath( File::Basename::dirname(__FILE__) .'/' . $relative_path );
}


use lib "${base_dir}/lib";
use Foo;

Ideally there should be some module somewhere that does this, if not, I'm half tempted to write one:

use Some::Module ();
use lib Some::Module::relative_self('../../lib', __FILE__ ); 
kikito
  • 51,734
  • 32
  • 149
  • 189
Kent Fredric
  • 56,416
  • 14
  • 107
  • 150
  • additonally its probably plausible to get __FILE__ out of caller() . /me ponders – Kent Fredric Apr 25 '09 at 08:21
  • I had a look at findbin, and it does cover the average use case, but the way it works for some other cases and how it does it in the code is awful. creating a variable at use() time is just really weird to me. – Kent Fredric Apr 26 '09 at 10:31
3

The "FindBin" module will only work if the directory that the perl script resides in is in your system PATH, else it will fail. To overcome that you can manipulate the $0 value to get your path-to-perl-module information and pass the value to use lib.

Something like this -

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

    #Covert the script path to absolute and get its directory name
    our $path = dirname( rel2abs($0) );

    #Replace the bin tag with lib to get module directory
    $path =~ s{bin/?$}{lib};
}

use lib $path;

EDIT: The FindBin module works just perfectly and can be used as described in Michael's answer. My understanding of its workings was incomplete and so led me to making the first comment which I now retract. Anyway, I don't see any reason why this method shouldn't work albeit with a few more lines than could be achieved using FindBin (TMTOWTDI).

Community
  • 1
  • 1
aks
  • 24,359
  • 3
  • 32
  • 35
  • This recently bit me because rel2abs might end up giving back the wrong Windows path. File::Spec::Win32::rel2abs might end up calling Cwd::getdcwd and return the wrong thing. I had to submit a patch to Archive::Extract to get around this: http://rt.cpan.org/Public/Bug/Display.html?id=43278 – brian d foy Apr 25 '09 at 18:09
  • FindBin works just fine for locations that aren't in your path. IIRC it fails if there is a (different) file with the same name as your script in your path. – Michael Carman Apr 25 '09 at 19:02
  • Your method is actually similar to what FindBin already does, but without the cross-platform love. Really. FindBin is the way to go. – Michael Cramer Apr 25 '09 at 20:51
1
use lib './';

has been working for me with Perl v5.14.2 on a linux box to include files in the same directory as my script.

Perhaps it would work for you to move your lib and bin directories under your script's working directory and try to reference them using

use lib './bin/';
Paul Roub
  • 36,322
  • 27
  • 84
  • 93
firetiger77
  • 351
  • 3
  • 9
  • 1
    I am not sure if Frew was looking for this solution. Correct me if I am wrong but he actually wanted a better way to include libraries outside the working directory. The reorg you are suggesting may not be possible for all projects – comiventor Nov 15 '17 at 20:04
0

My solution:

use lib substr(__FILE__, 0, rindex (__FILE__, "/"));
Anthony
  • 12,177
  • 9
  • 69
  • 105
Leo
  • 1,433
  • 23
  • 40
  • 1
    Seems a bit cludgy compared to using a regex? Also note that `__FILE__` may sometimes contain a *relative* filename (e.g. `./myscript`) which might (or might not) bite you. – zrajm Sep 28 '13 at 00:59
0

Just to add my own two cents to this collection of answers, I usually solve this problem using something along these lines:

use lib do {
    use Cwd 'realpath';
    my ($dir) = __FILE__ =~ m{^(.*)/};  # $dir = path of current file
    realpath("$dir/../lib");            # path of '../lib' relative to $dir
};

I like using a do block because everything needed is so neatly contained therein. If you ever need to copy/paste, or try to understand your code at a later time you don't have to look for a separate BEGIN block or anything like that.

The above code naïvely assumes that / is used as a dir/filename separator.

zrajm
  • 1,361
  • 1
  • 12
  • 21
-3

How about:

BEGIN: {
    push @INC, '/full/path/to/lib';
}

To do a relative reference would assume that you're going to keep it in the bin dir, so insert the relative reference there instead.

Edward Q. Bridges
  • 16,712
  • 8
  • 35
  • 42
  • note the question: this doesn't really tell me how to do a relative use. – Frew Schmidt Apr 25 '09 at 00:38
  • You probably want to put the special paths at the front of @INC instead of at the end. – brian d foy Apr 25 '09 at 18:10
  • typing too quickly :-)... @frew - the source code is for a fully qualified path -- in my note i explain to insert a relative path. @bdf - Yes you're prb. right, to do that would be an 'unshift' instead of a push. – Edward Q. Bridges Apr 26 '09 at 00:07