Как получить полный путь к исполняемому скрипту 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";

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__ );


__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__));

2

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


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


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

Что когда вы вызываете символическую ссылку? 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__);
}

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

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));
}

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

-2

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

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

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

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


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

-5

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


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

что произойдет, если исполняемый скрипт находится вне PATH?
Znik
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.