Як отримати повний шлях до сценарію Perl, який виконується?


168

У мене є скрипт Perl і мені потрібно визначити повний шлях та ім'я файлу під час виконання. Я виявив, що залежно від того, як ви називаєте скрипт, $0змінюється, а іноді містить, fullpath+filenameа іноді просто filename. Оскільки робочий каталог також може відрізнятися, я не можу придумати спосіб надійного отримання fullpath+filenameсценарію.

Хтось отримав рішення?

Відповіді:


251

Є кілька способів:

  • $0 - це сценарій, що виконується в даний час, передбачений POSIX, щодо поточного робочого каталогу, якщо сценарій знаходиться на рівні нижче або нижче
  • Крім того, 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_____, оскільки він може містити ім'я лише зі шляху.
Aftershock

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
Невеликий коментар: Activestate perl для Windows $ 0 зазвичай містить зворотні косої риси та abs_path, що повертаються вперед, та швидке "tr / \ // \\ /;" потрібно було це виправити.
Кріс Мадден

3
хотів додати, що є а realpath, що є синонімом abs_path, якщо ви віддаєте перевагу імені без підкреслення
vol7ron

@Chris, ти повідомив про помилку у технічному обслуговуванні модуля Cwd? Схоже, помилка прийняття Windows.
Зник

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 - це відмінний спосіб змінити опис процесу, який відображається під інструментом 'ps' unix :) Це може відображати стан процесу
curren

9

Отримання абсолютного шляху до $0або __FILE__те , що ви хочете. Єдина біда, якщо хтось зробив, chdir()а $0відносний - тоді вам потрібно отримати абсолютний шлях у aBEGIN{} , щоб запобігти будь -то сюрпризи.

FindBinнамагається піти один кращий і поглядати $PATHна щось відповіднеbasename($0) , але є випадки, коли це робить дуже надто дивні речі (зокрема: коли файл "прямо перед вами" в cwd.)

File::Fuмає File::Fu->program_nameі File::Fu->program_dirдля цього.


Чи справді хтось виявиться таким нерозумним, щоби (назавжди) chdir()під час збирання?
СамБ

Просто почніть всі роботи, засновані на поточному режимі та $ 0 на сценарії.
Зник

7

Короткий короткий фон:

На жаль, API Unix не забезпечує запущену програму з повним шляхом до виконуваного файлу. Насправді програма, що виконує вашу, може надати все, що вона хоче, у полі, яке зазвичай повідомляє вашій програмі, що це таке. Як вказують усі відповіді, є різні евристики для пошуку ймовірних кандидатів. Але нічого, крім пошуку всієї файлової системи, завжди буде працювати, і навіть це не вдасться, якщо виконуваний файл буде переміщений або видалений.

Але ви не хочете виконувати виконуваний Perl, який саме працює, але сценарій, який він виконує. І Перл повинен знати, де сценарій, щоб його знайти. Він зберігає це в __FILE__, поки $0є від Unix API. Це все ще може бути відносним шляхом, тому прийміть пропозицію Марка і канонізуйте йогоFile::Spec->rel2abs( __FILE__ );


__FILE__все ще дає мені відносний шлях. тобто '.'.
Felwit

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()функції ввімкнено $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

Що робити, коли ви називаєте symlink? 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 не містить повного імені шляху, виконання запуску $ scriptname і збереження результату в змінну повинно вказати вам, де знаходиться сценарій.


Це не спрацює, оскільки $ 0 також може повернути відносний шлях до файлу: ../perl/test.pl
Lathan

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