Bagaimana cara mendapatkan path lengkap ke skrip Perl yang sedang dieksekusi?


168

Saya memiliki skrip Perl dan perlu menentukan path lengkap dan nama file skrip selama eksekusi. Saya menemukan bahwa tergantung pada bagaimana Anda memanggil skrip $0bervariasi dan kadang-kadang berisi fullpath+filenamedan kadang-kadang adil filename. Karena direktori yang berfungsi dapat bervariasi juga, saya tidak dapat memikirkan cara untuk mendapatkan fullpath+filenamescript secara andal .

Adakah yang punya solusi?

Jawaban:


251

Ada beberapa cara:

  • $0 adalah skrip yang saat ini sedang dijalankan sebagaimana disediakan oleh POSIX, relatif terhadap direktori kerja saat ini jika skrip berada pada atau di bawah CWD
  • Selain itu, cwd(), getcwd()dan abs_path()disediakan olehCwd modul dan memberitahu Anda di mana script sedang dijalankan dari
  • Modul ini FindBinmenyediakan $Bin& $RealBinvariabel yang biasanya merupakan jalur ke skrip pelaksana; modul ini juga menyediakan $Script& $RealScriptyang merupakan nama skrip
  • __FILE__ adalah file aktual yang ditangani interpreter Perl selama kompilasi, termasuk path lengkapnya.

Saya telah melihat tiga pertama ( $0, Cwdmodul dan FindBinmodul) gagal secara mod_perlspektakuler, menghasilkan output yang tidak berharga seperti '.'atau string kosong. Dalam lingkungan seperti itu, saya menggunakan __FILE__dan mendapatkan jalur dari itu menggunakan File::Basenamemodul:

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

2
Ini benar-benar solusi terbaik, terutama jika Anda sudah memiliki $ 0 yang dimodifikasi
Caterham

8
Sepertinya abs_path perlu digunakan dengan _____FILE_____ karena dapat berisi nama hanya dengan path.
Aftershock

6
@vicTROLLA Mungkin karena rekomendasi terbesar dari jawaban ini (menggunakan dirname dengan __FILE__) tidak berfungsi seperti yang diharapkan? Saya berakhir dengan jalur relatif dari tempat skrip dieksekusi, sementara jawaban yang diterima memberi saya jalur absolut penuh.
Izkata

10
dirname(__FILE__)tidak mengikuti symlinks, jadi jika Anda menautkan file yang dapat dieksekusi dan di mana berharap menemukan lokasi beberapa file lain di lokasi instal Anda perlu memeriksa if( -l __FILE__)dan kemudian dirname(readlink(__FILE__)).
DavidG

3
@IliaRostovtsev Anda dapat menemukan ketika modul pertama kali dimasukkan dalam modul standar dengan mantera ini: perl -e 'use Module::CoreList; print Module::CoreList->first_release("File::Basename");'; echo. Untuk File::Basenameitu adalah Perl 5.0.0, yang dirilis pada akhir 90-an — saya pikir ini aman untuk digunakan sekarang.
Drew Stephens

145

$ 0 biasanya nama program Anda, jadi bagaimana dengan ini?

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

Menurut saya ini seharusnya berfungsi karena abs_path tahu jika Anda menggunakan jalur relatif atau absolut.

Pembaruan Untuk siapa pun yang membaca ini tahun kemudian, Anda harus membaca jawaban Drew . Ini jauh lebih baik daripada milikku.


11
Komentar kecil, pada perl aktivasi di windows $ 0 biasanya berisi garis miring terbalik dan abs_path garis miring ke belakang, jadi "tr / \ // \\ \\ /;" diperlukan untuk memperbaikinya.
Chris Madden

3
ingin menambahkan bahwa ada realpath, yang merupakan sinonim dengan abs_path, jika Anda lebih suka nama tanpa garis bawah
vol7ron

@ Chris, apakah Anda melaporkan bug ke pengelola modul Cwd? Sepertinya bug adopsi Windows.
Znik

1
Masalah lain yang saya miliki: perl -e 'use Cwd "abs_path";print abs_path($0);' cetakan/tmp/-e
leonbloy

