打开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-Path
和New-Item
方法。
将已知的相对文件系统路径安全地传递给 .NET 方法:
如前所述,当前目录中的差异要求将绝对路径传递给 .NET 方法,该路径基于PowerShell的当前目录到达。
这些示例假定someFile.txt
将相对路径传递给 .NET 方法[IO.File]::ReadAllText()
和[IO.File]::WriteAllText()
请注意,使用简单的字符串内插,/
(可以与互换使用)用于连接路径组件;如果当前目录恰好是根目录,则最终将得到2个路径分隔符,但这不会影响功能。但是,如果仍需要避免这种情况,请改用
Join-Path
cmdlet。
如果已知路径存在:
使用Convert-Path
:
[IO.File]::ReadAllText((Convert-Path -LiteralPath someFile.txt))
不幸的是,Convert-Path
和Resolve-Path
仅适用于现有路径(从PowerShell(核心)7.2开始);GitHub问题#2993中提出了为不存在的路径提供选择加入。
同样,如果Convert-Path
和Resolve-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-Path
cmdlet 提供类似的功能,但它不会将基于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
现在将是一个相对路径