Jak uzyskać pełną ścieżkę do wykonywanego skryptu Perla?


168

Mam skrypt Perl i muszę określić pełną ścieżkę i nazwę pliku skryptu podczas wykonywania. Odkryłem, że w zależności od tego, jak wywołasz skrypt, $0różni się i czasami zawiera, fullpath+filenamea czasami po prostu filename. Ponieważ katalog roboczy również może się różnić, nie mogę wymyślić sposobu na niezawodne pobranie fullpath+filenameskryptu.

Czy ktoś ma rozwiązanie?

Odpowiedzi:


251

Jest kilka sposobów:

  • $0 jest aktualnie wykonywanym skryptem dostarczonym przez POSIX, względnym w stosunku do bieżącego katalogu roboczego, jeśli skrypt znajduje się na lub poniżej CWD
  • Dodatkowo cwd(), getcwd()i abs_path()są dostarczane przez Cwdmoduł i powiedzieć, gdzie skrypt jest uruchamiany z
  • Moduł FindBindostarcza zmienne $Bin& $RealBin, które zwykle są ścieżką do wykonywanego skryptu; ten moduł zawiera również $Script& $RealScriptto jest nazwa skryptu
  • __FILE__ to rzeczywisty plik, którym zajmuje się interpreter Perla podczas kompilacji, łącznie z pełną ścieżką.

Widziałem trzy pierwsze ( $0Z Cwdmodułu i FindBinmoduł) nie pod mod_perlspektakularnie, produkując takie jak wyjście bezwartościowe '.'lub pusty ciąg. W takich środowiskach używam __FILE__i pobieram ścieżkę z tego za pomocą File::Basenamemodułu:

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

2
To naprawdę najlepsze rozwiązanie, zwłaszcza jeśli masz już zmodyfikowane 0 $
Caterham

8
Wygląda na to, że abs_path musi być używane z _____FILE_____, ponieważ może zawierać nazwę tylko ze ścieżką.
Aftershock

6
@vicTROLLA Prawdopodobnie dlatego, że największa rekomendacja z tej odpowiedzi (używając dirname z __FILE__) nie działa zgodnie z oczekiwaniami? W końcu otrzymuję ścieżkę względną, z której skrypt został wykonany, podczas gdy zaakceptowana odpowiedź daje mi pełną ścieżkę bezwzględną.
Izkata

10
dirname(__FILE__)nie podąża za dowiązaniami symbolicznymi, więc jeśli połączyłeś plik wykonywalny i gdzie masz nadzieję znaleźć lokalizację innego pliku w lokalizacji instalacji, musisz sprawdzić, if( -l __FILE__)a następnie dirname(readlink(__FILE__)).
DavidG

3
@IliaRostovtsev można znaleźć, gdy moduł został po raz pierwszy uwzględniona w standardowych modułów z tego zaklęcia: perl -e 'use Module::CoreList; print Module::CoreList->first_release("File::Basename");'; echo. To File::Basenamebył Perl 5.0.0, który został wydany pod koniec lat 90-tych - myślę, że teraz jest bezpieczny.
Drew Stephens

145

0 $ to zazwyczaj nazwa twojego programu, więc co powiesz na to?

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

Wydaje mi się, że powinno to działać tak, jak abs_path wie, czy używasz ścieżki względnej czy bezwzględnej.

Aktualizacja Każdy, kto czyta to lata później, powinien przeczytać odpowiedź Drew . Jest dużo lepsze niż moje.


11
Mały komentarz, na temat activeestate perl w systemie Windows $ 0 zwykle zawiera ukośniki odwrotne i abs_path zwrócone ukośniki, więc szybkie "tr / \ // \\ /;" trzeba było to naprawić.
Chris Madden,

3
chciałem dodać, że istnieje realpath, co jest synonimem abs_path, jeśli wolisz nazwę bez podkreślenia
vol7ron

@Chris, czy zgłosiłeś błąd do opiekuna modułu Cwd? Wygląda na to, że błąd adopcji systemu Windows.
Znik

1
Inny problem mam: perl -e 'use Cwd "abs_path";print abs_path($0);' wydruki/tmp/-e
leonbloy

