Как найти исходный путь исполняемого скрипта?

Я хочу иметь возможность определить, по какому пути был запущен мой исполняемый скрипт.
Часто это будет не $pwd.

Мне нужно вызывать другие скрипты, которые находятся в структуре папок относительно моего скрипта, и хотя я мог бы жестко закодировать пути, это одновременно неприятно и немного неприятно при попытке перейти от «dev» к «test» к "производство".


person Kevin Buchan    schedule 29.04.2009    source источник
comment
И фиксированные пути ненадежны, если пользователь устанавливает программное обеспечение не там, где вы ожидали...   -  person Jonathan Leffler    schedule 29.04.2009
comment
Эммм.... мой вопрос был помечен как дубликат вопроса, заданного почти ровно через два года после моего? ВТФ?   -  person Kevin Buchan    schedule 20.05.2014
comment
Возможно, это неправильный способ дублирования — как правило, самый старый вопрос должен быть главным (каноническим) вопросом. Стоит ли фиксить, менее понятно — возможно, спросите на MSO. Я отмечаю, что дубликат сам помечен как возможный дубликат третий вопрос, который также был задан после этого.   -  person Jonathan Leffler    schedule 20.05.2014


Ответы (7)


Вездесущий скрипт, изначально опубликованный Джеффри Сновером из команда PowerShell (приведенная в ответе Скайлера) и варианты, опубликованные Китом Седирком и EBGreen, страдают серьезным недостатком --сообщает ли код то, что вы ожидаете, зависит от того, где вы его вызываете!

В приведенном ниже коде эта проблема решается путем простой ссылки на область script вместо области parent:

function Get-ScriptDirectory
{
    Split-Path $script:MyInvocation.MyCommand.Path
}

Чтобы проиллюстрировать проблему, я создал тестовую машину, которая оценивает целевое выражение четырьмя различными способами. (Термины в квадратных скобках являются ключами к следующей таблице результатов.)

  1. встроенный код [встроенный]
  2. встроенная функция, т.е. функция в основной программе [inline function]
  3. Функция с точечным источником, т. е. та же функция, перемещенная в отдельный файл .ps1 [точечный источник]
  4. Модульная функция, т.е. эта же функция вынесена в отдельный файл .psm1 [модуль]

Последние два столбца показывают результат использования области скрипта (например, $script:) или родительской области (с -scope 1). Результат «сценарий» означает, что вызов правильно сообщил о расположении сценария. Результат «модуль» означает, что вызов сообщил о расположении модуля, содержащего функцию, а не сценарий, вызвавший функцию; это указывает на недостаток обеих функций в том, что вы не можете поместить функцию в модуль.

Если оставить в стороне проблему с модулем, то замечательное наблюдение из таблицы заключается в том, что использование подхода с родительской областью в большинстве случаев терпит неудачу (фактически, в два раза чаще, чем успешно).

таблица комбинаций ввода

Наконец, вот тестовый автомобиль:

function DoubleNested()
{
    "=== DOUBLE NESTED ==="
    NestCall
}

function NestCall()
{
    "=== NESTED ==="
    "top level:"
    Split-Path $script:MyInvocation.MyCommand.Path
    #$foo = (Get-Variable MyInvocation -Scope 1).Value
    #Split-Path $foo.MyCommand.Path
    "immediate func call"
    Get-ScriptDirectory1
    "dot-source call"
    Get-ScriptDirectory2
    "module call"
    Get-ScriptDirectory3
}

function Get-ScriptDirectory1
{
    Split-Path $script:MyInvocation.MyCommand.Path
    # $Invocation = (Get-Variable MyInvocation -Scope 1).Value
    # Split-Path $Invocation.MyCommand.Path
}

. .\ScriptDirFinder.ps1
Import-Module ScriptDirFinder -force

"top level:"
Split-Path $script:MyInvocation.MyCommand.Path
#$foo = (Get-Variable MyInvocation -Scope 1).Value
#Split-Path $foo.MyCommand.Path

"immediate func call"
Get-ScriptDirectory1
"dot-source call"
Get-ScriptDirectory2
"module call"
Get-ScriptDirectory3

NestCall
DoubleNested

Содержимое ScriptDirFinder.ps1:

function Get-ScriptDirectory2
{
    Split-Path $script:MyInvocation.MyCommand.Path
#   $Invocation = (Get-Variable MyInvocation -Scope 1).Value
#   Split-Path $Invocation.MyCommand.Path
}

Содержимое ScriptDirFinder.psm1:

function Get-ScriptDirectory3
{
    Split-Path $script:MyInvocation.MyCommand.Path
#   $Invocation = (Get-Variable MyInvocation -Scope 1).Value
#   Split-Path $Invocation.MyCommand.Path
}

Я не знаком с тем, что было представлено в PowerShell 2, но вполне может быть, что в PowerShell 1 не существовало области сценария, когда Джеффри Сновер опубликовал свой пример.

