Как получить полный путь к исполняемому скрипту 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_____, поскольку он может содержать имя только с путем.
Афтершок
6
@vicTROLLA Возможно, потому что самая большая рекомендация из этого ответа (используя dirname с __FILE__) не работает так, как ожидалось? В итоге я получаю относительный путь от места выполнения сценария, а принятый ответ дает мне полный абсолютный путь.
Изката,
10
dirname(__FILE__)не следует по символическим ссылкам, поэтому, если вы связали исполняемый файл и где в надежде найти расположение какого-либо другого файла в месте установки, вам необходимо проверить, 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-х годов - я думаю, что теперь он безопасен для использования.
Дрю Стивенс
145

$ 0, как правило, название вашей программы, так как насчет этого?

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

Мне кажется, это должно работать, так как abs_path знает, используете ли вы относительный или абсолютный путь.

Обновление Для тех, кто читает это несколько лет спустя, вы должны прочитать ответ Дрю . Это намного лучше, чем у меня.

Овидий
источник
11
Небольшой комментарий о perl activestate для windows $ 0 обычно содержит обратную косую черту, а abs_path возвращает прямую косую черту, поэтому быстрый "tr / \ // \\ /;" было необходимо исправить это.
Крис Мэдден
3
хотел бы добавить, что есть 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";
bmdhacks
источник
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 - отличный способ изменить описание процесса, видимое в Unix-инструменте «ps» :) Это может показать текущее состояние процесса и т. Д. Это зависит от цели программиста :)
Znik
9

Получить абсолютный путь к $0или __FILE__то, что вы хотите. Единственная проблема в том, что если кто-то сделал a, chdir()а a $0был относительным - тогда вам нужно найти абсолютный путь в a, BEGIN{}чтобы предотвратить какие-либо сюрпризы.

FindBinпытается пойти лучше и найти что- $PATHто подходящее basename($0), но бывают случаи, когда это делает слишком удивительные вещи (в частности: когда файл «прямо перед вами» в cwd.)

File::Fuимеет File::Fu->program_nameи File::Fu->program_dirдля этого.

Эрик Вильгельм
источник
Действительно ли вероятно, что кто-то будет настолько глуп, чтобы (навсегда) chdir()во время компиляции?
SamB
Просто выполните все работы, основываясь на текущем каталоге и $ 0 в начале скрипта.
Znik
7

Краткий обзор:

К сожалению, API Unix не предоставляет запущенной программе полный путь к исполняемому файлу. Фактически, программа, выполняющая вашу, может предоставить все, что она хочет в поле, которое обычно сообщает вашей программе, что это такое. Как показывают все ответы, существуют различные эвристические методы поиска вероятных кандидатов. Но ничего кроме поиска по всей файловой системе не всегда будет работать, и даже это не даст результата, если исполняемый файл будет перемещен или удален.

Но вам не нужен исполняемый файл Perl, который фактически выполняется, а сценарий, который он выполняет. И Perl должен знать, где находится скрипт, чтобы его найти. Он хранит это в __FILE__то время $0как из Unix API. Это все еще может быть относительный путь, поэтому возьмите предложение Марка и канонизируйте егоFile::Spec->rel2abs( __FILE__ );

wnoise
источник
__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__));
Matt
источник
2

Perlfaq8 отвечает на очень похожий вопрос, используя rel2abs()функцию on $0. Эту функцию можно найти в File :: Spec.

Moritz
источник
2

Нет необходимости использовать внешние модули, с одной строкой вы можете иметь имя файла и относительный путь. Если вы используете модули и вам нужно применить путь относительно каталога скрипта, то достаточно относительного пути.

$0 =~ m/(.+)[\/\\](.+)$/;
print "full path: $1, file name: $2\n";
Даниэль Соуза
источник
Он не обеспечивает правильный полный путь скрипта, если вы запустите его как «./myscript.pl», так как он будет показывать только «.» вместо. Но мне все еще нравится это решение.
Кев
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
источник
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
Putnik
источник
Что когда вы вызываете символическую ссылку? Cwd отлично работает с этим делом.
Znik
0

Проблема только с использованием dirname(__FILE__)заключается в том, что он не следует символическим ссылкам. Мне пришлось использовать это для моего скрипта, чтобы перейти по символической ссылке на фактическое местоположение файла.

use File::Basename;
my $script_dir = undef;
if(-l __FILE__) {
  $script_dir = dirname(readlink(__FILE__));
}
else {
  $script_dir = dirname(__FILE__);
}
DavidG
источник
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/;
Elmar
источник
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));
}
drjumper
источник
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";
user3228609
источник
-2

Что не так с $^X?

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

Даст вам полный путь к используемому бинарному Perl.

выворачивать наизнанку

user3061015
источник
1
Он дает путь к бинарному Perl, а путь к необходимому скрипту
Putnik
-5

В * nix у вас, скорее всего, есть команда whereis, которая ищет ваш $ PATH в поисках двоичного файла с заданным именем. Если $ 0 не содержит полное имя пути, выполнение whereis $ scriptname и сохранение результата в переменную должны сообщить вам, где находится скрипт.

foxxtrot
источник
Это не будет работать, а $ 0 также может возвращать относительный путь к файлу: ../perl/test.pl
Lathan
что произойдет, если исполняемый скрипт находится вне PATH?
Znik