如何获取正在执行的Perl脚本的完整路径?

如何获取正在执行的Perl脚本的完整路径?

How do I get the full path to a Perl script that is executing?

我有Perl脚本,需要在执行期间确定脚本的完整路径和文件名。 我发现,取决于脚本的调用方式,$0有所不同,有时包含fullpath+filename,有时仅包含filename。 因为工作目录也可能变化,所以我想不出一种可靠地获取脚本fullpath+filename的方法。

任何人都有解决方案吗?


有几种方法:

  • $0是POSIX提供的当前正在执行的脚本,相对于当前工作目录(如果脚本位于CWD或以下)
  • 此外,Cwd模块提供了cwd()getcwd()abs_path(),并告诉您脚本从何处运行
  • 模块FindBin提供$Bin$RealBin变量,它们通常是执行脚本的路径。此模块还提供$Script$RealScript,它们是脚本的名称
  • __FILE__是Perl解释器在编译过程中要处理的实际文件,包括其完整路径。

我已经看到前三个($0Cwd模块和FindBin模块)在mod_perl下严重失败,产生了毫无价值的输出,例如'.'或空字符串。在这样的环境中,我使用__FILE__并使用File::Basename模块从中获取路径:

1
2
use File::Basename;
my $dirname = dirname(__FILE__);

$ 0通常是程序的名称,那么这又如何呢?

1
2
use Cwd 'abs_path';
print abs_path($0);

在我看来,这应该作为abs_path知道您正在使用相对还是绝对路径。

更新对于几年后阅读的任何人,您都应该阅读Drew的答案。比我的好多了。


1
2
Use File::Spec;
File::Spec->rel2abs( __FILE__ );

http://perldoc.perl.org/File/Spec/Unix.html


我认为您要查找的模块是FindBin:

1
2
3
4
5
6
#!/usr/bin/perl
use FindBin;

$0 ="stealth";
print"The actual path to this is: $FindBin::Bin/$FindBin::Script\
"
;

您可以使用FindBin,Cwd,File :: Basename或它们的组合。它们全部在Perl IIRC的基本发行版中。

我过去使用过Cwd:

Cwd:

1
2
3
4
use Cwd qw(abs_path);
my $path = abs_path($0);
print"$path\
"
;

您想要获得$0__FILE__的绝对路径。唯一的麻烦是,如果有人做了chdir()并且$0是相对的-那么您需要在BEGIN{}中获取绝对路径,以防止出现任何意外情况。

FindBin试图做得更好,并在$PATH中徘徊,寻找与basename($0)相匹配的内容,但是有时候这会做得太令人惊讶了(特别是:当文件"就在您的面前"时)在cwd中。)

File::Fu为此具有File::Fu->program_nameFile::Fu->program_dir


一些简短的背景:

不幸的是,Unix API没有为运行的程序提供可执行文件的完整路径。实际上,执行您的程序可以在通常告诉您程序内容的字段中提供所需的内容。正如所有答案所指出的那样,有各种启发式方法可以找到可能的候选人。但是搜索整个文件系统总是可行的,即使移动或删除了可执行文件,即使这样也会失败。

但是您不希望Perl可执行文件(实际上正在运行),而是要执行的脚本。而且Perl需要知道脚本在哪里可以找到它。它存储在__FILE__中,而$0来自Unix API。这仍然可以是相对路径,因此请接受Mark的建议并使用File::Spec->rel2abs( __FILE__ );将其标准化


你有没有尝试过:

1
$ENV{'SCRIPT_NAME'}

要么

1
2
3
use FindBin '$Bin';
print"The script is located in $Bin.\
"
;

它实际上取决于如何调用它,以及它是CGI还是从普通shell运行等。


为了获得包含我的脚本的目录的路径,我使用了已经给出的答案的组合。

1
2
3
4
5
6
7
#!/usr/bin/perl
use strict;
use warnings;
use File::Spec;
use File::Basename;

my $dir = dirname(File::Spec->rel2abs(__FILE__));

无需使用外部模块,只需一行即可获得文件名和相对路径。如果您正在使用模块,并且需要应用相对于脚本目录的路径,则相对路径就足够了。

1
2
3
$0 =~ m/(.+)[\\/\\\\](.+)$/;
print"full path: $1, file name: $2\
"
;

perlfaq8使用$0上的rel2abs()函数回答了一个非常相似的问题。该功能可以在File :: Spec中找到。


您在寻找这个吗?

1
2
3
4
5
6
my $thisfile = $1 if $0 =~
/\\\\([^\\\\]*)$|\\/([^\\/]*)$/;

print"You are running $thisfile
now.\
"
;

输出将如下所示:

1
You are running MyFileName.pl now.

它在Windows和Unix上均可使用。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#!/usr/bin/perl -w
use strict;


my $path = $0;
$path =~ s/\\.\\///g;
if ($path =~ /\\//){
  if ($path =~ /^\\//){
    $path =~ /^((\\/[^\\/]+){1,}\\/)[^\\/]+$/;
    $path = $1;
    }
  else {
    $path =~ /^(([^\\/]+\\/){1,})[^\\/]+$/;
    my $path_b = $1;
    my $path_a = `pwd`;
    chop($path_a);
    $path = $path_a."/".$path_b;
    }
  }
else{
  $path = `pwd`;
  chop($path);
  $path.="/";
  }
$path =~ s/\\/\\//\\//g;



print"\
$path\
"
;

:DD


在Windows上,同时使用dirnameabs_path对我来说效果最好。

1
2
3
4
5
6
7
8
use File::Basename;
use Cwd qw(abs_path);

# absolute path of the directory containing the executing script
my $abs_dirname = dirname(abs_path($0));
print"\
dirname(abs_path(\\$0)) -> $abs_dirname\
"
;

