Làm cách nào để có được đường dẫn đầy đủ tới tập lệnh Perl đang thực thi?


168

Tôi có tập lệnh Perl và cần xác định đường dẫn và tên tệp đầy đủ của tập lệnh trong khi thực thi. Tôi phát hiện ra rằng tùy thuộc vào cách bạn gọi tập lệnh $0khác nhau và đôi khi có chứa fullpath+filenamevà đôi khi chỉ filename. Bởi vì thư mục làm việc có thể khác nhau, tôi không thể nghĩ ra cách nào để lấy fullpath+filenamekịch bản một cách đáng tin cậy .

Bất cứ ai cũng có một giải pháp?

Câu trả lời:


251

Có một số cách:

  • $0 là tập lệnh hiện đang thực thi do POSIX cung cấp, liên quan đến thư mục làm việc hiện tại nếu tập lệnh nằm ở hoặc bên dưới CWD
  • Thêm vào đó, cwd(), getcwd()abs_path()được cung cấp bởi các Cwdmô-đun và cho bạn biết nơi kịch bản đang được chạy từ
  • Mô-đun FindBincung cấp các biến $Bin& thường là đường dẫn đến tập lệnh thực thi; mô-đun này cũng cung cấp & đó là tên của tập lệnh$RealBin$Script$RealScript
  • __FILE__ là tệp thực tế mà trình thông dịch Perl xử lý trong quá trình biên dịch, bao gồm cả đường dẫn đầy đủ của nó.

Tôi đã thấy ba cái đầu tiên ( $0, Cwdmô-đun và FindBinmô-đun) thất bại mod_perlmột cách ngoạn mục, tạo ra đầu ra vô giá trị như '.'hoặc một chuỗi rỗng. Trong các môi trường như vậy, tôi sử dụng __FILE__và nhận đường dẫn từ đó bằng cách sử dụng File::Basenamemô-đun:

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

2
Đây thực sự là giải pháp tốt nhất, đặc biệt nếu bạn đã sửa đổi $ 0
Caterham

8
Có vẻ như abs_path cần được sử dụng với _____FILE___ vì nó chỉ có thể chứa tên với đường dẫn.
Aftershock

6
@vicTROLLA Có lẽ vì đề xuất lớn nhất từ ​​câu trả lời này (sử dụng dirname với __FILE__) không hoạt động như mong đợi? Tôi kết thúc với đường dẫn tương đối từ nơi tập lệnh được thực thi, trong khi câu trả lời được chấp nhận cho tôi đường dẫn tuyệt đối đầy đủ.
Izkata

10
dirname(__FILE__)không theo symlink, vì vậy nếu bạn đã liên kết tệp thực thi và nơi hy vọng tìm thấy vị trí của một số tệp khác trong vị trí cài đặt, bạn cần kiểm tra if( -l __FILE__)và sau đó dirname(readlink(__FILE__)).
DavidG

3
@IliaRostovtsev Bạn có thể tìm thấy khi một mô-đun lần đầu tiên được bao gồm trong các mô-đun tiêu chuẩn với câu thần chú này : perl -e 'use Module::CoreList; print Module::CoreList->first_release("File::Basename");'; echo. Vì File::Basenameđó là Perl 5.0.0, được phát hành vào cuối thập niên 90, tôi nghĩ bây giờ nó tiết kiệm để sử dụng.
Drew Stephens

145

$ 0 thường là tên chương trình của bạn, vậy còn cái này thì sao?

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

Dường như với tôi rằng điều này sẽ hoạt động như abs_path biết nếu bạn đang sử dụng một đường dẫn tương đối hoặc tuyệt đối.

Cập nhật Đối với bất kỳ ai đọc những năm sau này, bạn nên đọc câu trả lời của Drew . Nó tốt hơn tôi nhiều.


11
Nhận xét nhỏ, khi kích hoạt perl trên windows $ 0 thường chứa dấu gạch chéo ngược và abs_path trả về dấu gạch chéo về phía trước, do đó, "tr / \ // \\ /;" nhanh chóng là cần thiết để sửa chữa nó.
Chris Madden

3
muốn thêm rằng có một realpath, đó là một từ đồng nghĩa với abs_path, trong đó bạn thích cái tên không gạch dưới
vol7ron

@Chris, bạn đã báo cáo lỗi cho người bảo trì mô-đun Cwd chưa? Có vẻ như lỗi chấp nhận Windows.
Znik

1
Vấn đề khác tôi có: perl -e 'use Cwd "abs_path";print abs_path($0);' bản in/tmp/-e
leonbloy

2
@leonbloy Khi bạn thực thi một tập lệnh nội tuyến (với -e), tôi tin rằng perl tạo một tệp tạm thời để lưu trữ tập lệnh nội tuyến của bạn. Có vẻ như vị trí, trong trường hợp của bạn, là /tmp. Bạn đã mong đợi kết quả là gì?
GreenGiant


16

Tôi nghĩ rằng mô-đun bạn đang tìm kiếm là FindBin:

