提问人:ahelwer 提问时间:9/4/2019 最后编辑:mklement0ahelwer 更新时间:11/8/2023 访问量:2223
PowerShell 如何处理路径中的“.”?
How does PowerShell treat "." in paths?
问:
打开 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 的目录而不是当前目录解释“.”?我怎样才能让它解释相对于当前目录的“.”?
答:
这更像是 .net 的事情:为什么 PowerShell 中的 .NET 对象不使用当前目录?
常见的解决方法:
[xml]$xml = cat file.xml
$xml.save("$pwd\file.xml")
Powershell 将 视为当前位置。因此,如果这样做,这将返回当前目录中的所有内容。若要获取相对路径,需要执行如下操作:.
Get-ChildItem -Path ".\"
Set-Location -Path "Path"
$path = Get-Item -Path "file" | Resolve-Path -Relative
$path
现在将是一个相对路径
正如 js2010 的有用答案所述,正是使用 .NET 方法引入了问题:
.NET的单个进程范围的当前目录通常与PowerShell的特定于运行空间的目录不同[1]。
这具有以下含义:
由于 PowerShell 本身确实可靠地将
.
解释为当前位置(这是 PowerShell 对当前目录概念的概括,该目录也可以引用其他类型的位置,在其他 PowerShell 驱动器提供程序(如注册表提供程序)公开的驱动器上),因此可以使用 PowerShell 命令(如果可用)来避免此问题。调用 .NET 方法时,请务必事先将任何相对路径解析为绝对的文件系统本机 [2] 路径,或者在支持的情况下,另外提供当前 PowerShell 文件系统位置作为参考目录 - 这样可以避免当前目录不匹配的问题。
(另一个次优选项是每次传递相对路径时首先设置,但这很笨拙,如果同一进程中可能存在多个 PowerShell 运行空间,则不应使用。
[Environment]::CurrentDirectory = $PWD.ProviderPath
下一部分介绍如何安全地将相对 PowerShell 路径传递给 .NET 方法,而底部部分则解决了问题中的特定问题:如何将任意给定的 PowerShell 路径解析为绝对的本机文件系统路径。
注意事项:
为了解析相对路径,下面的解决方案假定 PowerShell 的当前位置(由 / 反映在 中输出)是文件系统位置,即目录 - 这是典型的。
Get-Location
$PWD
如果无法做出此假设(例如,在不太可能的情况下,当前位置是注册表位置),则需要显式引用提供程序的位置,而不是下面。值得注意的是,这排除了以下 AND 方法。
FileSystem
(Get-Location -PSProvider FileSystem).ProviderPath
$PWD.ProviderPath
Convert-Path
New-Item
将已知的相对文件系统路径安全地传递给 .NET 方法:
如前所述,当前目录中的差异要求将绝对路径传递给 .NET 方法,该路径基于 PowerShell 的当前目录得出。
这些示例假定要传递给 .NET 方法的相对路径和someFile.txt
[IO.File]::ReadAllText()
[IO.File]::WriteAllText()
请注意,使用简单的字符串插值,(可以与 互换使用)用于连接路径组件;如果当前目录恰好是根目录,则最终会得到 2 个路径分隔符,但这不会影响功能。但是,如果仍需要避免这种情况,请改用 cmdlet。/
\
Join-Path
如果已知路径存在:
使用 convert-path
:
[IO.File]::ReadAllText((Convert-Path -LiteralPath someFile.txt))
不幸的是,Convert-Path
和 Resolve-Path
仅适用于现有路径(从 PowerShell (Core) 7.2 开始);GitHub issue #2993 中提出了为不存在的路径提供选择加入。
同样,如果并支持一个参数以允许显式指定目标提供程序,这将很有帮助,正如已经支持的那样 - 请参阅 GitHub 问题 #10494。Convert-Path
Resolve-Path
-PSProvider
Get-Location
如果路径是要创建的路径:
最简单、最可靠的方法是使用 New-Item
让 PowerShell 预先创建项,这将返回一个文件系统信息对象,其属性反映完整的本机路径:.FullName
# For a *directory* path, use -Type Directory
[IO.File]::WriteAllText(
(New-Item -Type File ./someFile.txt).FullName,
"sample content"
)
如果不想事先在 PowerShell 中创建文件/目录,有几种方法:
- 最简单,但不是完全可靠,via(如果当前目录基于使用 PowerShell 创建的特定于 PowerShell 的驱动器,或者当前位置不是文件系统位置,则失败):
$PWD
New-PsDrive
[IO.File]::WriteAllText("$PWD/someFile.txt", "sample content")
- 更可靠:via(将基于 PowerShell 驱动器的路径解析为基础的本机文件系统路径,但如果当前位置不是文件系统位置,则仍可能失败):
$PWD.ProviderPath
[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
相关 cmdlet 提供类似的功能,但它不会将基于 PowerShell 特定驱动器的路径(使用 创建)解析为其基础本机文件系统路径。Resolve-Path
New-PsDrive
如果路径尚不存在:
注意:
- 为简洁起见,下面用于指代当前文件系统位置的本机路径。如前所述,这假定 PowerShell 的当前位置确实是文件系统位置,这是典型的;为了获得完全的鲁棒性,请改用。
$PWD.ProviderPath
(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.路径]::Combine()
:
注意:在最简单的情况下,即如果你的相对路径从不以 (or )[3] 开头,并且你不关心对结果路径进行归一化(通过解析 or 分量和/或归一化为 ),单独就足够了:\
/
.\
..\
/
\
[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 当前位置的本机文件系统目录的驱动器规范为前缀,以形成真正的完整路径。
评论
Get-Location
$PSScriptRoot
$MyInvocation.MyCommand.Path
Resolve-Path $dir