Comment obtenir le chemin complet d'un script Perl en cours d'exécution?

168

J'ai un script Perl et je dois déterminer le chemin complet et le nom de fichier du script pendant l'exécution. J'ai découvert que selon la façon dont vous appelez le script $0varie et contient parfois le fullpath+filenameet parfois juste filename. Parce que le répertoire de travail peut également varier, je ne peux pas penser à un moyen d'obtenir de manière fiable fullpath+filenamele script.

Quelqu'un a-t-il une solution?

Chris Madden
la source

Réponses:

251

Il existe plusieurs façons:

  • $0 est le script en cours d'exécution tel que fourni par POSIX, par rapport au répertoire de travail actuel si le script est au niveau ou en dessous du CWD
  • De plus, cwd(), getcwd()et abs_path()sont fournis par le Cwdmodule et vous dire où le script est exécuté à partir
  • Le module FindBinfournit les variables $Bin& $RealBinqui sont généralement le chemin vers le script en cours d'exécution; ce module fournit également $Script& $RealScriptqui sont le nom du script
  • __FILE__ est le fichier réel traité par l'interpréteur Perl lors de la compilation, y compris son chemin complet.

J'ai vu les trois premiers ( $0, le Cwdmodule et le FindBinmodule) échouer de façon mod_perlspectaculaire, produisant une sortie sans valeur telle qu'une '.'chaîne vide. Dans de tels environnements, j'utilise __FILE__et j'obtiens le chemin à partir de celui en utilisant le File::Basenamemodule:

use File::Basename;
my $dirname = dirname(__FILE__);
Drew Stephens
la source
2
C'est vraiment la meilleure solution, surtout si vous avez déjà un $ 0 modifié
Caterham
8
Il semble que abs_path doit être utilisé avec _____FILE_____ car il peut contenir le nom uniquement avec le chemin.
Aftershock
6
@vicTROLLA Probablement parce que la plus grande recommandation de cette réponse (utiliser dirname avec __FILE__) ne fonctionne pas comme prévu? Je me retrouve avec le chemin relatif à partir duquel le script a été exécuté, tandis que la réponse acceptée me donne le chemin absolu complet.
Izkata
10
dirname(__FILE__)ne suit pas les liens symboliques, donc si vous avez lié le fichier exécutable et où dans l'espoir de trouver l'emplacement d'un autre fichier dans l'emplacement d'installation, vous devez vérifier if( -l __FILE__)et ensuite dirname(readlink(__FILE__)).
DavidG
3
@IliaRostovtsev Vous pouvez trouver lorsqu'un module a été inclus dans les modules standard avec ce incantatoire: perl -e 'use Module::CoreList; print Module::CoreList->first_release("File::Basename");'; echo. Pour File::Basenamecela, il y avait Perl 5.0.0, qui a été publié à la fin des années 90 - je pense qu'il est maintenant prêt à être utilisé.
Drew Stephens le
145

$ 0 est généralement le nom de votre programme, alors qu'en est-il de cela?

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

Il me semble que cela devrait fonctionner car abs_path sait si vous utilisez un chemin relatif ou absolu.

Mise à jour Pour quiconque lira ces années plus tard, vous devriez lire la réponse de Drew . C'est bien mieux que le mien.

Ovide
la source
11
Petit commentaire, sur activestate perl sur windows $ 0 contient généralement des barres obliques inverses et abs_path a renvoyé des barres obliques, donc un rapide "tr / \ // \\ /;" était nécessaire pour le réparer.
Chris Madden
3
voulait ajouter qu'il y a un realpath, qui est un synonyme de abs_path, au cas où vous préférez le nom sans trait de soulignement
vol7ron
@Chris, avez-vous signalé un bogue au responsable du module Cwd? Cela ressemble à un bogue d'adoption de Windows.
Znik
1
Autre problème que j'ai: perl -e 'use Cwd "abs_path";print abs_path($0);' impressions/tmp/-e
leonbloy
2
@leonbloy Lorsque vous exécutez un script en ligne (avec -e), je crois que perl crée un fichier temporaire pour stocker votre script en ligne. On dirait que l'emplacement, dans votre cas, est /tmp. À quoi espériez-vous le résultat?
GreenGiant
16