2
@leonbloy Ketika Anda menjalankan skrip inline (dengan -e), saya percaya perl membuat file temp untuk menyimpan skrip inline Anda. Sepertinya lokasi, dalam kasus Anda, adalah /tmp. Apa yang Anda harapkan dari hasilnya?
GreenGiant


16

Saya pikir modul yang Anda cari adalah FindBin:

#!/usr/bin/perl
use FindBin;

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

11

Anda bisa menggunakan FindBin , CWD , File :: basename , atau kombinasi dari mereka. Semuanya ada di distribusi dasar Perl IIRC.

Saya menggunakan Cwd di masa lalu:

Cwd:

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

@ bmdhacks, kamu benar. Anggapannya adalah, Anda tidak mengubah $ 0. Misalnya Anda melakukan pekerjaan di atas segera setelah skrip dimulai (di blok inisialisasi), atau di tempat lain ketika Anda tidak mengubah $ 0. Tetapi $ 0 adalah cara terbaik untuk mengubah deskripsi proses yang terlihat di bawah alat 'unix' ps ':) Ini dapat menunjukkan status proses curren, dll. Ini tergantung pada tujuan programmer :)
Znik

9

Mendapatkan jalur absolut $0atau __FILE__apa yang Anda inginkan. Satu-satunya masalah adalah jika seseorang melakukan chdir()dan $0itu relatif - maka Anda perlu mendapatkan jalur absolut dalam BEGIN{}untuk mencegah kejutan.

FindBinmencoba untuk pergi yang lebih baik dan merendahkan diri di dalam $PATHuntuk sesuatu yang cocokbasename($0) , tetapi ada kalanya hal-hal yang jauh terlalu mengejutkan (khususnya: ketika file "tepat di depan Anda" di cwd.)

File::Fumemiliki File::Fu->program_namedan File::Fu->program_diruntuk ini.


Apakah benar-benar ada orang yang sebodoh itu (secara permanen) chdir()pada waktu kompilasi?
SamB

Cukup lakukan semua pekerjaan berdasarkan dir saat ini dan $ 0 pada saat skrip dimulai.
Znik

7

Beberapa latar belakang pendek:

Sayangnya Unix API tidak menyediakan program yang berjalan dengan jalur lengkap ke yang dapat dieksekusi. Bahkan, program yang menjalankan program Anda dapat memberikan apa pun yang diinginkan di bidang yang biasanya memberi tahu program Anda tentang apa itu. Ada, seperti semua jawaban tunjukkan, berbagai heuristik untuk menemukan calon yang mungkin. Tapi tidak ada kekurangan mencari seluruh sistem file akan selalu bekerja, dan bahkan itu akan gagal jika executable dipindahkan atau dihapus.

Tetapi Anda tidak ingin Perl dapat dieksekusi, yang sebenarnya sedang berjalan, tetapi skrip yang dieksekusi. Dan Perl perlu tahu di mana skrip itu menemukannya. Ini menyimpan ini __FILE__, sementara $0dari API Unix. Ini masih bisa menjadi jalur relatif, jadi terima saran Mark dan dikanonkanFile::Spec->rel2abs( __FILE__ );


__FILE__masih memberi saya jalan relatif. yaitu '.'.
felwithe

6

Sudahkah Anda mencoba:

$ENV{'SCRIPT_NAME'}

atau

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

Itu benar-benar tergantung pada bagaimana itu dipanggil dan apakah itu CGI atau dijalankan dari shell normal, dll.


$ ENV {'SCRIPT_NAME'} kosong ketika skrip berjalan di konsol
Putnik

Gagasan buruk karena lingkungan SCRIPT_NAME tergantung pada shell yang Anda gunakan. Ini sepenuhnya tidak kompatibel dengan windows cmd.exe, dan tidak kompatibel ketika Anda memanggil skrip langsung dari binari lain. Tidak ada garansi wariable ini diatur. Cara di atas jauh lebih bermanfaat.
Znik

6

Untuk mendapatkan jalur ke direktori yang berisi skrip saya, saya telah menggunakan kombinasi jawaban yang sudah diberikan.

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

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

