Welcome to WuJiGu Developer Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
594 views
in Technique[技术] by (71.8m points)

windows - How does PowerShell treat "." in paths?

Consider the following sequence of commands upon opening a PowerShell terminal:

PS C:Usersusername> cd source
PS C:Usersusernamesource> $dir = ".emp"
PS C:Usersusernamesource> [System.IO.Path]::GetFullPath($dir)
C:Usersusernameemp

Now this:

PS C:Usersusername> cd source
PS C:Usersusernamesource> powershell
Windows PowerShell
Copyright (C) Microsoft Corporation. All rights reserved.

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

PS C:Usersusernamesource> $dir = ".emp"
PS C:Usersusernamesource> [System.IO.Path]::GetFullPath($dir)
C:Usersusernamesourceemp

Why does PowerShell interpret "." relative to the directory in which PowerShell was started, rather than the current directory? How can I get it to interpret "." relative to the current directory?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

As js2010's helpful answer states, it is the use of a .NET method that introduces the problem:
.NET's single, process-wide current directory typically and by design[1] differs from PowerShell's runspace-specific one.

This has the following implications:

  • Since PowerShell itself does reliably interpret . as the current location (which is PowerShell's generalization of the concept of a current directory that can refer to other types of locations as well, on drives exposed by other PowerShell drive providers, such as the registry provider), you can avoid the problem by using PowerShell commands, if available.

  • When you do call .NET methods, be sure to resolve any relative paths to absolute ones beforehand or, where supported, additionally supply the current PowerShell filesystem location as a reference directory - this avoids the problem of the mismatched current directories.

    • (Another, but suboptimal option is to first set [Environment]::CurrentDirectory = $PWD.ProviderPath every time a relative path is passed, but that is clumsy and shouldn't be used if there's a chance that multiple PowerShell runspaces exist in the same process.)

The next section shows how to safely pass relative PowerShell paths to .NET methods, whereas the bottom section solves the specific problem in your question: how to resolve an arbitrary, given PowerShell path to an absolute, native filesystem path.


Passing a known relative PowerShell path safely to a .NET method:

As stated, the discrepancy in current directories requires that an absolute path be passed to .NET methods, arrived at based on PowerShell's current directory.

The examples assume relative path someFile.txt to be passed to .NET method [IO.File]::ReadAllText()

Note that simple string interpolation is used, with / (which can be used interchangeably with ) used to join the path components; if the current directory happens to be the root directory, you'll end up with 2 path separators, but that doesn't affect functionality. If you still need to avoid that, however, use the Join-Path cmdlet instead.

  • Simplest, but not fully robust, via$PWD (fails if the current directory is based on a PowerShell-specific drive created with New-PsDrive or if the current location is not a filesystem location):
[IO.File]::ReadAllText("$PWD/someFile.txt")
  • More robust: via $PWD.ProviderPath (resolves a PowerShell drive-based path to the underlying native filesystem path, but can still fail if the current location is not a filesystem location):
[IO.File]::ReadAllText("$($PWD.ProviderPath)/someFile.txt")
  • Fully robust: via (Get-Location -PSProvider FileSystem).ProviderPath
[IO.File]::ReadAllText("$((Get-Location -PSProvider FileSystem).ProviderPath)/someFile.txt")

Note: The above works with both existent and nonexistent paths; if the path is known to exist - such as with [IO.File]::ReadAllText(), as opposed to [IO.File]::WriteAllText() - you can also use the following, but only if you can further assume that the current location is a filesystem location:

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

That Convert-Path and Resolve-Path only work with existing paths (as of PowerShell Core 7.0.0-preview.3) is unfortunate; providing an opt-in for nonexistent path has been proposed on GitHub.

Similarly, it would be helpful if Convert-Path and Resolve-Path supported a -PSProvider parameter to allow specifying the target provider explicitly, as Get-Location already supports - see this suggestion on GitHub.


Resolving a given, arbitrary PowerShell filesystem path to an absolute native path:

If the path exists, use Convert-Path to resolve any PowerShell filesystem path to an absolute, filesystem-native one:

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

The related Resolve-Path cmdlet provides similar functionality, but it doesn't resolve paths based on PowerShell-specific drives (created with New-PsDrive) to their underlying native filesystem paths.

If the path doesn't exist (yet):

In PowerShell Core, which builds on .NET Core, you can use the new [IO.Path]::GetFullPath() overload that accepts a reference directory for the specified relative path:

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

Note how the current location's native filesystem path, $PWD.ProviderPath, is passed as the reference directory.

Caveat: If there's a chance that the current location is on a drive other than a filesystem drive, use
(Get-Location -PSProvider FileSystem).ProviderPath to reliably refer to the current filesystem location (directory).

In Windows PowerShell, you can use [IO.Path]::Combine(), but note that you'll have to remove the ./ prefix manually if you don't want it in the resulting path:

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

[1] While a given process typically has only one PowerShell runspace (session), the potential for multiple ones to coexist in a process means that it is conceptually impossible for all of them to sync their individual working directories with the one and only process-wide .NET working directory. For a more in-depth explanation, see this GitHub issue.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to WuJiGu Developer Q&A Community for programmer and developer-Open, Learning and Share
...