有一种正确的方法可以做到这一点,就像您在这个混乱中看到的https://dev59.com/7nRB5IYBdhLWcg3w1Kn0#74201349
以下是其中任何答案都不完全可接受的原因:
- 它必须支持PowerShell提供程序。
- 它必须适用于不存在于不存在驱动器中的路径。
- 它必须处理“..”和“。”,这就是规范化路径的含义。
- 没有外部库,也没有正则表达式。
- 它不能重新根路径,这意味着相对路径保持相对。
出于以下原因,我列出了每种方法的预期结果列表,如下所示:
function tests {
context "cwd" {
it 'has no external libraries' {
Load-NormalizedPath
}
it 'barely work for FileInfos on existing paths' {
Get-NormalizedPath 'a\..\c' | should -be 'c'
}
it 'process .. and . (relative paths)' {
Get-NormalizedPath 'a\b\..\..\c\.' | should -be 'c'
}
it 'must support powershell providers' {
Get-NormalizedPath "FileSystem::\\$env:COMPUTERNAME\Shared\a\..\c" | should -be "FileSystem::\\$env:COMPUTERNAME\Shared\c"
}
it 'must support powershell drives' {
Get-NormalizedPath 'HKLM:\Software\Classes\.exe\..\.dll' | should -be 'HKLM:\Software\Classes\.dll'
}
it 'works with non-existant paths' {
Get-NormalizedPath 'fred\frog\..\frag\.' | should -be 'fred\frag'
}
it 'works with non-existant drives' {
Get-NormalizedPath 'U:\fred\frog\..\frag\.' | should -be 'U:\fred\frag'
}
it 'barely work for direct UNCs' {
Get-NormalizedPath "\\$env:COMPUTERNAME\Shared\a\..\c" | should -be "\\$env:COMPUTERNAME\Shared\c"
}
}
context "reroot" {
it 'doesn''t reroot subdir' {
Get-NormalizedPath 'fred\frog\..\frag\.' | should -be 'fred\frag'
}
it 'doesn''t reroot local' {
Get-NormalizedPath '.\fred\frog\..\frag\.' | should -be 'fred\frag'
}
it 'doesn''t reroot parent' {
Get-NormalizedPath "..\$((Get-Item .).Name)\fred\frog\..\frag\." | should -be 'fred\frag'
}
}
context "drive root" {
beforeEach { Push-Location 'c:/' }
it 'works on drive root' {
Get-NormalizedPath 'fred\frog\..\..\fred\frag\' | should -be 'fred\frag\'
}
afterEach { Pop-Location }
}
context "temp drive" {
beforeEach { New-PSDrive -Name temp -PSProvider FileSystem 'b:/tools' }
it 'works on temp drive' {
Get-NormalizedPath 'fred\frog\..\..\fred\frag\' | should -be 'fred\frag\'
}
it 'works on temp drive with absolute path' {
Get-NormalizedPath 'temp:\fred\frog\..\..\fred\frag\' | should -be 'temp:\fred\frag\'
}
afterEach { Remove-PSDrive -Name temp }
}
context "unc drive" {
beforeEach { Push-Location "FileSystem::\\$env:COMPUTERNAME\Shared\" }
it 'works on unc drive' {
Get-NormalizedPath 'fred\frog\..\..\fred\frag\' | should -be 'fred\frag\'
}
afterEach { Pop-Location }
}
}
正确的答案使用 GetUnresolvedProviderPathFromPSPath
,但是它不能单独工作,如果您尝试直接使用它,您将得到这些结果。
来自这个答案 https://dev59.com/7nRB5IYBdhLWcg3w1Kn0#52157943。
$path = Join-Path '/' $path
$path = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($path)
$path = $path.Replace($pwd.Path, '').Replace($pwd.Drive.Root, '')
pros: simple
cons: needs boilerplate to make it correct, doesn't work with other providers or non-ex drives.
Context cwd
[+] has no external libraries 4ms (1ms|3ms)
[+] barely work for FileInfos on existing paths 3ms (2ms|0ms)
[+] process .. and . (relative paths) 3ms (2ms|0ms)
[-] must support powershell providers 4ms (3ms|1ms)
Expected: 'FileSystem::\\LUIZMONAD\Shared\c'
But was: '\\LUIZMONAD\Shared\a\..\c'
^
[-] must support powershell drives 14ms (4ms|10ms)
Expected: 'HKLM:\Software\Classes\.dll'
But was: 'Cannot find drive. A drive with the name '\HKLM' does not exist.'
^
[+] works with non-existant paths 3ms (2ms|1ms)
[-] works with non-existant drives 4ms (3ms|1ms)
Expected: 'U:\fred\frag'
But was: 'Cannot find drive. A drive with the name '\U' does not exist.'
^
[-] barely work for direct UNCs 3ms (3ms|1ms)
Expected: '\\LUIZMONAD\Shared\c'
But was: '\\LUIZMONAD\Shared\a\..\c'
-------------------^
Context reroot
[+] doesn't reroot subdir 3ms (2ms|1ms)
[+] doesn't reroot local 33ms (33ms|1ms)
[-] doesn't reroot parent 4ms (3ms|1ms)
Expected: 'fred\frag'
But was: '\fred\frag'
^
Context drive root
[+] works on drive root 5ms (3ms|2ms)
Context temp drive
[+] works on temp drive 4ms (3ms|1ms)
[-] works on temp drive with absolute path 6ms (5ms|1ms)
Expected: 'temp:\fred\frag\'
But was: 'Cannot find drive. A drive with the name '\temp' does not exist.'
^
Context unc drive
[+] works on unc drive 6ms (5ms|1ms)
Tests completed in 207ms
Tests Passed: 9, Failed: 6, Skipped: 0 NotRun: 0
所以,我们需要做的是剥离驱动程序/提供程序/UNC,然后使用
GetUnresolvedProviderPathFromPSPath
,最后再把驱动程序/提供程序/UNC放回去。
不幸的是,
GetUPPFP依赖于当前的
pwd
状态,但至少我们没有改变它。
$path_drive = [ref] $null
$path_abs = $ExecutionContext.SessionState.Path.IsPSAbsolute($path, $path_drive)
$path_prov = $ExecutionContext.SessionState.Path.IsProviderQualified($path)
$norm_path = Split-Path $path -NoQualifier
$path_direct = $norm_path.StartsWith('//') -or $norm_path.StartsWith('\\')
if ($path_direct) {
$norm_path = $norm_path.Substring(2)
}
$norm_path = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($norm_path)
if (($path_drive.Value -eq $pwd.Drive.Name) -or $path_direct) {
$norm_path = $norm_path.Substring($pwd.Path.Trim('/', '\').Length + 1)
} elseif (-not $path_prov) {
if ($pwd.Drive) {
$norm_path = $norm_path.Substring($pwd.Drive.Root.Length)
} else {
$norm_path = $norm_path.Substring($pwd.ProviderPath.Length + 1)
}
}
if ($path_direct) {
$norm_path = $pwd.Provider.ItemSeparator + $pwd.Provider.ItemSeparator + $norm_path
}
if ($path_prov) {
$norm_path = $ExecutionContext.SessionState.Path.Combine($path_drive.Value + '::/', $norm_path)
}
elseif ($path_abs) {
$norm_path = $ExecutionContext.SessionState.Path.Combine($path_drive.Value + ':', $norm_path)
}
$norm_path
pros: doesn't use the dotnet path function, uses proper powershell infrastructure.
cons: kind of complex, depends on `pwd`
Context cwd
[+] has no external libraries 8ms (2ms|6ms)
[+] barely work for FileInfos on existing paths 4ms (3ms|1ms)
[+] process .. and . (relative paths) 3ms (2ms|1ms)
[+] must support powershell providers 13ms (13ms|0ms)
[+] must support powershell drives 3ms (2ms|1ms)
[+] works with non-existant paths 3ms (2ms|0ms)
[+] works with non-existant drives 3ms (2ms|1ms)
[+] barely work for direct UNCs 3ms (2ms|1ms)
Context reroot
[+] doesn't reroot subdir 3ms (2ms|1ms)
[+] doesn't reroot local 3ms (2ms|1ms)
[+] doesn't reroot parent 15ms (14ms|1ms)
Context drive root
[+] works on drive root 4ms (3ms|1ms)
Context temp drive
[+] works on temp drive 4ms (3ms|1ms)
[+] works on temp drive with absolute path 3ms (3ms|1ms)
Context unc drive
[+] works on unc drive 9ms (8ms|1ms)
Tests completed in 171ms
Tests Passed: 15, Failed: 0, Skipped: 0 NotRun: 0
源代码与测试:
https://gist.github.com/Luiz-Monad/d5aea290087a89c070da6eec84b33742#file-normalize-path-ps-md
UNC\server\share
不是 UNC 路径,它只是一个相对本地路径。你是不是想说\\.\UNC\server\share
? - Mathias R. Jessen\\?\UNC\server\share
? - Theo((Get-Item $path | Select-Object -ExpandProperty Target) -replace "^UNC\\","\\")
手动删除了 UNC,并且手动没有在路径末尾添加反斜杠。我知道这是可能的,这不是我的问题。我的问题是是否有一种正确的方法可以使用官方 cmdlet 来实现。 - Kevin Holtkamp