#!/usr/bin/perl
use FindBin;

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

11

Bạn có thể sử dụng FindBin , Cwd , File :: Basename hoặc kết hợp chúng. Tất cả đều nằm trong bản phân phối cơ bản của Perl IIRC.

Tôi đã sử dụng Cwd trong quá khứ:

Cwd:

use Cwd qw(abs_path);
my $path = abs_path($0);
print "$path\n";

@bmdhacks, bạn nói đúng. Giả định là, bạn đã không thay đổi 0 $. Ví dụ: bạn làm việc ở trên ngay khi tập lệnh bắt đầu (trong khối khởi tạo) hoặc ở nơi khác khi bạn không thay đổi $ 0. Nhưng $ 0 là cách tuyệt vời để thay đổi mô tả quy trình hiển thị trong công cụ unix 'ps' :) Điều này có thể hiển thị trạng thái quy trình hiện tại, v.v. Điều này phụ thuộc vào mục đích lập trình viên :)
Znik

9

Có được con đường tuyệt đối đến $0hoặc __FILE__là những gì bạn muốn. Rắc rối duy nhất là nếu ai đó đã làm một chdir()$0tương đối - thì bạn cần có được đường dẫn tuyệt đối BEGIN{}để ngăn chặn mọi bất ngờ.

FindBincố gắng đi tốt hơn và $PATHtìm kiếm thứ gì đó phù hợp với điều gì đó basename($0), nhưng có những lúc điều đó quá đáng ngạc nhiên (cụ thể là: khi tập tin "ở ngay trước mặt bạn" trong cwd.)

File::FuFile::Fu->program_nameFile::Fu->program_dircho điều này.


Có thực sự có khả năng bất cứ ai sẽ ngu ngốc đến mức (vĩnh viễn) chdir()tại thời điểm biên dịch không?
SamB

Đơn giản chỉ cần làm tất cả các công việc dựa trên thư mục hiện tại và $ 0 khi tập lệnh bắt đầu.
Znik

7

Một số nền tảng ngắn:

Thật không may, API Unix không cung cấp một chương trình đang chạy với đường dẫn đầy đủ đến tệp thực thi. Trong thực tế, chương trình thực thi của bạn có thể cung cấp bất cứ điều gì nó muốn trong trường thường cho chương trình của bạn biết nó là gì. Có, như tất cả các câu trả lời chỉ ra, các phương pháp phỏng đoán khác nhau để tìm kiếm các ứng cử viên có khả năng. Nhưng không có gì ngắn gọn khi tìm kiếm toàn bộ hệ thống tập tin sẽ luôn hoạt động và thậm chí điều đó sẽ thất bại nếu thực thi được di chuyển hoặc loại bỏ.

Nhưng bạn không muốn Perl thực thi, đó là những gì thực sự đang chạy, nhưng kịch bản mà nó đang thực thi. Và Perl cần biết kịch bản ở đâu để tìm thấy nó. Nó lưu trữ cái này trong __FILE__, trong khi $0là từ API Unix. Đây vẫn có thể là một đường dẫn tương đối, vì vậy hãy lấy gợi ý của Mark và chuẩn hóa nó vớiFile::Spec->rel2abs( __FILE__ );


__FILE__vẫn cho tôi một con đường tương đối. I E '.'.
felwithe

6

Bạn đã thử chưa:

$ENV{'SCRIPT_NAME'}

hoặc là

use FindBin '$Bin';
print "The script is located in $Bin.\n";

Nó thực sự phụ thuộc vào cách nó được gọi và nếu nó là CGI hoặc được chạy từ một vỏ bình thường, v.v.


$ ENV {'SCRIPT_NAME'} trống khi tập lệnh đang chạy trên bảng điều khiển
Putnik

Ý tưởng tồi vì môi trường SCRIPT_NAME phụ thuộc vào trình bao bạn đang sử dụng. Điều này hoàn toàn không tương thích với windows cmd.exe và không tương thích khi bạn gọi script trực tiếp từ các nhị phân khác. Không có bảo hành này có thể cảnh báo được thiết lập. Trên đây là cách sử dụng nhiều hơn.
Znik

6

Để có được đường dẫn đến thư mục chứa tập lệnh của tôi, tôi đã sử dụng kết hợp các câu trả lời đã cho.

#!/usr/bin/perl
use strict;
use warnings;
use File::Spec;
use File::Basename;

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

2

perlfaq8 trả lời một câu hỏi rất giống với việc sử dụng rel2abs()chức năng trên $0. Chức năng đó có thể được tìm thấy trong File :: Spec.


2

Không cần sử dụng các mô-đun bên ngoài, chỉ với một dòng bạn có thể có tên tệp và đường dẫn tương đối. Nếu bạn đang sử dụng các mô-đun và cần áp dụng một đường dẫn liên quan đến thư mục script, thì đường dẫn tương đối là đủ.

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