原因如下:

1
2
3
4
5
6
7
8
9
# this gives the answer I want in relative path form, not absolute
my $rel_dirname = dirname(__FILE__);
print"dirname(__FILE__) -> $rel_dirname\
"
;

# this gives the slightly wrong answer, but in the form I want
my $full_filepath = abs_path($0);
print"abs_path(\\$0) -> $full_filepath\
"
;

__FILE__的问题在于它将打印核心模块" .pm"路径,而不必打印正在运行的" .cgi"或" .pl"脚本路径。我想这取决于您的目标是什么。

在我看来,Cwd仅需要为mod_perl更新。这是我的建议:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
my $path;

use File::Basename;
my $file = basename($ENV{SCRIPT_NAME});

if (exists $ENV{MOD_PERL} && ($ENV{MOD_PERL_API_VERSION} < 2)) {
  if ($^O =~/Win/) {
    $path = `echo %cd%`;
    chop $path;
    $path =~ s!\\\\!/!g;
    $path .= $ENV{SCRIPT_NAME};
  }
  else {
    $path = `pwd`;
    $path .="/$file";
  }
  # add support for other operating systems
}
else {
  require Cwd;
  $path = Cwd::getcwd()."/$file";
}
print $path;

请添加任何建议。


没有任何对shell有效的外部模块,即使使用'../'也可以很好地工作:

1
2
3
4
5
my $self = `pwd`;
chomp $self;
$self .='/'.$1 if $0 =~/([^\\/]*)$/; #keep the filename only
print"self=$self\
"
;

测试:

1
2
3
4
5
6
7
8
$ /my/temp/Host$ perl ./host-mod.pl
self=/my/temp/Host/host-mod.pl

$ /my/temp/Host$ ./host-mod.pl
self=/my/temp/Host/host-mod.pl

$ /my/temp/Host$ ../Host/./host-mod.pl
self=/my/temp/Host/host-mod.pl

仅使用dirname(__FILE__)的问题是它不遵循符号链接。我必须在脚本中使用它来跟随符号链接到达实际的文件位置。

1
2
3
4
5
6
7
8
use File::Basename;
my $script_dir = undef;
if(-l __FILE__) {
  $script_dir = dirname(readlink(__FILE__));
}
else {
  $script_dir = dirname(__FILE__);
}

实际上,所有无库解决方案都无法通过多种方式来编写路径(请考虑../或/bla/x/../bin/./x/../等。我的解决方案看起来像我有一个怪癖:我不知道为什么我必须两次运行替换项,否则我会得到一个虚假的" ./"或" ../"。对我来说似乎很健壮。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  my $callpath = $0;
  my $pwd = `pwd`; chomp($pwd);

  # if called relative -> add pwd in front
  if ($callpath !~ /^\\//) { $callpath = $pwd."/".$callpath; }  

  # do the cleanup
  $callpath =~ s!^\\./!!;                          # starts with ./ -> drop
  $callpath =~ s!/\\./!/!g;                        # /./ -> /
  $callpath =~ s!/\\./!/!g;                        # /./ -> /        (twice)

  $callpath =~ s!/[^/]+/\\.\\./!/!g;                # /xxx/../ -> /
  $callpath =~ s!/[^/]+/\\.\\./!/!g;                # /xxx/../ -> /   (twice)

  my $calldir = $callpath;
  $calldir =~ s/(.*)\\/([^\\/]+)/$1/;

"最佳"答案都不适合我。使用FindBin'$ Bin'或Cwd的问题是它们返回了所有符号链接都已解析的绝对路径。在我的情况下,我需要带有符号链接的确切路径-与返回Unix命令" pwd"相同,而不是" pwd -P"。以下功能提供了解决方案:

1
2
3
4
5
6
7
8
9
10
sub get_script_full_path {
    use File::Basename;
    use File::Spec;
    use Cwd qw(chdir cwd);
    my $curr_dir = cwd();
    chdir(dirname($0));
    my $dir = $ENV{PWD};
    chdir( $curr_dir);
    return File::Spec->catfile($dir, basename($0));
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
use strict ; use warnings ; use Cwd 'abs_path';
    sub ResolveMyProductBaseDir {

        # Start - Resolve the ProductBaseDir
        #resolve the run dir where this scripts is placed
        my $ScriptAbsolutPath = abs_path($0) ;
        #debug print"\\$ScriptAbsolutPath is $ScriptAbsolutPath \
" ;
        $ScriptAbsolutPath =~ m/^(.*)(\\\\|\\/)(.*)\\.([a-z]*)/;
        $RunDir = $1 ;
        #debug print"
\\$1 is $1 \
" ;
        #change the \'s to /'s if we are on Windows
        $RunDir =~s/\\\\/\\//gi ;
        my @DirParts = split ('/' , $RunDir) ;
        for (my $count=0; $count < 4; $count++) {   pop @DirParts ;     }
        my $ProductBaseDir = join ( '/' , @DirParts ) ;
        # Stop - Resolve the ProductBaseDir
        #debug print"
ResolveMyProductBaseDir $ProductBaseDir is $ProductBaseDir \
" ;
        return $ProductBaseDir ;
    } #eof sub

$^X怎么了?

1
2
3
#!/usr/bin/env perl
print"This is executed by $^X\
"
;

将为您提供正在使用的Perl二进制文件的完整路径。

翻转


在* nix上,您可能具有" whereis"命令,该命令在$ PATH中搜索具有给定名称的二进制文件。如果$ 0不包含完整路径名,则运行whereis $ scriptname并将结果保存到变量中将告诉您脚本的位置。


推荐阅读