2
@leonbloy Kiedy wykonujesz skrypt wbudowany (z -e), wydaje mi się, że Perl tworzy plik tymczasowy do przechowywania skryptu wbudowanego. Wygląda na to, że w twoim przypadku lokalizacja to /tmp. Jaki będzie wynik?
GreenGiant,


16

Myślę, że moduł, którego szukasz, to FindBin:

#!/usr/bin/perl
use FindBin;

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

11

Możesz użyć FindBin , Cwd , File :: Basename lub ich kombinacji. Wszystkie znajdują się w podstawowej dystrybucji Perl IIRC.

W przeszłości korzystałem z Cwd:

Cwd:

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

@bmdhacks, masz rację. Zakłada się, że nie zmieniłeś 0 $. Na przykład, wykonujesz powyższe czynności zaraz po uruchomieniu skryptu (w bloku inicjalizacyjnym) lub w innym miejscu, gdy nie zmienisz $ 0. Ale 0 $ to doskonały sposób na zmianę opisu procesu widocznego pod narzędziem unix 'ps' :) To może pokazać aktualny stan procesu itp.
Zależy

9

Uzyskanie absolutnej ścieżki do $0lub __FILE__jest tym, czego chcesz. Jedynym problemem jest to, że jeśli ktoś zrobił a, chdir()a $0był względny - wtedy musisz uzyskać absolutną ścieżkę w aBEGIN{} aby uniknąć niespodzianek.

FindBinpróbuje pójść o jeden lepszy i czołgać się w $PATHposzukiwaniu czegoś pasującego dobasename($0) , ale są chwile, kiedy robi to zbyt zaskakujące rzeczy (w szczególności: gdy plik znajduje się „tuż przed tobą” w cwd).

File::Fuma File::Fu->program_namei File::Fu->program_dirdo tego.


Czy jest naprawdę prawdopodobne, że ktokolwiek byłby tak głupi, aby (na stałe) chdir()w czasie kompilacji?
SamB

Po prostu wykonaj wszystkie prace w oparciu o bieżący katalog i 0 $ na początku skryptu.
Znik

7

Krótkie tło:

Niestety, Unix API nie zapewnia działającego programu z pełną ścieżką do pliku wykonywalnego. W rzeczywistości program wykonujący twój może dostarczyć wszystko, czego chce, w polu, które normalnie mówi twojemu programowi, czym to jest. Jak wskazują wszystkie odpowiedzi, istnieją różne heurystyki służące do znajdowania prawdopodobnych kandydatów. Ale nic poza przeszukiwaniem całego systemu plików zawsze będzie działać, a nawet to się nie powiedzie, jeśli plik wykonywalny zostanie przeniesiony lub usunięty.

Ale nie chcesz pliku wykonywalnego Perla, który faktycznie działa, ale skrypt, który wykonuje. Perl musi wiedzieć, gdzie skrypt ma go znaleźć. Przechowuje to w __FILE__, podczas gdy $0pochodzi z Unix API. To wciąż może być ścieżka względna, więc skorzystaj z sugestii Marka i kanonizuj jąFile::Spec->rel2abs( __FILE__ );


__FILE__nadal daje mi względną ścieżkę. ie „.”.
felwithe

6

Czy próbowałeś:

$ENV{'SCRIPT_NAME'}

lub

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

To naprawdę zależy od tego, jak jest nazywany i czy jest to CGI, czy jest uruchamiany z normalnej powłoki itp.


$ ENV {'SCRIPT_NAME'} jest puste, gdy skrypt działa na konsoli
Putnik,

Zły pomysł, ponieważ środowisko SCRIPT_NAME zależy od używanej powłoki. Jest to całkowicie niekompatybilne z cmd.exe systemu Windows i niekompatybilne, gdy wywołujesz skrypt bezpośrednio z innych plików binarnych. Nie ma gwarancji, że ta gwarancja jest ustawiona. Powyższe sposoby są znacznie bardziej użyteczne.
Znik

6

Aby uzyskać ścieżkę do katalogu zawierającego mój skrypt, użyłem kombinacji udzielonych już odpowiedzi.

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

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

2

perlfaq8 odpowiada na bardzo podobne pytanie, używając rel2abs()funkcji on $0. Tę funkcję można znaleźć w File :: Spec.


