in reply to Do modules know the callers full path?

that's what you want?

use v5.12; use warnings; use Cwd qw(abs_path); say 'abs_path($0): ', abs_path($0);

--->

PS D:\transfer_D\d_temp> perl .\temp\tst.pl abs_path($0): D:/transfer_D/d_temp/temp/tst.pl PS D:\transfer_D\d_temp>

Cheers Rolf
(addicted to the 𐍀𐌴𐍂𐌻 Programming Language :)
Wikisyntax for the Monastery

Replies are listed 'Best First'.
Re^2: Do modules know the callers full path?
by haj (Vicar) on Feb 15, 2023 at 19:52 UTC

    abs_path($0) gives wrong results if the script is invoked with a relative path and then changes directory. A robust solution is FindBin, which also resolves symbolic links:

    use 5.020; use warnings; use Cwd qw(abs_path); use FindBin; say qq(Started as '$0'); say q(abs_path is '), abs_path($0), q('); say q(FindBin says '), qq($FindBin::RealBin/$FindBin::RealScript), q('); say q(Changing directory...); chdir '..'; say q(abs_path is '), abs_path($0), q('); say q(FindBin says '), qq($FindBin::RealBin/$FindBin::RealScript), q(');
      But FindBin emphasizes that it should not be used in modules, for the reasons they gave in their documentation. And the OP requested a way to find the path to the calling script from a module ... so as good as your solution is within a script, it's not the recommended solution to put into a module.

        That's true, but it is not the whole truth. The documentation says (emphasis mine):

        Which also means that you should avoid using FindBin in modules that you plan to put on CPAN.

        Using FindBin in a module works fine. If you use FindBin::again();, then you are safe. The reason why one should not use it on CPAN is because other users of that module might not be aware that they also need to call FindBin::again(); in their code to be safe ... which, on close inspection, they only need for use cases like mod_perl where the "scripts" aren't actually fed to the interpreter on the command line.

        While we are looking at the fine print: Like abs_path($0), FindBin relies on $0. So if used in programs which modify $0 (like e.g. plackup does), then one should hope that it does so in a sensible way.

        I looked into FindBin and it's essentially applying Cwd inside a BEGIN block to grab it as soon as possible.

        So it's prone to the same (potential) problem of a chdir prior at compile-time.

        That's a built-in race condition, how soon is soon enough and you can't avoid someone to be sooner ....

        Cheers Rolf
        (addicted to the 𐍀𐌴𐍂𐌻 Programming Language :)
        Wikisyntax for the Monastery

      I tested my solution from a module too and it's unlikely such a chdir happens at compile time before a module is used.

      Especially since a BEGIN { chdir ".." } also effects relative paths in @INC.

      Granted I was too lazy posting two files.

      So I'd speculate this approach is good enough for the OPs cases.

      Cheers Rolf
      (addicted to the 𐍀𐌴𐍂𐌻 Programming Language :)
      Wikisyntax for the Monastery

        I tested my solution from a module too and it's unlikely such a chdir happens at compile time before a module is used.

        Especially since a BEGIN { chdir ".." } also effects relative paths in @INC.

        Granted I was too lazy posting two files.

        So I'd speculate this approach is good enough for the OPs cases.

        Modules can be loaded at run time. And that will break any code that relies on a constant working directory.

        Load a simple module at run time from a simple script and have the module report where the main script is:

        /tmp/perlmonks>cat run.pl #!/usr/bin/perl use strict; use warnings; use lib '/tmp/perlmonks'; # Tons of work omitted ... ;-) @ARGV and chdir '/tmp/unrelated'; require Mod; Mod::where(); /tmp/perlmonks>cat Mod.pm package Mod; use v5.12; # for say use strict; use warnings; use FindBin; sub where { say "Script is in $FindBin::Bin"; say "Script basename is $FindBin::Script"; say "Real script directory is $FindBin::RealBin"; say "Real script basename is $FindBin::RealScript"; say "Module was loaded from $INC{'Mod.pm'}"; } 1; /tmp/perlmonks>perl run.pl Script is in /tmp/perlmonks Script basename is run.pl Real script directory is /tmp/perlmonks Real script basename is run.pl Module was loaded from /tmp/perlmonks/Mod.pm /tmp/perlmonks>

        That works even from the command line:

        /tmp/perlmonks>perl -MMod -e Mod::where Script is in /tmp/perlmonks Script basename is -e Real script directory is /tmp/perlmonks Real script basename is -e Module was loaded from Mod.pm /tmp/perlmonks>

        Run the script again, but this time, change the working directory before loading the module:

        /tmp/perlmonks>perl run.pl x Cannot find current script 'run.pl' at /usr/share/perl5/FindBin.pm lin +e 166. BEGIN failed--compilation aborted at /usr/share/perl5/FindBin.pm line +166. Compilation failed in require at /tmp/perlmonks/Mod.pm line 6. BEGIN failed--compilation aborted at /tmp/perlmonks/Mod.pm line 6. Compilation failed in require at run.pl line 11. /tmp/perlmonks>

        Obviously, FindBin has some tests to prevent accidents, but they can be defeated:

        /tmp/perlmonks>touch /tmp/unrelated/run.pl /tmp/perlmonks>perl run.pl Script is in /tmp/unrelated Script basename is run.pl Real script directory is /tmp/unrelated Real script basename is run.pl Module was loaded from /tmp/perlmonks/Mod.pm /tmp/perlmonks>

        Now, FindBin is sure that the main script is /tmp/unrelated/run.pl. It is not.

        To fix that problem, load FindBin as early as possible from the main script, or even via command line parameters:

        /tmp/perlmonks>perl -MFindBin run.pl x Script is in /tmp/perlmonks Script basename is run.pl Real script directory is /tmp/perlmonks Real script basename is run.pl Module was loaded from /tmp/perlmonks/Mod.pm /tmp/perlmonks>

        Oh, by the way, this is not the problem from the FindBin documentation. FindBin documents a different problem, with persistent environments like mod_perl:

        KNOWN ISSUES

        If there are two modules using FindBin from different directories under the same interpreter, this won't work. Since FindBin uses a BEGIN block, it'll be executed only once, and only the first caller will get it right. This is a problem under mod_perl and other persistent Perl environments, where you shouldn't use this module. Which also means that you should avoid using FindBin in modules that you plan to put on CPAN.

        Alexander

        --
        Today I will gladly share my knowledge and experience, for there are no sweeter words than "I told you so". ;-)

        ...it depends. If the call to abs_path is in a subroutine, then it won't happen at compile time.