ฉันจะรับพา ธ แบบเต็มไปยังสคริปต์ Perl ที่รันอยู่ได้อย่างไร


168

ฉันมีสคริปต์ Perl และต้องกำหนดเส้นทางแบบเต็มและชื่อไฟล์ของสคริปต์ระหว่างการดำเนินการ ผมค้นพบว่าขึ้นอยู่กับว่าคุณเรียกสคริปต์ที่$0แตกต่างกันและบางครั้งก็มีและบางครั้งเพียงแค่fullpath+filename filenameเนื่องจากไดเรกทอรีการทำงานอาจแตกต่างกันไปเช่นกันฉันไม่สามารถคิดวิธีที่จะได้รับfullpath+filenameสคริปต์อย่างน่าเชื่อถือ

ใครมีวิธีแก้ปัญหา?

คำตอบ:


251

มีหลายวิธี:

  • $0 เป็นสคริปต์ที่กำลังดำเนินการในปัจจุบันซึ่งจัดทำโดย POSIX ซึ่งเกี่ยวข้องกับไดเรกทอรีการทำงานปัจจุบันหากสคริปต์อยู่ที่หรือต่ำกว่า CWD
  • นอกจากนี้cwd(), getcwd()และabs_path()มีการให้บริการโดยCwdโมดูลและบอกคุณที่สคริปต์ที่จะถูกเรียกใช้จาก
  • โมดูลFindBinจัดเตรียม$Bin& $RealBinตัวแปรที่โดยปกติแล้วจะเป็นพา ธ ไปยังสคริปต์ที่เรียกใช้งาน โมดูลนี้ยังให้$Script& $RealScriptนั่นคือชื่อของสคริปต์
  • __FILE__ เป็นไฟล์จริงที่ล่าม Perl เกี่ยวข้องกับในระหว่างการรวบรวมรวมถึงเส้นทางแบบเต็ม

ผมเคยเห็นครั้งแรกที่สาม ( $0ที่CwdโมดูลและFindBinโมดูล) ล้มเหลวภายใต้mod_perlยี่หระการผลิตการส่งออกไร้ค่าเช่น'.'หรือสตริงที่ว่างเปล่า ในสภาพแวดล้อมเช่นนี้ฉันใช้__FILE__และรับเส้นทางจากสิ่งนั้นโดยใช้File::Basenameโมดูล:

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

2
นี่เป็นทางออกที่ดีที่สุดโดยเฉพาะอย่างยิ่งถ้าคุณมีการแก้ไข $ 0
Caterham

8
ดูเหมือนว่า abs_path จะต้องใช้กับ _____FILE_____ เนื่องจากอาจมีชื่อพร้อมกับเส้นทางเท่านั้น
Aftershock

6
@vicTROLLA อาจเป็นเพราะคำแนะนำที่ใหญ่ที่สุดจากคำตอบนี้ (ใช้ dirname ด้วย__FILE__) ไม่ได้ผลอย่างที่คิด ฉันจบลงด้วยเส้นทางสัมพัทธ์จากที่สคริปต์ถูกประหารชีวิตในขณะที่คำตอบที่ยอมรับให้ฉันเส้นทางที่สมบูรณ์แบบ
Izkata

10
dirname(__FILE__)ไม่เป็นไปตาม symlinks ดังนั้นหากคุณเชื่อมโยงแฟ้มที่ปฏิบัติการและสถานที่ที่หวังที่จะหาตำแหน่งของไฟล์อื่น ๆ บางอย่างที่อยู่ในสถานที่ที่ติดตั้งคุณต้องตรวจสอบแล้วif( -l __FILE__) dirname(readlink(__FILE__))
DavidG

3
@IliaRostovtsev perl -e 'use Module::CoreList; print Module::CoreList->first_release("File::Basename");'; echoคุณสามารถค้นหาเมื่อโมดูลเป็นครั้งแรกที่รวมอยู่ในโมดูลมาตรฐานที่มีคาถานี้: สำหรับFile::Basenameสิ่งนั้นคือ Perl 5.0.0 ซึ่งวางจำหน่ายในช่วงปลายยุค 90 ฉันคิดว่าตอนนี้ประหยัดได้แล้ว
Drew Stephens

145

โดยปกติ $ 0 เป็นชื่อโปรแกรมของคุณแล้วจะเป็นอย่างไร

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