Nó không cung cấp đường dẫn đầy đủ của tập lệnh nếu bạn chạy nó như "./myscript.pl", vì nó sẽ chỉ hiển thị "." thay thế. Nhưng tôi vẫn thích giải pháp này.
Keve

1
#!/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 "\n$path\n";

: DD


4
Xin đừng trả lời bằng mã. Hãy giải thích tại sao đây là câu trả lời đúng.
Lee Taylor

1

Bạn đang tìm kiếm điều này?:

my $thisfile = $1 if $0 =~
/\\([^\\]*)$|\/([^\/]*)$/;

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

Đầu ra sẽ như thế này:

You are running MyFileName.pl now.

Nó hoạt động trên cả Windows và Unix.


0
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 \n" ;
        $ScriptAbsolutPath =~ m/^(.*)(\\|\/)(.*)\.([a-z]*)/; 
        $RunDir = $1 ; 
        #debug print "\$1 is $1 \n" ;
        #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 \n" ; 
        return $ProductBaseDir ; 
    } #eof sub 

Mặc dù câu trả lời chỉ có nguồn có thể giải quyết câu hỏi của người dùng, nhưng nó không giúp họ hiểu tại sao nó hoạt động. Bạn đã cho người dùng một con cá, nhưng thay vào đó bạn nên dạy họ CÁCH câu cá.
Người đàn ông Tin

0

Vấn đề __FILE__là nó sẽ in đường dẫn lõi ".pm" không nhất thiết là đường dẫn kịch bản ".cgi" hoặc ".pl" đang chạy. Tôi đoán nó phụ thuộc vào mục tiêu của bạn là gì.

Dường như với tôi rằng Cwdchỉ cần cập nhật cho mod_perl. Đây là gợi ý của tôi:

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;

Vui lòng thêm bất kỳ đề nghị.


0

Không có bất kỳ mô-đun bên ngoài nào, hợp lệ cho shell, hoạt động tốt ngay cả với '../':

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

kiểm tra:

$ /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

Điều gì khi bạn gọi symlink? Cwd hoạt động tuyệt vời với trường hợp này.
Znik

0

Vấn đề với việc chỉ sử dụng dirname(__FILE__)là nó không tuân theo các liên kết tượng trưng. Tôi đã phải sử dụng điều này cho kịch bản của mình để theo liên kết tượng trưng đến vị trí tệp thực tế.

use File::Basename;
my $script_dir = undef;
if(-l __FILE__) {
  $script_dir = dirname(readlink(__FILE__));
}
else {
  $script_dir = dirname(__FILE__);
}

0

Tất cả các giải pháp không có thư viện thực sự không hoạt động trong hơn một vài cách để viết một đường dẫn (nghĩ ../ hoặc /bla/x/../bin/./x/../ vv. Giải pháp của tôi trông giống như bên dưới. Tôi có một điều khó hiểu: Tôi không có ý tưởng rõ ràng nhất tại sao tôi phải chạy thay thế hai lần. Nếu tôi không, tôi nhận được một "./" hoặc "../". có vẻ khá mạnh mẽ với tôi

  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/;

0

Không có câu trả lời "hàng đầu" nào phù hợp với tôi. Vấn đề với việc sử dụng FindBin '$ Bin' hoặc Cwd là chúng trả về đường dẫn tuyệt đối với tất cả các liên kết tượng trưng được giải quyết. Trong trường hợp của tôi, tôi cần đường dẫn chính xác với các liên kết tượng trưng hiện tại - giống như trả về lệnh Unix "pwd" chứ không phải "pwd -P". Hàm sau cung cấp giải pháp:

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));
}

0

Trên Windows sử dụng dirnameabs_pathcùng nhau làm việc tốt nhất cho tôi.

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 "\ndirname(abs_path(\$0)) -> $abs_dirname\n";

đây là lý do tại sao:

# this gives the answer I want in relative path form, not absolute
my $rel_dirname = dirname(__FILE__); 
print "dirname(__FILE__) -> $rel_dirname\n"; 

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

-2

Có chuyện gì với bạn $^Xvậy?

#!/usr/bin/env perl<br>
print "This is executed by $^X\n";

Sẽ cung cấp cho bạn đường dẫn đầy đủ đến nhị phân Perl đang được sử dụng.

Evert


1
Nó đưa đường dẫn đến nhị phân Perl trong khi đường dẫn đến một tập lệnh được yêu cầu
Putnik

-5

Trên * nix, bạn có thể có lệnh "whereis", tìm kiếm $ PATH của bạn đang tìm kiếm một nhị phân có tên đã cho. Nếu $ 0 không chứa tên đường dẫn đầy đủ, việc chạy whereis $ scriptname và lưu kết quả vào một biến sẽ cho bạn biết vị trí của tập lệnh.


Điều đó sẽ không hoạt động, vì $ 0 cũng có thể trả về một đường dẫn tương đối đến tệp: ../perl/test.pl
Lathan

Điều gì sẽ xảy ra nếu tập lệnh thực thi nằm ngoài PATH?
Znik
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.