Je pense que le module que vous recherchez est FindBin:

#!/usr/bin/perl
use FindBin;

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

Vous pouvez utiliser FindBin , Cwd , File :: Basename ou une combinaison de ceux-ci. Ils sont tous dans la distribution de base de Perl IIRC.

J'ai utilisé Cwd dans le passé:

Cwd:

use Cwd qw(abs_path);
my $path = abs_path($0);
print "$path\n";
Benjamin W. Smith
la source
@bmdhacks, vous avez raison. La présomption est que vous n'avez pas changé 0 $. Par exemple, vous travaillez ci-dessus dès que le script démarre (dans le bloc d'initialisation), ou ailleurs lorsque vous ne modifiez pas $ 0. Mais $ 0 est un excellent moyen de changer la description du processus visible sous l'outil Unix 'ps' :) Cela peut afficher l'état actuel du processus, etc. Cela dépend du but du programmeur :)
Znik
9

Obtenir le chemin absolu vers $0ou __FILE__est ce que vous voulez. Le seul problème est que si quelqu'un a fait un chdir()et que le $0était relatif - alors vous devez obtenir le chemin absolu dans a BEGIN{}pour éviter toute surprise.

FindBinessaie de faire mieux et de ramper dans le $PATHpour quelque chose qui correspond à basename($0), mais il y a des moments où cela fait des choses beaucoup trop surprenantes (en particulier: lorsque le fichier est "juste devant vous" dans le cwd.)

File::Fua File::Fu->program_nameet File::Fu->program_dirpour cela.

Eric Wilhelm
la source
Est-il vraiment probable que quelqu'un soit assez stupide pour (en permanence) chdir()au moment de la compilation?
SamB
Faites simplement tous les travaux basés sur le répertoire actuel et $ 0 au début du script.
Znik
7

Quelques brèves informations:

Malheureusement, l'API Unix ne fournit pas de programme en cours d'exécution avec le chemin complet de l'exécutable. En fait, le programme qui exécute le vôtre peut fournir tout ce qu'il veut sur le terrain qui indique normalement à votre programme ce qu'il est. Il existe, comme toutes les réponses le soulignent, diverses heuristiques pour trouver des candidats probables. Mais rien de moins qu'une recherche dans tout le système de fichiers fonctionnera toujours, et même cela échouera si l'exécutable est déplacé ou supprimé.

Mais vous ne voulez pas de l'exécutable Perl, qui est ce qui est en cours d'exécution, mais du script qu'il exécute. Et Perl a besoin de savoir où se trouve le script pour le trouver. Il stocke cela dans __FILE__, alors qu'il $0provient de l'API Unix. Cela peut encore être un chemin relatif, alors prenez la suggestion de Mark et canonisez-la avecFile::Spec->rel2abs( __FILE__ );

wnoise
la source
__FILE__me donne encore un chemin relatif. c'est à dire '.'.
felwithe
6

As-tu essayé:

$ENV{'SCRIPT_NAME'}

ou

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

Cela dépend vraiment de la façon dont il est appelé et si c'est CGI ou exécuté à partir d'un shell normal, etc.

Sean
la source
$ ENV {'SCRIPT_NAME'} est vide lorsque le script s'exécute sur la console
Putnik
Mauvaise idée car l'environnement SCRIPT_NAME dépend du shell que vous utilisez. Ceci est totalement incompatible avec Windows cmd.exe et incompatible lorsque vous appelez le script directement à partir d'autres binaires. Il n'y a aucune garantie que cette variable est définie. Les moyens ci-dessus sont beaucoup plus utilisables.
Znik
6

Afin d'obtenir le chemin vers le répertoire contenant mon script, j'ai utilisé une combinaison de réponses déjà données.

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

my $dir = dirname(File::Spec->rel2abs(__FILE__));
Mat
la source
2

