PowerShell 如何处理路径中的"."?



打开PowerShell终端时,请考虑以下命令序列:

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

现在这个:

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 = ".temp"
PS C:Usersusernamesource> [System.IO.Path]::GetFullPath($dir)
C:Usersusernamesourcetemp

为什么 PowerShell 相对于启动 PowerShell 的目录而不是当前目录来解释"."?如何让它相对于当前目录解释"."?

正如 js2010 的有用答案所述,使用.NET 方法引入了问题
。NET的单个进程范围的当前目录通常和设计[1]PowerShell的特定于运行空间的目录不同。

这具有以下含义:

  • 由于PowerShell 本身确实能够可靠地将.解释为当前位置(这是 PowerShell 对当前目录概念的概括,该目录也可以引用其他 PowerShell 驱动器提供程序(如注册表提供程序)公开的驱动器上其他类型的位置),因此可以使用PowerShell命令(如果可用)来避免此问题。

  • 调用 .NET 方法时,请确保事先将任何相对路径解析为绝对的文件系统本机路径[2],或者在支持的情况下,另外提供当前 PowerShell 文件系统位置作为引用目录 - 这样可以避免当前目录不匹配的问题。

  • (另一个次优选项是每次传递相对路径时首先设置[Environment]::CurrentDirectory = $PWD.ProviderPath,但这很笨拙,如果同一进程中可能存在多个 PowerShell 运行空间,则不应使用。

下一部分介绍如何安全地将相对 PowerShell 路径传递给 .NET 方法,而下一部分解决问题中的特定问题:如何将任意给定的 PowerShell 路径解析为绝对的本机文件系统路径。

注意事项

  • 为了解析相对路径,下面的解决方案假设PowerShell的当前位置(如Get-Location输出/反映在$PWD中)是一个文件系统位置,即一个目录- 这是典型的。

  • 如果无法做出这一假设(例如,在不太可能的情况下,当前位置是登记处位置),则需要明确提及FileSystem提供程序的位置,使用(Get-Location -PSProvider FileSystem).ProviderPath代替下面的$PWD.ProviderPath。值得注意的是,这排除了以下Convert-PathNew-Item方法。


将已知的相对文件系统路径安全地传递给 .NET 方法:

如前所述,当前目录中的差异要求将绝对路径传递给 .NET 方法,该路径基于PowerShell的当前目录到达。

这些示例假定someFile.txt将相对路径传递给 .NET 方法[IO.File]::ReadAllText()[IO.File]::WriteAllText()

请注意,使用简单的字符串内插,/(可以与互换使用)用于连接路径组件;如果当前目录恰好是目录,则最终将得到2个路径分隔符,但这不会影响功能。但是,如果仍需要避免这种情况,请改用Join-Pathcmdlet。

如果已知路径存在

使用Convert-Path

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

不幸的是,Convert-PathResolve-Path仅适用于现有路径(从PowerShell(核心)7.2开始);GitHub问题#2993中提出了为不存在的路径提供选择加入。

同样,如果Convert-PathResolve-Path支持-PSProvider参数以允许显式指定目标提供程序,这将很有帮助,因为Get-Location已经支持这样做 - 请参阅 GitHub 问题 #10494。

如果路径是要创建的路径:

最简单且可靠的方法是使用New-Item让 PowerShell预先创建项,这将返回一个文件系统信息对象,其.FullName属性反映完整的本机路径:

# For a *directory* path, use -Type Directory
[IO.File]::WriteAllText(
(New-Item -Type File ./someFile.txt).FullName,
"sample content"
)

如果不想事先在 PowerShell 中创建文件/目录,有几种方法:

  • 最简单,但不是完全可靠的,通过$PWD(如果当前目录基于使用New-PsDrive创建的特定于 PowerShell 的驱动器,或者当前位置不是文件系统位置,则失败):
[IO.File]::WriteAllText("$PWD/someFile.txt", "sample content")
  • 更健壮:通过$PWD.ProviderPath(将基于 PowerShell 驱动器的路径解析为底层本机文件系统路径,但如果当前位置不是文件系统位置,仍可能失败):
[IO.File]::WriteAllText("$($PWD.ProviderPath)/someFile.txt", "sample content")
  • 完全坚固:通孔(Get-Location -PSProvider FileSystem).ProviderPath
[IO.File]::WriteAllText(
"$((Get-Location -PSProvider FileSystem).ProviderPath)/someFile.txt"),
"sample content"
)

任何给定的文件系统路径解析为完整的本机路径:

也就是说,你可能必须将提供给你的路径解析为完整的文件系统本机路径,该路径可能是相对的,也可能不是相对的,并且可能基于也可能不基于特定于 PowerShell 的驱动器(.NET 对此一无所知)。

如果该路径存在,请使用Convert-Path任何 PowerShell 文件系统路径解析为绝对的文件系统本机路径:

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

相关的Resolve-Pathcmdlet 提供类似的功能,但它不会将基于PowerShell 特定驱动器(使用New-PsDrive创建)的路径解析为其基础本机文件系统路径。

如果路径尚不存在

注意:

  • 为简洁起见,下面使用$PWD.ProviderPath来引用当前文件系统位置的本机路径。如前所述,这假定 PowerShell 的当前位置确实是文件系统位置,这是典型的;要获得完全的稳健性,请改用(Get-Location -PSProvider FileSystem).ProviderPath

在基于 .NET Core/.NET 5+ 构建的PowerShell (Core) v6+ 中,可以使用接受指定相对路径的引用目录的新[IO.Path]::GetFullPath()重载:

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

Windows PowerShell,您还需要[IO.Path]::Combine()

注意:在最简单的情况下,即如果你的相对路径从不以(或/)[3]开头,并且你不关心规范结果路径(通过解析...组件和/或将/规范化为),那么仅[IO.Path]::Combine()足够了:

# !! Note the limitations.
$dir = "temp"
[IO.Path]::Combine($PWD.ProviderPath, $dir)

[IO.Path]::Combine()[IO.Path]::GetFullPath()相结合可以克服以下限制:

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

[1] 虽然给定进程通常只有一个 PowerShell 运行空间(会话),但多个运行空间在一个进程中共存的可能性意味着,从概念上讲,所有这些进程都不可能将其各自的工作目录与唯一的进程范围的 .NET 工作目录同步。有关更深入的说明,请参阅此 GitHub 问题。

[2] 也就是说,基于特定于 PowerShell的驱动器的路径(请参阅New-PSDrive)必须转换为基于所有进程已知的驱动器的路径。

[3] 正如 Theo 所指出的,[IO.Path]::Combine()认为一条(非 UNC)路径以(或/)开头,尽管它仅相对于当前驱动器扎根。因此,此类路径必须以 PowerShell 当前位置底层本机文件系统目录的驱动器规范为前缀,以形成真正完整的路径。

这更像是一个.net的东西:为什么PowerShell中的.NET对象不使用当前目录?

常见的解决方法:

[xml]$xml = cat file.xml
$xml.save("$pwdfile.xml")

Powershell将.视为当前位置。因此,如果您执行Get-ChildItem -Path ".",这将返回当前目录中的所有内容。要获取相对路径,您需要执行以下操作:

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

$path现在将是一个相对路径

最新更新