ดูเหมือนว่าสิ่งนี้จะทำงานได้เป็น abs_path หากคุณใช้เส้นทางสัมพัทธ์หรือเส้นทางสัมบูรณ์

อัปเดตสำหรับทุกคนที่อ่านปีนี้ในภายหลังคุณควรอ่านคำตอบของ Drew มันดีกว่าของฉันมาก


11
ความคิดเห็นเล็ก ๆ ในการเปิดใช้งาน Perl บน windows $ 0 มักจะมีเครื่องหมายแบ็กสแลชและ abs_path ส่งคืนเครื่องหมายสแลชดังนั้น "tr / \ // \\ /;" ถูกต้องเพื่อแก้ไข
Chris Madden

3
ต้องการที่จะเพิ่มว่ามี a realpathซึ่งเป็นชื่อพ้องกับabs_pathกรณีที่คุณต้องการชื่อที่ไม่เน้น
vol7ron

@Chris คุณรายงานข้อผิดพลาดไปยังผู้ดูแลโมดูล Cwd หรือไม่? ดูเหมือนว่าข้อผิดพลาดการยอมรับของ Windows
Znik

1
ฉันมีปัญหาอื่น ๆ : perl -e 'use Cwd "abs_path";print abs_path($0);' พิมพ์/tmp/-e
leonbloy

2
@leonbloy เมื่อคุณรันสคริปต์แบบอินไลน์ (ด้วย-e) ฉันเชื่อว่า perl สร้างไฟล์ชั่วคราวเพื่อเก็บสคริปต์แบบอินไลน์ของคุณ /tmpดูเหมือนว่าสถานที่ในกรณีของคุณคือ คุณคาดหวังผลลัพธ์ที่จะเป็นอย่างไร
GreenGiant


16

ฉันคิดว่าโมดูลที่คุณกำลังมองหาคือ FindBin:

#!/usr/bin/perl
use FindBin;

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

11

คุณสามารถใช้FindBin , Cwd , File :: Basenameหรือใช้ร่วมกัน พวกมันทั้งหมดอยู่ในการกระจายฐานของ Perl IIRC

ฉันใช้ Cwd ในอดีต:

CWD:

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

@bmdhacks คุณพูดถูก ข้อสันนิษฐานคือคุณไม่ได้เปลี่ยน 0 $ ตัวอย่างเช่นคุณทำงานได้ทันทีที่สคริปต์เริ่มต้น (ในบล็อกการเริ่มต้น) หรือที่อื่นเมื่อคุณไม่เปลี่ยน $ 0 แต่ $ 0 เป็นวิธีที่ดีในการอธิบายขั้นตอนการเปลี่ยนแปลงภายใต้การมองเห็น 'PS' เครื่องมือยูนิกซ์ :) นี้สามารถแสดงสถานะกระบวนการ Curren ฯลฯ นี้จะขึ้นอยู่กับวัตถุประสงค์โปรแกรมเมอร์ :)
Znik

9

รับเส้นทางที่แน่นอนไปยัง$0หรือ__FILE__เป็นสิ่งที่คุณต้องการ ปัญหาเดียวคือถ้ามีคนทำchdir()และ$0เป็นญาติ - แล้วคุณจะต้องได้รับเส้นทางที่แน่นอนในการBEGIN{}ป้องกันความประหลาดใจใด ๆ

FindBinพยายามใช้สิ่งที่ดีกว่าและคลานไปรอบ ๆ$PATHเพื่อหาสิ่งที่ตรงกันbasename($0)แต่มีบางครั้งที่สิ่งต่าง ๆ น่าประหลาดใจ (โดยเฉพาะ: เมื่อไฟล์นั้น "อยู่ตรงหน้าคุณ" ใน cwd)

File::FuมีFile::Fu->program_nameและFile::Fu->program_dirสำหรับสิ่งนี้


เป็นไปได้หรือไม่ที่ใคร ๆ ก็จะโง่เง่าเหมือนกันchdir()ในการรวบรวมเวลา?
SamB

เพียงแค่ทำงานทั้งหมดตาม dir ปัจจุบันและ $ 0 ที่สคริปต์เริ่มต้น
Znik

7

พื้นหลังสั้น ๆ บางส่วน:

น่าเสียดาย Unix API ไม่ได้จัดเตรียมโปรแกรมที่กำลังทำงานพร้อมพา ธ แบบเต็มไปยังโปรแกรมที่เรียกใช้งานได้ ในความเป็นจริงโปรแกรมที่ดำเนินการของคุณสามารถให้สิ่งที่ต้องการในฟิลด์ที่โดยปกติจะบอกโปรแกรมของคุณว่ามันคืออะไร ขณะที่คำตอบทั้งหมดชี้ให้เห็นว่าฮิวริสติกแบบต่างๆสำหรับการค้นหาผู้สมัครที่เป็นไปได้ แต่ไม่มีอะไรที่สั้นในการค้นหาระบบไฟล์ทั้งหมดจะใช้งานได้เสมอและแม้ว่ามันจะล้มเหลวหากการเคลื่อนย้ายหรือลบการปฏิบัติการ

แต่คุณไม่ต้องการให้โปรแกรม Perl ทำงานได้ซึ่งเป็นสิ่งที่ใช้งานได้จริง แต่สคริปต์นั้นทำงานอยู่ และ Perl จำเป็นต้องรู้ว่าสคริปต์จะค้นหาได้ที่ไหน มันเก็บไว้ใน__FILE__ขณะที่$0มาจาก Unix API สิ่งนี้ยังคงเป็นเส้นทางสัมพัทธ์ดังนั้นให้ทำตามคำแนะนำของ Mark และทำให้เป็นมาตรฐานด้วยFile::Spec->rel2abs( __FILE__ );


__FILE__ยังทำให้ฉันมีเส้นทางญาติ เช่น '.'
felwithe

6

คุณเคยลองไหม:

$ENV{'SCRIPT_NAME'}

หรือ

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

มันขึ้นอยู่กับว่ามันถูกเรียกมาอย่างไรและถ้าเป็น CGI หรือถูกเรียกใช้จากกระสุนปกติ ฯลฯ


$ ENV {'SCRIPT_NAME'} ว่างเปล่าเมื่อสคริปต์ทำงานที่คอนโซล
Putnik

แนวคิดที่ไม่ดีเนื่องจากสภาพแวดล้อม SCRIPT_NAME ขึ้นอยู่กับเชลล์ที่คุณใช้ สิ่งนี้ไม่เข้ากันอย่างสมบูรณ์กับ windows cmd.exe และไม่เข้ากันเมื่อคุณเรียกใช้สคริปต์โดยตรงจากไบนารีอื่น ๆ ไม่มีการรับประกันนี้ตั้งค่าได้ วิธีการดังกล่าวมีประโยชน์มากกว่า
Znik

6

เพื่อให้ได้เส้นทางไปยังไดเรกทอรีที่มีสคริปต์ของฉันฉันใช้ชุดคำตอบที่ให้ไว้แล้ว

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

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

2

perlfaq8ตอบคำถามที่คล้ายกันมากกับการใช้ฟังก์ชั่นบนrel2abs() $0ฟังก์ชันนั้นสามารถพบได้ในไฟล์ :: ข้อมูลจำเพาะ


2

ไม่จำเป็นต้องใช้โมดูลภายนอกเพียงแค่บรรทัดเดียวคุณก็สามารถมีชื่อไฟล์และพา ธ สัมพัทธ์ หากคุณกำลังใช้โมดูลและจำเป็นต้องใช้พา ธ ที่สัมพันธ์กับไดเรกทอรีสคริปต์เส้นทางสัมพัทธ์ก็เพียงพอแล้ว

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

ไม่ได้ให้เส้นทางแบบเต็มของสคริปต์ที่เหมาะสมหากคุณเรียกใช้เช่น "./myscript.pl" เนื่องจากจะแสดงเฉพาะ "." แทน. แต่ฉันก็ยังชอบวิธีนี้
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
โปรดอย่าเพิ่งตอบด้วยรหัส โปรดอธิบายว่าทำไมนี่คือคำตอบที่ถูกต้อง
ลีเทย์เลอร์

1

คุณกำลังมองหาสิ่งนี้หรือไม่:

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

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

ผลลัพธ์จะมีลักษณะดังนี้:

You are running MyFileName.pl now.

มันทำงานได้ทั้งบน Windows และ 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 

