Wie erhalte ich den vollständigen Pfad zu einem ausgeführten Perl-Skript?

168

Ich habe Perl-Skript und muss den vollständigen Pfad und Dateinamen des Skripts während der Ausführung ermitteln. Ich habe festgestellt, dass das Skript je nach Aufruf $0unterschiedlich ist und manchmal das fullpath+filenameund manchmal nur das Skript enthält filename. Da auch das Arbeitsverzeichnis variieren kann, kann ich mir keine Möglichkeit vorstellen, fullpath+filenamedas Skript zuverlässig abzurufen.

Hat jemand eine Lösung?

Chris Madden
quelle

Antworten:

251

Es gibt einige Möglichkeiten:

  • $0 ist das aktuell ausgeführte Skript, wie es von POSIX bereitgestellt wird, relativ zum aktuellen Arbeitsverzeichnis, wenn sich das Skript im oder unter dem CWD befindet
  • Darüber hinaus cwd(), getcwd()und abs_path()werden von dem mitgelieferten CwdModul und Ihnen sagen , wo das Skript aus betrieben wird
  • Das Modul FindBinstellt die $Bin& $RealBinVariablen zur Verfügung, die normalerweise den Pfad zum ausführenden Skript darstellen. Dieses Modul bietet auch $Script& $RealScriptdas sind die Namen des Skripts
  • __FILE__ ist die eigentliche Datei, mit der sich der Perl-Interpreter während der Kompilierung befasst, einschließlich des vollständigen Pfads.

Ich habe gesehen, dass die ersten drei ( $0, das CwdModul und das FindBinModul) mod_perlspektakulär versagen und wertlose Ausgaben wie '.'oder eine leere Zeichenfolge erzeugen . In solchen Umgebungen verwende __FILE__ich den Pfad und erhalte ihn mithilfe des File::BasenameModuls:

use File::Basename;
my $dirname = dirname(__FILE__);
Drew Stephens
quelle
2
Dies ist wirklich die beste Lösung, besonders wenn Sie bereits einen modifizierten $ 0
Caterham
8
Es sieht so aus, als müsste abs_path mit _____FILE_____ verwendet werden, da es den Namen möglicherweise nur mit dem Pfad enthält.
Nachbeben
6
@vicTROLLA Wahrscheinlich, weil die größte Empfehlung aus dieser Antwort (mit dirname with __FILE__) nicht ganz wie erwartet funktioniert? Am Ende habe ich den relativen Pfad, von dem aus das Skript ausgeführt wurde, während die akzeptierte Antwort mir den vollständigen absoluten Pfad gibt.
Izkata
10
dirname(__FILE__)folgt keinen Symlinks. Wenn Sie also die ausführbare Datei verlinkt haben und hoffen, den Speicherort einer anderen Datei im Installationsspeicherort zu finden, müssen Sie dies überprüfen if( -l __FILE__)und dann dirname(readlink(__FILE__)).
DavidG
3
@IliaRostovtsev Sie können feststellen, wann ein Modul zum ersten Mal in den Standardmodulen mit dieser Beschwörung enthalten war : perl -e 'use Module::CoreList; print Module::CoreList->first_release("File::Basename");'; echo. Dafür File::Basenamewar Perl 5.0.0, das Ende der 90er Jahre veröffentlicht wurde - ich denke, es ist jetzt sicher, es zu verwenden.
Drew Stephens
145

$ 0 ist normalerweise der Name Ihres Programms. Wie wäre es damit?

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

Mir scheint, dass dies funktionieren sollte, da abs_path weiß, ob Sie einen relativen oder absoluten Pfad verwenden.

Update Für alle, die dies Jahre später lesen, sollten Sie Drews Antwort lesen . Es ist viel besser als meins.

Ovid
quelle
11
Kleiner Kommentar zu Activestate Perl unter Windows $ 0 enthält normalerweise Backslashes und abs_path zurückgegebene Schrägstriche, also ein kurzes "tr / \ // \\ /;" wurde benötigt, um es zu beheben.
Chris Madden
3
wollte hinzufügen, dass es ein gibt realpath, was ein Synonym für ist abs_path, falls Sie den Namen ohne Unterstrich bevorzugen
vol7ron
@ Chris, haben Sie dem Cwd-Modulbetreuer einen Fehler gemeldet? Es scheint wie ein Windows-Adoptionsfehler.
Znik
1
Anderes Problem, das ich habe: perl -e 'use Cwd "abs_path";print abs_path($0);' Drucke/tmp/-e
leonbloy
2
@leonbloy Wenn Sie ein Skript inline (mit -e) ausführen , erstellt Perl meiner Meinung nach eine temporäre Datei zum Speichern Ihres Inline-Skripts. Sieht aus wie der Ort in Ihrem Fall ist /tmp. Was haben Sie erwartet?
GreenGiant
16