2

Nie ma potrzeby korzystania z zewnętrznych modułów, wystarczy jedna linia, aby podać nazwę pliku i ścieżkę względną. Jeśli używasz modułów i chcesz zastosować ścieżkę względną do katalogu skryptów, wystarczy ścieżka względna.

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

Nie dostarcza prawidłowej pełnej ścieżki do skryptu, jeśli uruchomisz go w stylu „./myscript.pl”, ponieważ pokaże tylko „.” zamiast. Ale nadal lubię to rozwiązanie.
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
Proszę nie odpowiadać tylko kodem. Proszę wyjaśnić, dlaczego jest to prawidłowa odpowiedź.
Lee Taylor

1

Szukasz tego ?:

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

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

Wynik będzie wyglądał następująco:

You are running MyFileName.pl now.

Działa zarówno w systemie Windows, jak i 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 

Chociaż odpowiedź dotycząca samego źródła może rozwiązać pytanie użytkownika, nie pomaga mu zrozumieć, dlaczego to działa. Dałeś użytkownikowi rybę, ale zamiast tego powinieneś nauczyć go JAK łowić.
Tin Man

0

Problem __FILE__polega na tym, że wypisze ścieżkę do modułu podstawowego „.pm”, niekoniecznie ścieżkę skryptu „.cgi” lub „.pl”, który jest uruchomiony. Myślę, że to zależy od tego, jaki jest twój cel.

Wydaje mi się, że Cwdwystarczy zaktualizować mod_perl. Oto moja sugestia:

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;

Dodaj wszelkie sugestie.


0

Bez żadnych zewnętrznych modułów, ważne dla powłoki, działa dobrze nawet z '../':

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

test:

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

Co w przypadku wywołania łącza symbolicznego? Cwd działa doskonale w tym przypadku.
Znik

0

Problem z samym użyciem dirname(__FILE__)polega na tym, że nie podąża za dowiązaniami symbolicznymi. Musiałem użyć tego, aby mój skrypt podążał za dowiązaniem symbolicznym do rzeczywistej lokalizacji pliku.

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

0

Wszystkie rozwiązania bez biblioteki nie działają na więcej niż kilka sposobów zapisania ścieżki (pomyśl ../ lub /bla/x/../bin/./x/../ itd. Moje rozwiązanie wygląda tak poniżej. Mam jedno dziwactwo: nie mam pojęcia, dlaczego muszę dwukrotnie uruchamiać zamienniki. Jeśli nie, otrzymuję fałszywy „./” lub „../”. Poza tym wydaje mi się dość mocny.

  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

Żadna z „najpopularniejszych” odpowiedzi nie była dla mnie odpowiednia. Problem z używaniem FindBin '$ Bin' lub Cwd polega na tym, że zwracają one ścieżkę bezwzględną z rozwiązanymi wszystkimi dowiązaniami symbolicznymi. W moim przypadku potrzebowałem dokładnej ścieżki z obecnymi dowiązaniami symbolicznymi - tak samo, jak zwraca polecenie Unix "pwd", a nie "pwd -P". Następująca funkcja zapewnia rozwiązanie:

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

W systemie Windows najlepiej dla mnie działało używanie dirnamei abs_pathrazem.

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

dlatego:

# 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

Co jest nie tak $^X?

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

Zapewnia pełną ścieżkę do używanego pliku binarnego Perla.

Wynicować


1
Daje ścieżkę do pliku binarnego Perla, podczas gdy wymagana jest ścieżka do skryptu
Putnik,

-5

Na * nix prawdopodobnie masz polecenie "whereis", które przeszukuje $ PATH w poszukiwaniu pliku binarnego o podanej nazwie. Jeśli $ 0 nie zawiera pełnej nazwy ścieżki, uruchomienie whereis $ scriptname i zapisanie wyniku w zmiennej powinno powiedzieć, gdzie znajduje się skrypt.


To nie zadziała, ponieważ $ 0 może również zwrócić względną ścieżkę do pliku: ../perl/test.pl
Lathan

co się stanie, jeśli skrypt wykonywalny znajdzie się poza PATH?
Znik
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.