ในขณะที่คำตอบเฉพาะแหล่งที่มาอาจแก้ไขคำถามของผู้ใช้ แต่ก็ไม่ได้ช่วยให้พวกเขาเข้าใจว่าเหตุใดจึงใช้งานได้ คุณให้ปลาแก่ผู้ใช้ แต่คุณควรสอนพวกเขาถึงวิธีการตกปลา
มนุษย์ดีบุก

0

ปัญหา__FILE__คือมันจะพิมพ์เส้นทางหลักของโมดูล ". pm" ไม่จำเป็นต้องเป็นเส้นทางสคริปต์ ".cgi" หรือ ".pl" ที่กำลังทำงานอยู่ ฉันเดาว่ามันขึ้นอยู่กับว่าเป้าหมายของคุณคืออะไร

ดูเหมือนว่าฉันCwdจะต้องได้รับการปรับปรุงสำหรับ mod_perl นี่คือคำแนะนำของฉัน:

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;

กรุณาเพิ่มคำแนะนำใด ๆ


0

หากไม่มีโมดูลภายนอกให้ใช้งานได้กับเชลล์ทำงานได้ดีแม้กับ '../'

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

ทดสอบ:

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

เกิดอะไรขึ้นเมื่อคุณเรียก symlink Cwd ใช้งานได้ดีกับกรณีนี้
Znik

0

ปัญหาที่เกิดขึ้นกับการใช้งานเพียงอย่างเดียวdirname(__FILE__)คือมันไม่ได้ติดตาม symlinks ฉันต้องใช้สิ่งนี้สำหรับสคริปต์เพื่อติดตาม symlink ไปยังตำแหน่งไฟล์จริง

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

0

โซลูชันที่ไม่มีไลบรารีทั้งหมดใช้งานได้จริงไม่กี่วิธีในการเขียนเส้นทาง (คิดว่า .. / หรือ /bla/x/../bin/./x/../ เป็นต้น) โซลูชันของฉันดูเหมือน ด้านล่างฉันมีหนึ่งในการเล่นโวหาร: ฉันไม่มีความคิดที่ลึกซึ้งที่สุดว่าทำไมฉันต้องเรียกใช้การแทนที่สองครั้งถ้าฉันไม่มีฉันจะได้รับเก๊ "" / "หรือ" ../ "นอกเหนือจากนั้น ดูเหมือนว่าแข็งแกร่งสำหรับฉัน

  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

ไม่มีคำตอบ "อันดับต้น ๆ " ที่เหมาะสมสำหรับฉัน ปัญหาเกี่ยวกับการใช้ FindBin '$ Bin' หรือ Cwd ก็คือพวกเขากลับเส้นทางที่แน่นอนด้วยการแก้ไขลิงก์สัญลักษณ์ ในกรณีของฉันฉันต้องการเส้นทางที่แน่นอนที่มีลิงก์สัญลักษณ์อยู่ - เช่นเดียวกับคืนคำสั่ง Unix "pwd" และไม่ใช่ "pwd -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

ใน Windows ที่ใช้dirnameและabs_pathทำงานร่วมกันได้ดีที่สุดสำหรับฉัน

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

นี่คือเหตุผล:

# 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

มีอะไรผิดปกติกับ$^X?

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

จะให้เส้นทางแบบเต็มไปยังไบนารีของ Perl ที่ใช้อยู่

ปลิ้น


1
มันให้เส้นทางไปยังไบนารีของ Perl ในขณะที่พา ธ ไปยังสคริปต์ที่ต้องการ
Putnik

-5

บน * ระวังคุณน่าจะมีคำสั่ง "whereis" ซึ่งค้นหา $ PATH ของคุณเพื่อค้นหาไบนารีด้วยชื่อที่กำหนด ถ้า $ 0 ไม่มีชื่อพา ธ แบบเต็มการรัน whereis $ scriptname และการบันทึกผลลัพธ์ลงในตัวแปรควรบอกคุณว่าสคริปต์นั้นอยู่ที่ใด


นั่นจะไม่ทำงานเนื่องจาก $ 0 สามารถส่งคืนพา ธ สัมพัทธ์ไปยังไฟล์: ../perl/test.pl
Lathan

จะเกิดอะไรขึ้นหากสคริปต์ที่รันได้ไม่อยู่ใน PATH
Znik
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.