Ich denke, das Modul, das Sie suchen, ist FindBin:

#!/usr/bin/perl
use FindBin;

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

Sie können FindBin , Cwd , File :: Basename oder eine Kombination davon verwenden. Sie sind alle in der Basisdistribution von Perl IIRC enthalten.

Ich habe in der Vergangenheit Cwd verwendet:

Cwd:

use Cwd qw(abs_path);
my $path = abs_path($0);
print "$path\n";
Benjamin W. Smith
quelle
@bmdhacks, du hast recht. Vermutung ist, dass Sie 0 $ nicht geändert haben. Zum Beispiel arbeiten Sie oben, sobald das Skript startet (im Initialisierungsblock) oder anderswo, wenn Sie $ 0 nicht ändern. Aber $ 0 ist eine hervorragende Möglichkeit, die unter dem Unix-Tool 'ps' sichtbare Prozessbeschreibung zu ändern :) Dies kann den aktuellen Prozessstatus usw. anzeigen. Dies hängt vom Zweck des Programmierers ab :)
Znik
9

Den absoluten Weg zu $0oder zu finden, __FILE__ist das, was Sie wollen. Das einzige Problem ist, wenn jemand einen gemacht hat chdir()und der $0relativ war - dann müssen Sie den absoluten Pfad in a finden BEGIN{}, um Überraschungen zu vermeiden.

FindBinversucht, besser zu werden und in der Suche $PATHnach etwas Passendem zu suchen basename($0), aber es gibt Zeiten, in denen dies viel zu überraschende Dinge bewirkt (insbesondere: wenn die Datei im cwd "direkt vor Ihnen" liegt).

File::Fuhat File::Fu->program_nameund File::Fu->program_dirdafür.

Eric Wilhelm
quelle
Ist es wirklich wahrscheinlich, dass jemand so dumm ist, zur chdir()Kompilierungszeit (dauerhaft) zu arbeiten?
SamB
Führen Sie einfach alle Arbeiten basierend auf dem aktuellen Verzeichnis und $ 0 am Skriptbeginn aus.
Znik
7

Einige kurze Hintergrundinformationen:

Leider bietet die Unix-API kein laufendes Programm mit dem vollständigen Pfad zur ausführbaren Datei. Tatsächlich kann das Programm, das Ihr Programm ausführt, alles bereitstellen, was es in dem Feld möchte, das Ihrem Programm normalerweise sagt, was es ist. Wie alle Antworten zeigen, gibt es verschiedene Heuristiken, um wahrscheinliche Kandidaten zu finden. Die Suche im gesamten Dateisystem funktioniert jedoch immer, und selbst das schlägt fehl, wenn die ausführbare Datei verschoben oder entfernt wird.

Sie möchten jedoch nicht die ausführbare Perl-Datei, die tatsächlich ausgeführt wird, sondern das Skript, das sie ausführt. Und Perl muss wissen, wo sich das Skript befindet, um es zu finden. Es speichert dies in __FILE__, während $0es von der Unix-API stammt. Dies kann immer noch ein relativer Weg sein, also nimm Marks Vorschlag und kanonisiere ihn mitFile::Spec->rel2abs( __FILE__ );

wnoise
quelle
__FILE__gibt mir immer noch einen relativen Weg. dh '.'.
Felwithe
6

Hast du es versucht:

$ENV{'SCRIPT_NAME'}

oder

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

Es hängt wirklich davon ab, wie es aufgerufen wird und ob es CGI ist oder von einer normalen Shell usw. ausgeführt wird.

Sean
quelle
$ ENV {'SCRIPT_NAME'} ist leer, wenn das Skript auf der Konsole ausgeführt wird
Putnik
Schlechte Idee, da die Umgebung von SCRIPT_NAME von der verwendeten Shell abhängt. Dies ist vollständig inkompatibel mit Windows cmd.exe und inkompatibel, wenn Sie das Skript direkt von anderen Binärdateien aus aufrufen. Es gibt keine Garantie, dass diese Wariable eingestellt ist. Die oben genannten Möglichkeiten sind viel nützlicher.
Znik
6

Um den Pfad zu dem Verzeichnis zu erhalten, das mein Skript enthält, habe ich eine Kombination von bereits gegebenen Antworten verwendet.

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

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

perlfaq8 beantwortet eine sehr ähnliche Frage mit der rel2abs()Funktion on $0. Diese Funktion finden Sie in File :: Spec.

