Как PowerShell обрабатывает "." в дорожках?

Рассмотрим следующую последовательность команд при открытии терминала PowerShell:

PS C:\Users\username> cd source
PS C:\Users\username\source> $dir = ".\temp"
PS C:\Users\username\source> [System.IO.Path]::GetFullPath($dir)
C:\Users\username\temp

Теперь это:

PS C:\Users\username> cd source
PS C:\Users\username\source> powershell
Windows PowerShell
Copyright (C) Microsoft Corporation. All rights reserved.

Try the new cross-platform PowerShell https://aka.ms/pscore6

PS C:\Users\username\source> $dir = ".\temp"
PS C:\Users\username\source> [System.IO.Path]::GetFullPath($dir)
C:\Users\username\source\temp

Почему PowerShell интерпретирует "." относительно каталога, в котором был запущен PowerShell, а не текущего каталога? Как я могу заставить его интерпретировать "." относительно текущего каталога?

3 ответа

Решение

Как js2010 полезно ответа государств, это использование метода.NET, что создает проблему:
.NET в единый, процесс в масштабах текущего каталога, как правило, и по дизайну[1] отличается от PowerShell в пространство выполнения конкретных один.

Это имеет следующие последствия:

  • Поскольку PowerShell сама делает надежно интерпретировать.в качестве текущего местоположения (которое является обобщением PowerShell концепции текущего каталога, который может относиться также к другим типам местоположений, на дисках, предоставляемых другими поставщиками дисков PowerShell, такими как поставщик реестра), вы можете избежать проблемы, используя Команды PowerShell, если они доступны.

  • При вызове методов.NET не забудьте заранее преобразовать все относительные пути в абсолютные или, если это поддерживается, дополнительно указать текущее расположение файловой системы PowerShell в качестве справочного каталога - это позволяет избежать проблемы несовпадения текущих каталогов.

    • (Другой, но неоптимальный вариант - сначала установить [Environment]::CurrentDirectory = $PWD.ProviderPath каждый раз, когда передается относительный путь, но это неуклюже и не должно использоваться, если есть вероятность, что в одном процессе существует несколько пространств выполнения PowerShell.)

В следующем разделе показано, как безопасно передавать относительные пути PowerShell к методам.NET, тогда как нижний раздел решает конкретную проблему в вашем вопросе: как разрешить произвольный заданный путь PowerShell к абсолютному, собственному пути файловой системы.


Безопасная передача известного относительного пути PowerShell в метод.NET:

Как уже говорилось, несоответствие в текущих каталогах требует, чтобы методам.NET был передан абсолютный путь, полученный на основе текущего каталога PowerShell.

Примеры предполагают относительный путь someFile.txt для передачи в метод.NET [IO.File]::ReadAllText()

Обратите внимание, что используется простая строковая интерполяция с / (который может использоваться взаимозаменяемо с \) используется для соединения компонентов пути; если текущий каталог является корневым каталогом, вы получите два разделителя пути, но это не повлияет на функциональность. Однако, если вам все же нужно избежать этого, используйтеJoin-Path командлет вместо этого.

  • Самый простой, но не полностью надежный, через$PWD(не выполняется, если текущий каталог основан на диске PowerShell, созданном с помощьюNew-PsDriveили если текущее местоположение не является местоположением файловой системы):
[IO.File]::ReadAllText("$PWD/someFile.txt")
  • Более надежный: через$PWD.ProviderPath(разрешает путь на основе диска PowerShell к базовому пути собственной файловой системы, но все равно может завершиться ошибкой, если текущее местоположение не является местоположением файловой системы):
[IO.File]::ReadAllText("$($PWD.ProviderPath)/someFile.txt")
  • Полностью прочный: через(Get-Location -PSProvider FileSystem).ProviderPath
[IO.File]::ReadAllText("$((Get-Location -PSProvider FileSystem).ProviderPath)/someFile.txt")

Примечание. Приведенное выше работает как с существующими, так и с несуществующими путями; если известно, что путь существует - например, с[IO.File]::ReadAllText(), в отличие от [IO.File]::WriteAllText()- вы также можете использовать следующее, но только если вы дополнительно предполагаете, что текущее местоположение является местоположением файловой системы:

[IO.File]::ReadAllText((Convert-Path -LiteralPath someFile.txt))

Что Convert-Path а также Resolve-Pathработать только с существующими путями (начиная с PowerShell Core 7.0.0-preview.3) неудачно; предоставление согласия на несуществующий путь было предложено на GitHub.

Точно так же было бы полезно, если бы Convert-Path а также Resolve-Path поддержал -PSProvider параметр, позволяющий явно указать целевого поставщика, как Get-Locationуже поддерживает - см. это предложение на GitHub.


Преобразование заданного произвольного пути файловой системы PowerShell в абсолютный собственный путь:

Если путь существует, используйтеConvert-Path чтобы преобразовать любой путь к файловой системе PowerShell в абсолютный, родной для файловой системы:

$dir = "./temp"
Convert-Path -LiteralPath $dir

Связанные Resolve-Pathкомандлет предоставляет аналогичные функции, но не разрешает пути на основе дисков, специфичных для PowerShell (созданных с помощьюNew-PsDrive) к их исходным путям файловой системы.

Если путь не существует (пока):

В PowerShell Core, построенном на.NET Core, вы можете использовать новый[IO.Path]::GetFullPath() перегрузка, которая принимает справочный каталог для указанного относительного пути:

$dir = "./temp"
[IO.Path]::GetFullPath($dir, $PWD.ProviderPath)

Обратите внимание на собственный путь файловой системы текущего местоположения, $PWD.ProviderPath, передается как справочный каталог.

Предостережение: если есть вероятность, что текущее местоположение находится на диске, отличном от диска файловой системы, используйте
(Get-Location -PSProvider FileSystem).ProviderPathдля надежной ссылки на текущее расположение (каталог) файловой системы.

В Windows PowerShell вы можете использовать[IO.Path]::Combine(), но учтите, что вам придется удалить ./ префикс вручную, если он вам не нужен в результирующем пути:

$dir = "./temp"
[IO.Path]::Combine($PWD.ProviderPath, $dir -replace '^.[\\/]')

[1] В то время как данный процесс обычно имеет только одно пространство выполнения PowerShell (сеанс), возможность сосуществования нескольких из них в процессе означает, что для всех из них концептуально невозможно синхронизировать свои отдельные рабочие каталоги с одним-единственным процессом - широкий рабочий каталог.NET. Более подробное объяснение можно найти в этой проблеме на GitHub.

Powershell лечит .как текущее местоположение. Итак, если вы это сделаетеGet-ChildItem -Path ".\", это вернет все в текущем каталоге. Чтобы получить относительный путь, вам нужно сделать что-то вроде этого:

Set-Location -Path "Path"
$path = Get-Item -Path "file" | Resolve-Path -Relative

$path теперь будет относительный путь

Другие вопросы по тегам