2

perlfaq8 menjawab pertanyaan yang sangat mirip dengan menggunakan rel2abs()fungsi aktif $0. Fungsi itu dapat ditemukan di File :: Spec.


2

Tidak perlu menggunakan modul eksternal, hanya dengan satu baris Anda dapat memiliki nama file dan jalur relatif. Jika Anda menggunakan modul dan perlu menerapkan jalur relatif ke direktori skrip, jalur relatif sudah cukup.

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

Itu tidak menyediakan path lengkap yang tepat dari skrip jika Anda menjalankannya seperti "./myscript.pl", karena hanya akan menampilkan "." sebagai gantinya. Tapi saya masih suka solusi ini.
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
Tolong jangan hanya menjawab dengan kode. Tolong jelaskan mengapa ini adalah jawaban yang benar.
Lee Taylor

1

Apakah Anda mencari ini ?:

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

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

Outputnya akan terlihat seperti ini:

You are running MyFileName.pl now.

Ini bekerja pada Windows dan 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 

Meskipun jawaban hanya sumber mungkin memecahkan pertanyaan pengguna, itu tidak membantu mereka memahami mengapa itu bekerja. Anda telah memberi pengguna ikan, tetapi Anda harus mengajari mereka CARA memancing.
the Tin Man

0

Masalahnya __FILE__adalah bahwa ia akan mencetak jalur modul inti ".pm" belum tentu jalur skrip ".cgi" atau ".pl" yang sedang berjalan. Saya kira itu tergantung pada apa tujuan Anda.

Menurut saya itu Cwdhanya perlu diperbarui untuk mod_perl. Ini saran saya:

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;

Silakan tambahkan saran.


0

Tanpa modul eksternal, valid untuk shell, berfungsi dengan baik bahkan dengan '../':

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

uji:

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

Bagaimana ketika Anda memanggil symlink? Cwd bekerja sangat baik dengan kasus ini.
Znik

0

Masalah dengan hanya menggunakan dirname(__FILE__)adalah tidak mengikuti symlink. Saya harus menggunakan ini untuk skrip saya untuk mengikuti symlink ke lokasi file yang sebenarnya.

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

0

Semua solusi bebas pustaka sebenarnya tidak berfungsi lebih dari beberapa cara untuk menulis lintasan (pikirkan ../ atau /bla/x/../bin/./x/../ dll. Solusi saya terlihat seperti di bawah ini. Saya punya satu kekhasan: Saya tidak punya ide sedikit pun mengapa saya harus menjalankan penggantian dua kali. Jika tidak, saya mendapatkan palsu "./" atau "../". Selain itu, itu sepertinya cukup kuat untukku.

  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

Tidak ada jawaban "atas" yang tepat untuk saya. Masalah dengan menggunakan FindBin '$ Bin' atau Cwd adalah mereka mengembalikan path absolut dengan semua tautan simbolik diselesaikan. Dalam kasus saya, saya membutuhkan jalur yang tepat dengan tautan simbolik yang ada - sama seperti mengembalikan perintah Unix "pwd" dan bukan "pwd -P". Fungsi berikut menyediakan solusinya:

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

Di Windows, menggunakan dirnamedan abs_pathbersama - sama bekerja paling baik untuk saya.

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

inilah sebabnya:

# 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

Ada apa dengan ini $^X?

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

Akan memberi Anda path lengkap ke biner Perl yang digunakan.

Evert


1
Ini memberi jalur ke biner Perl sementara jalur ke skrip diperlukan
Putnik

-5

Pada * nix, Anda mungkin memiliki perintah "whereis", yang mencari $ PATH Anda mencari biner dengan nama yang diberikan. Jika $ 0 tidak mengandung nama path lengkap, menjalankan $ scriptname di mana dan menyimpan hasilnya ke dalam variabel akan memberi tahu Anda di mana skrip berada.


Itu tidak akan berfungsi, karena $ 0 juga bisa mengembalikan jalur relatif ke file: ../perl/test.pl
Lathan

apa yang akan terjadi jika skrip yang dapat dieksekusi keluar dari PATH?
Znik
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.