moritz
quelle
2

Es müssen keine externen Module verwendet werden. Mit nur einer Zeile können Sie den Dateinamen und den relativen Pfad angeben. Wenn Sie Module verwenden und einen Pfad relativ zum Skriptverzeichnis anwenden müssen, reicht der relative Pfad aus.

$0 =~ m/(.+)[\/\\](.+)$/;
print "full path: $1, file name: $2\n";
Daniel Souza
quelle
Es bietet nicht den richtigen vollständigen Pfad des Skripts, wenn Sie es wie "./myscript.pl" ausführen, da nur "." stattdessen. Aber ich mag diese Lösung immer noch.
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

mkc
quelle
4
Bitte antworten Sie nicht nur mit Code. Bitte erklären Sie, warum dies die richtige Antwort ist.
Lee Taylor
1

Suchst du das?:

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

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

Die Ausgabe sieht folgendermaßen aus:

You are running MyFileName.pl now.

Es funktioniert sowohl unter Windows als auch unter Unix.

Yong Li
quelle
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 
Yordan Georgiev
quelle
Eine Nur-Quelle-Antwort könnte die Frage des Benutzers lösen, hilft ihm jedoch nicht zu verstehen, warum sie funktioniert. Sie haben dem Benutzer einen Fisch gegeben, aber stattdessen sollten Sie ihm beibringen, wie man fischt.
der Blechmann
0

Das Problem mit __FILE__ ist, dass der Pfad des Kernmoduls ".pm" gedruckt wird, nicht unbedingt der Skriptpfad ".cgi" oder ".pl", der ausgeführt wird. Ich denke, es hängt davon ab, was Ihr Ziel ist.

Es scheint mir, dass Cwdnur für mod_perl aktualisiert werden muss. Hier ist mein Vorschlag:

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;

Bitte fügen Sie Vorschläge hinzu.

Jonathan
quelle
0

Ohne externe Module, gültig für Shell, funktioniert auch mit '../' gut:

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

Prüfung:

$ /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
Putnik
quelle
Was ist, wenn Sie symlink aufrufen? Cwd funktioniert hervorragend mit diesem Fall.
Znik
0

Das Problem bei der Verwendung dirname(__FILE__)ist, dass es keinen Symlinks folgt. Ich musste dies für mein Skript verwenden, um dem Symlink zum tatsächlichen Speicherort der Datei zu folgen.

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

Alle bibliotheksfreien Lösungen funktionieren nicht auf mehr als ein paar Arten, um einen Pfad zu schreiben (denken Sie an ../ oder /bla/x/../bin/./x/../ usw. Meine Lösung sieht so aus Ich habe eine Besonderheit: Ich habe nicht die geringste Ahnung, warum ich die Ersetzungen zweimal ausführen muss. Wenn ich das nicht tue, bekomme ich ein falsches "./" oder "../". Abgesehen davon scheint mir ziemlich robust zu sein.

  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/;
Elmar
quelle
0

Keine der "Top" -Antworten war richtig für mich. Das Problem bei der Verwendung von FindBin '$ Bin' oder Cwd besteht darin, dass sie den absoluten Pfad zurückgeben, wobei alle symbolischen Links aufgelöst sind. In meinem Fall brauchte ich den genauen Pfad mit vorhandenen symbolischen Links - genau wie der Unix-Befehl "pwd" und nicht "pwd -P". Die folgende Funktion bietet die Lösung:

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));
}
Drjumper
quelle
0

Unter Windows funktionierte die Verwendung von dirnameund abs_pathzusammen am besten für mich.

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

hier ist der Grund:

# 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";
user3228609
quelle
-2

Was ist los mit $^X?

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

Würde Ihnen den vollständigen Pfad zur verwendeten Perl-Binärdatei geben.

Evert

user3061015
quelle
1
Es gibt Pfad zur Perl-Binärdatei, während Pfad zu einem Skript erforderlich ist
Putnik
-5

Unter * nix haben Sie wahrscheinlich den Befehl "whereis", der Ihren $ PATH nach einer Binärdatei mit einem bestimmten Namen durchsucht. Wenn $ 0 nicht den vollständigen Pfadnamen enthält, sollten Sie durch Ausführen von whereis $ scriptname und Speichern des Ergebnisses in einer Variablen feststellen, wo sich das Skript befindet.

foxxtrot
quelle
Das wird nicht funktionieren, da $ 0 auch einen relativen Pfad zur Datei zurückgeben könnte: ../perl/test.pl
Lathan
Was passiert, wenn das ausführbare Skript nicht über PATH verfügt?
Znik