如何对PowerShell中的路径进行规范化和比较

jhkqcmku  于 2022-11-10  发布在  Shell
关注(0)|答案(2)|浏览(124)

我需要测试包含路径的两个字符串,看它们是否指向相同的目录。
例如,在比较C:\WindowsC:\Windows\时,简单地使用字符串比较失败。
这个问题可以通过按照this StackOverflow问题使用Join-Path来解决,但它仍然省略了其他事情:
例如,\\server\share有时可以表示为UNC\server\share\\<ip>\share
有没有一种适当的方法可以在不使用变通方法的情况下检查这一点?
编辑:我已经在PowerShell模块PSHelperTools中实现了答案中的代码,可以使用Install-Module PSHelperTools下载

azpvetkf

azpvetkf1#

目前,我将此作为一种解决办法:

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("\")
}
jecbmhm3

jecbmhm32#

有一种正确的方法可以做到这一点,就像你在这个混乱的https://stackoverflow.com/a/74201349/1964796中看到的那样
由于以下原因,没有一个答案是完全可以接受的。

  • 它必须支持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://stackoverflow.com/a/52157943/1964796

$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

相关问题