Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl: the Markov chain saw
 
PerlMonks  

How to determine absolute path of current Perl file?

by hakonhagland (Scribe)
on Feb 29, 2016 at 09:00 UTC ( [id://1156424]=perlquestion: print w/replies, xml ) Need Help??

hakonhagland has asked for the wisdom of the Perl Monks concerning the following question:

Background:

I am trying to improve the CPAN module Data::Printer such that it can display the variable name automatically (similarly to Data::Dumper::Simple). However, I will try to do this without using PadWalker or a source filter ( perlfilter or Filter::Util::Call ). Instead, I will try read the source file by exploiting filename and line number of current source from caller. Then parsing the line with PPI to recover the variable name.

Problem

My current problem is to determine the absolute path of the current Perl file. The problem occurs when the filename (as given by (caller)[1] ) is relative. For example running perl ./test/test.pl with ./test/test.pl:

use My::Module; My::Module::print_file_name();
and ./lib/My/Module.pm:
package My::Module; use feature qw(say); sub print_file_name { say __FILE__; } 1;
will give output lib/My/Module.pm (the same will perl function caller) which is a relative path name. My first attempt was to use FindBin (since I cannot rely on the current directory being the same as when the script was run):
if ( !File::Spec->file_name_is_absolute( $filename ) ) { $filename = File::Spec->rel2abs( $filename, $FindBin::Bin ); }
but the problem is that $filename is not relative to $FindBin::Bin so it will not work.

Then I considered using Module::Path, but this seemed like overkill for my use case. What I think I need is a simple module, say Cwd::Initial which gives me the initial working directory. For example:

package Cwd::Initial; use strict; use warnings; use Cwd (); our $cwd; our $VERSION = '0.10'; sub _getcwd { $cwd = Cwd::getcwd(); } BEGIN { _getcwd(); } sub getcwd { return $cwd; } 1;

Questions

Some questions:
  • Have I missed something? Is this functionality already available at CPAN?
  • If not, is the module Cwd::Initial really necessary? Should I instead suggest a modification to FindBin to expose the initial directory as part of its API?
  • Or, is the use case so special that I should rather not rely on a CPAN module to provide this but instead use a BEGIN{} block and a package variable our $inital_cwd?

Replies are listed 'Best First'.
Re: How to determine absolute path of current Perl file?
by Discipulus (Canon) on Feb 29, 2016 at 09:37 UTC
    hello hakonhagland and welcome to the monastery,

    probably i do not fully understand your question (in case i'm sorry) but is not abs_path from core module Cwd what you need? anyway the last module you cited is fairly simple to understand: it cycles @INC and tries possible instances of file, the same way Perl does when you use a module.

    Just a dozen of lines plus an half doze to handle the path separator of different OS.

    L*

    There are no rules, there are no thumbs..
    Reinvent the wheel, then learn The Wheel; may be one day you reinvent one of THE WHEELS.
      Hi Discipulus! Thanks for your reply. My understanding is that Cwd::abs_path() uses the current directory, but that directory may not be equal to the initial current directory (for example if the user has called chdir). Regarding Module::Path, yes it could work.. but not all perl files are modules, for example the main script ./test/test.pl. Also I think there is no reason to cycle all the @INC directories for the given module, when it always (if the path is relative) will be under (that is, relative to) the initial current directory.
Re: How to determine absolute path of current Perl file?
by Hosen1989 (Scribe) on Feb 29, 2016 at 12:47 UTC

    Hi

    I had faced some issue like yours (I think).

    try next lines in your script (It work for me):

    use Cwd; my $this_file_full_path = Cwd::abs_path(__FILE__); print $this_file_full_path;

    BR

    Hosen

Re: How to determine absolute path of current Perl file?
by 1nickt (Canon) on Feb 29, 2016 at 11:44 UTC

    Hi hakonhagland,

    I use the following in scripts that I write on one system to run on another:

    use Cwd qw/ realpath /; my $path = realpath($0);
    (note that realpath() is just an alias for abs_path())

    Hope this helps!


    The way forward always starts with a minimal test.
      Hi 1nickt! Thanks for your reply. Yes realpath($0) will give me the absolute path of $0 but it will be relative to the current directory, and not the initial current directory. For example, if the user script has changed directory with chdir it will not give a correct path name I think.
        hello, this i was used to do in the script perspective:

        my $origin = File::Spec->file_name_is_absolute($0) ? $0 : File::Spec-> +rel2abs($0) ; my ($drive,$directories,$file) = File::Spec->splitpath( $origin ); my $path = File::Spec->catdir($drive,$directories); ################ check starting path my $current = File::Spec->rel2abs('.'); if ($current ne $path) { (chdir $path and print "OK chdir to $path\n")|| die "FATAL una +ble to change directory to '$path'."; }

        But if now i understand, you want in a module.pm get the absolute path of the script.pl that used it.

        If so you can have in the module a BEGIN block that executes before any attempt for (user's) script to change directory. If in the script there is a chdir in a BEGIN block before loading your module.. you are lost.

        L*

        There are no rules, there are no thumbs..
        Reinvent the wheel, then learn The Wheel; may be one day you reinvent one of THE WHEELS.

        Then don't change the directory or try to find the current directory at program start by using a BEGIN block.

Re: How to determine absolute path of current Perl file?
by Tanktalus (Canon) on Feb 29, 2016 at 22:27 UTC

    Avoiding filters: good.

    Avoiding PadWalker: mind saying why?

    Given my experience with PPI, I'm going to guess that you'll have a larger dependency list and more fragile code than if you used PadWalker.

    Having said that, if you want the full path name to the current file, just use __FILE__. And if you want the full path name to the file that just called you, just use (caller)[1]. As a bonus, caller will also give you the line number that you were called from.

    I'm not sure what else you're looking for, but I'd like to discourage the use of PPI for this. I do use PPI to parse and rewrite code, and that was marginally painful. This just seems ... well, really painful. The authors of PPI may have differing opinions :)

      Hi Tanktalus!

      Avoiding PadWalker: mind saying why?

      The reluctance to use PadWalker was merely based on information I could read about it in the source and Pod documentation of Data::Dumper::Simple. It says:

      Note that if you strongly object to source filters, I've also released Data::Dumper::Names. It does what this module does by it uses PadWalker instead of a source filter. Unfortunately, it has a few limitations and is not as powerful as this module. Think of Data::Dumper::Names as a "proof of concept".
      Also Data::Dumper::Names says:
      This module is an alternative to Data::Dumper::Simple. Many people like the aforementioned module but do not like the fact that it uses a source filter. In order to pull off the trick with this module, we use PadWalker. This introduces its own set of problems, not the least of which is that PadWalker uses undocumented features of the Perl internals and has an annoying tendency to break. Thus, if this module doesn't work on your machine you may have to go back to Data::Dumper::Simple.

      Now, I tested PadWalker quickly today to check how it works for some simple cases. And based on these tests I have a gut feeling that it may not be as general solution as using PPI. Firstly, I noticed that it can only access lexical variable names in the argument list. This is currently a severe limitation since very often you want to print out package variables. On the other hand, a good thing about PadWalker seems to be that it can easily differentiate between calls like p $var and p func($var). (The p function here is the call to Data::Printer::p()). So it will understand that in the latter call $var is not the sought printed variable. This is more difficult with PPI, but it should be possible.

      Having said that, if you want the full path name to the current file, just use __FILE__. And if you want the full path name to the file that just called you, just use (caller)1. As a bonus, caller will also give you the line number that you were called from.

      Yes exactly, that linenumber information given by caller() was what I was trying to exploit! However, the problem of determining the absolute path of the filename still persists, since __FILE__ can be a relative pathname. I am curious why the Perl porters did not make __FILE__ always be an absolute pathname. I cannot see any benefits of populating it with a relative pathname :)

        > Note that if you strongly object to source filters, I've also released Data::Dumper::Names. It does what this module does by it uses PadWalker instead of a source filter. Unfortunately, it has a few limitations and is not as powerful as this module. Think of Data::Dumper::Names as a "proof of concept".

        Ovid didn't update this text for years, but last time I talked to him he was very confident about this being stable.

        But please note that PadWalker has still limitations, like when being called from within an eval .

        see PadWalker::var_name BUG?

        Cheers Rolf
        (addicted to the Perl Programming Language and ☆☆☆☆ :)
        Je suis Charlie!

        Regarding benefits of non-absolute __FILE__. I suspect it is because the information requires consulting the file system, perhaps at length, to resolve links and such. Often unnecessary and always more expensive.

Re: How to determine absolute path of current Perl file?
by FreeBeerReekingMonk (Deacon) on Feb 29, 2016 at 21:48 UTC
    So, require your module to be one of the first in the list. This worked for me:

    runme.pl

    #!/usr/bin/perl # In order to 'use' a perlmodule in a userspace library you need to ad +d it to @INC. # But if you do not want to use an absolute path inside the script, yo +u need this workaround: BEGIN{ use Cwd 'abs_path'; my $this = $0; # this script $this =~ s/[^\/]+$//; # strip the filename, now we have a (rel +ative) directory my $path = abs_path($this); # now we have an absolute director +y unshift @INC, $path; # which we add to @INC # chdir('/tmp'); # does seem to confuse abs_path! Uncomment to + see } use My::Module; My::Module::print_file_name(); My::Module::print_script_name(); My::Module::print_caller_name();

    -*Module.pl*- ./My/Module.pm

    #!/usr/bin/perl package My::Module; use feature qw(say); local $_SCRIPT = ""; local $_PATH = ""; BEGIN{ use Cwd 'abs_path'; $_PATH = $0; # this script $_PATH =~ s/([^\/]+)$//; # strip the filename, now we have a ( +relative) directory $_SCRIPT = $1; # the stripped part is the filename $_PATH = abs_path($_PATH); # now we have an absolute directory + print "My::Module 0=$0 _PATH=$_PATH _SCRIPT=$_SCRIPT \n"; } sub print_file_name { say __FILE__; } sub print_script_name { say "print_script_name: $_PATH/$_SCRIPT"; } sub print_caller_name { my @D = caller(); $D[1]= abs_path($_PATH.'/'.$D[1]) unless $D[1]=~m{^/}; say "print_caller_name: @D"; } 1;

    If you bet on the fact that if chdir() was done, and that new directory does NOT contain the script that was run, You can add a kill or warning in the BEGIN block of the Module.pm, like:

    warn "ALERT ALERT ALERT" unless(-f "$_PATH/$_SCRIPT");
Re: How to determine absolute path of current Perl file? (Data::Dumper::Lazy)
by LanX (Saint) on Mar 01, 2016 at 11:18 UTC

      Hi LanX!

      I was not aware of Data::Dumper::Lazy, thanks for bringing it to my attention. I looks really interesting to use B::Deparse. But I am not sure it will be compatible with the call syntax for Data::Printer. Currently I am planning to modify Data::Printer directly and propose a pull request to the author of the module. I have not considered building a new module on top of Data::Printer yet.

      I also did some quick tests with Data::Dumper::Lazy and the output confused me a little bit. I think the module is still under development, am I right? :)
        > think the module is still under development, am I right?

        Well unfortunately I tend to be a perfectionist and overload things with features.

        I'm not aware of bugs, AFAIK it's stable.

        > But I am not sure it will be compatible with the call syntax for Data::Printer .

        Well you'd always need an extra surrounding { BLOCK } , any other argument structure will be returned 1to1 from this block

        you could publish something like Data::Printer::Lazy.

        > and the output confused me a little bit.

        Could you please show me an example of this "confusing output"?

        Cheers Rolf
        (addicted to the Perl Programming Language and ☆☆☆☆ :)
        Je suis Charlie!

        update
        looked into the (little ;) code and it mentions
        # TODO # * ignore pragmas

        Now I remember B::Deparse::coderef2text will list active use pragmas in the first lines, these need to be filtered out.

Re: How to determine absolute path of current Perl file?
by Anonymous Monk on Mar 01, 2016 at 12:41 UTC

    You have made the (reasonable) assumption that source files do not change during the execution? Why not further assume that the cwd is unchanged during compilation? Otherwise, you'd have to account for the possibility that a chdir occurs anywhere between the use statements...

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://1156424]
Front-paged by Discipulus
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others having a coffee break in the Monastery: (3)
As of 2024-04-19 19:44 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found