perlfaq8 répond à une question très similaire en utilisant la rel2abs()fonction on $0. Cette fonction peut être trouvée dans File :: Spec.

Moritz
la source
2

Il n'est pas nécessaire d'utiliser des modules externes, avec une seule ligne, vous pouvez avoir le nom du fichier et le chemin relatif. Si vous utilisez des modules et devez appliquer un chemin relatif au répertoire de script, le chemin relatif est suffisant.

$0 =~ m/(.+)[\/\\](.+)$/;
print "full path: $1, file name: $2\n";
daniel souza
la source
Il ne fournit pas le chemin complet correct du script si vous l'exécutez comme "./myscript.pl", car il afficherait uniquement "." au lieu. Mais j'aime toujours cette solution.
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
la source
4
Veuillez ne pas simplement répondre par code. Veuillez expliquer pourquoi c'est la bonne réponse.
Lee Taylor
1

Vous cherchez cela?:

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

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

La sortie ressemblera à ceci:

You are running MyFileName.pl now.

Cela fonctionne à la fois sous Windows et Unix.

Yong Li
la source
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
la source
Bien qu'une réponse source uniquement puisse résoudre la question de l'utilisateur, elle ne les aide pas à comprendre pourquoi cela fonctionne. Vous avez donné un poisson à l'utilisateur, mais à la place, vous devez lui apprendre COMMENT pêcher.
the Tin Man
0

Le problème avec __FILE__est qu'il affichera le chemin du module principal ".pm" pas nécessairement le chemin du script ".cgi" ou ".pl" en cours d'exécution. Je suppose que cela dépend de votre objectif.

Il me semble qu'il Cwdsuffit de mettre à jour mod_perl. Voici ma suggestion:

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;

Veuillez ajouter des suggestions.

Jonathan
la source
0

Sans aucun module externe, valable pour le shell, fonctionne bien même avec '../':

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

tester:

$ /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
la source
Que faire lorsque vous appelez symlink? Cwd fonctionne très bien avec ce boîtier.
Znik
0

Le problème avec la simple utilisation dirname(__FILE__)est qu'il ne suit pas les liens symboliques. J'ai dû l'utiliser pour que mon script suive le lien symbolique vers l'emplacement réel du fichier.

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

Toutes les solutions sans bibliothèque ne fonctionnent pas pour plus de quelques façons d'écrire un chemin (pensez ../ ou /bla/x/../bin/./x/../ etc. Ma solution ressemble à ci-dessous. J'ai une particularité: je n'ai pas la moindre idée de la raison pour laquelle je dois exécuter les remplacements deux fois. Sinon, j'obtiens un faux "./" ou "../". À part cela, il me semble assez robuste.

  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
la source
0

Aucune des réponses «top» ne me convenait. Le problème avec l'utilisation de FindBin '$ Bin' ou Cwd est qu'ils renvoient un chemin absolu avec tous les liens symboliques résolus. Dans mon cas, j'avais besoin du chemin exact avec les liens symboliques présents - le même que celui qui renvoie la commande Unix "pwd" et non "pwd -P". La fonction suivante fournit la solution:

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
la source
0

Sur Windows, l'utilisation dirnameet abs_pathensemble fonctionnaient mieux pour moi.

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

Voici pourquoi:

# 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
la source
-2

Quel est le problème avec $^X?

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

Vous donnerait le chemin complet du binaire Perl utilisé.

Evert

utilisateur3061015
la source
1
Il donne le chemin vers le binaire Perl tandis que le chemin vers un script est requis
Putnik
-5

Sur * nix, vous avez probablement la commande "whereis", qui recherche votre $ PATH à la recherche d'un binaire avec un nom donné. Si $ 0 ne contient pas le nom du chemin complet, exécuter whereis $ scriptname et enregistrer le résultat dans une variable devrait vous indiquer où se trouve le script.

Foxxtrot
la source
Cela ne fonctionnera pas, car $ 0 pourrait également renvoyer un chemin relatif vers le fichier: ../perl/test.pl
Lathan
que se passera-t-il si le script exécutable est hors de PATH?
Znik