如何在PowerShell中规范化和比较路径。

3

我需要测试两个包含路径的字符串,以确定它们是否指向相同的目录。

简单地使用字符串比较会导致比较失败,例如比较 C:\WindowsC:\Windows\

可以按照这个 StackOverflow 问题中提到的方法使用 Join-Path 来解决此问题,但是仍然有其他问题:
例如,\\server\share 有时可以表示为 UNC\server\share\\<ip>\share

有没有一个适当的方式来检查它,而不必使用变通方法?

编辑: 我已经在我的 PowerShell 模块 PSHelperTools 中实现了找到的代码,可以使用 Install-Module PSHelperTools 下载。


UNC\server\share 不是 UNC 路径,它只是一个相对本地路径。你是不是想说 \\.\UNC\server\share - Mathias R. Jessen
@JeffZeitlin 这对我的情况不起作用,因为正如Mathias所提到的那样,它被识别为相对本地路径。 - Kevin Holtkamp
@MathiasR.Jessen 你是不是指的是 \\?\UNC\server\share - Theo
所以,显然没有一个可检查这个的 cmdlet,特别是检查非有效 UNC 路径是否实际上是 UNC。你尝试了什么?分享你的代码。 - Santiago Squarzon
@SantiagoSquarzon 我使用 ((Get-Item $path | Select-Object -ExpandProperty Target) -replace "^UNC\\","\\") 手动删除了 UNC,并且手动没有在路径末尾添加反斜杠。我知道这是可能的,这不是我的问题。我的问题是是否有一种正确的方法可以使用官方 cmdlet 来实现。 - Kevin Holtkamp
显示剩余3条评论
2个回答

0

目前我正在使用这个方法作为解决办法:

function Format-Path(){
    [Cmdletbinding()]
    param($Path)

    Write-Verbose "Format-Path: $Path"
    if($Path.StartsWith(".")){
        #Get Absolute path if path starts with "."
        $Path = Resolve-Path -Path $Path
        Write-Verbose "Resolved Path: $Path"
    }
    if($Path -match "^.*::(.*)"){
        $Path = $Path -replace "^.*::(.*)", '$1'
        Write-Verbose "Replaced Powershell providers: $Path"
    }
    $Path = $Path -replace "/"                      , "\" `
                  -replace "^\\\\\.\\"              , "" `
                  -replace "^\\\\\?\\"              , "" `
                  -replace "^UNC\\"                 , "\\"
    Write-Verbose "Replaced UNC conventions: $Path"

    if($Path -match "^\\\\([A-Za-z]+)(@SSL)?"){
        $Path = $Path -replace "^\\\\([A-Za-z]+)(@SSL)?", "\\$((Resolve-DnsName $matches[1] -Type "A").IPAddress)"
        Write-Verbose "Resolve name into IP: $Path"
    }

    return $Path.TrimEnd("\")
}

0

有一种正确的方法可以做到这一点,就像您在这个混乱中看到的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)
# we split the drive away, it makes UnresolvedPath fail on non-existing drives.
$norm_path  = Split-Path $path -NoQualifier
# strip out UNC
$path_direct = $norm_path.StartsWith('//') -or $norm_path.StartsWith('\\')
if ($path_direct) {
    $norm_path = $norm_path.Substring(2)
}
# then normalize
$norm_path = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($norm_path)
# then we cut out the current location if same drive
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) {
    # or we cut out the current drive
    if ($pwd.Drive) {
        $norm_path = $norm_path.Substring($pwd.Drive.Root.Length)
    } else {
        # or we cut out the UNC special case
        $norm_path = $norm_path.Substring($pwd.ProviderPath.Length + 1)
    }
}
# then add back the UNC if any
if ($path_direct) {
    $norm_path = $pwd.Provider.ItemSeparator + $pwd.Provider.ItemSeparator + $norm_path
}
# then add back the provider if any
if ($path_prov) {
    $norm_path = $ExecutionContext.SessionState.Path.Combine($path_drive.Value + '::/', $norm_path)
}
# or add back the drive if any
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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接