Я был удивлен, когда, хотя я обнаружил, что его пример кода широко распространен в Интернете, он сразу же потерпел неудачу, когда я попробовал его! Но это было потому, что я использовал его не так, как в примере Сновера (я вызвал его не наверху скрипта, а внутри другой функции (мой пример с "вложенным дважды").)

Обновление 2011.09.12

Вы можете прочитать об этом вместе с другими советами и рекомендациями по модулям в моей только что опубликованной статье на Simple-Talk.com: Дальше по кроличьей норе: модули PowerShell и инкапсуляция.

person Michael Sorens    schedule 08.08.2011

Вы отметили свой вопрос для Powershell версии 1.0, однако, если у вас есть доступ к Powershell версии 3.0, вы знаете, что у вас есть $PSCommandPathand$PSScriptRoot, что немного упрощает получение пути к сценарию. Дополнительные сведения см. в разделе "ДРУГИЕ ВОЗМОЖНОСТИ СЦЕНАРИЙ" на этой странице.

person Michael Kelley    schedule 09.10.2013
comment
Блестящий. Спасибо за добавление актуальной информации. Я случайно знал это, но следующий парень, который будет искать, скорее всего, не будет, но будет использовать текущую версию PoSh. - person Kevin Buchan; 10.10.2013

Мы используем такой код в большинстве наших скриптов уже несколько лет без проблем:

#--------------------------------------------------------------------
# Dot source support scripts
#--------------------------------------------------------------------
$ScriptPath = $MyInvocation.MyCommand.Path
$ScriptDir  = Split-Path -Parent $ScriptPath
. $ScriptDir\BuildVars.ps1
. $ScriptDir\LibraryBuildUtils.ps1
. $ScriptDir\BuildReportUtils.ps1
person Keith Hill    schedule 01.05.2009
comment
Обратите внимание, что это не происходит при вызове из функции. - person friism; 10.01.2011
comment
Правда нужно префикс script: как в принятом ответе $script:MyInvocation.MyCommand.Path - person Marc; 09.01.2017

Недавно я столкнулся с той же проблемой. Следующая статья помогла мне решить проблему: http://blogs.msdn.com/powershell/archive/2007/06/19/get-scriptdirectory.aspx

Если вам не интересно, как это работает, вот весь код, который вам нужен в соответствии со статьей:

function Get-ScriptDirectory
{
$Invocation = (Get-Variable MyInvocation -Scope 1).Value
Split-Path $Invocation.MyCommand.Path
}

И тогда вы получите путь, просто выполнив:

$path = Get-ScriptDirectory
person Skyler    schedule 29.04.2009

Я думаю, вы можете найти путь к вашему запущенному скрипту, используя

$MyInvocation.MyCommand.Path

Надеюсь, поможет !

Седрик

person Cédric Rup    schedule 29.04.2009
comment
Это даст вам полный путь, включая имя скрипта, если вы не находитесь в функции. Если вы находитесь в функции, то это будет $null. - person EBGreen; 29.04.2009

Это одна из тех странностей (по крайней мере, на мой взгляд) в PS. Я уверен, что для этого есть вполне веская причина, но мне это все равно кажется странным. Так:

Если вы находитесь в сценарии, но не в функции, $myInvocation.InvocationName предоставит вам полный путь, включая имя сценария. Если вы находитесь в сценарии и внутри функции, $myInvocation.ScriptName даст вам то же самое.

person EBGreen    schedule 29.04.2009
comment
Не используйте $MyInvocation.InvocationName. Если вы используете скрипт с точечным исходным кодом, это свойство вернет .. И если вы используете & для выполнения скрипта, свойство вернет &. Вы также не получите полный путь, если сделаете что-то вроде .\myscript.ps1. - person JasonMArcher; 29.04.2009

Спасибо мсоренс! Это действительно помогло мне с моим пользовательским модулем. Если кто-то заинтересован в создании своего собственного, вот как устроен мой.

MyModule (folder)
 - MyModule.psd1 (help New-ModuleManifest)
 - MyScriptFile.ps1 (ps1 files are easy to test)

Затем вы ссылаетесь на MyScriptFile.ps1 в MyModule.psd1. Ссылка на .ps1 в массиве NestedModules поместит функции в состояние сеанса модуля, а не в состояние глобального сеанса. (Как написать манифест модуля )

NestedModules = @('.\MyScriptFile.ps1','.\MyOtherScriptFile.ps1')

Содержимое MyScriptFile.ps1

function Get-ScriptDirectory {
    Split-Path $script:MyInvocation.MyCommand.Path
}

try {
    Export-ModuleMember -Function "*-*"
}
catch{}

Команда try/catch скрывает ошибку от Export-ModuleMember при запуске MyScriptFile.ps1.

Скопируйте каталог MyModule по одному из путей, найденных здесь $env:PSModulePath.

PS C:\>Import-Module MyModule
PS C:\>Get-Command -Module MyModule

CommandType     Name                                               ModuleName                                                                                                                                                
-----------     ----                                               ----------                                                                                                                                                
Function        Get-ScriptDirectory                                MyModule  
person Coding101    schedule 30.01.2013