代码之家  ›  专栏  ›  技术社区  ›  dan-gph

如何在PowerShell中规范化路径?

  •  120
  • dan-gph  · 技术社区  · 17 年前

    我有两条路:

    fred\frog
    

    ..\frag
    

    我可以在PowerShell中将它们连接在一起,如下所示:

    join-path 'fred\frog' '..\frag'
    

    这给了我这个:

    fred\frog\..\frag
    

    但我不想那样。我想要一个没有双点的标准化路径,就像这样:

    fred\frag
    

    我怎样才能得到那个?

    14 回复  |  直到 17 年前
        1
  •  109
  •   abatishchev Karl Johan    13 年前

    你可以扩展。.\frag返回其完整路径,解析路径为:

    PS > resolve-path ..\frag 
    

    [io.path]::Combine("fred\frog",(resolve-path ..\frag).path)
    
        2
  •  89
  •   ComFreek    5 年前

    $pwd , Join-Path [System.IO.Path]::GetFullPath 以获得完全合格的扩展路径。

    cd Set-Location )不会更改进程当前的工作目录,只需将相对文件名传递给。NET API不理解PowerShell上下文,可能会产生意外的副作用,例如解析到基于初始工作目录(而不是当前位置)的路径。

    你要做的是首先确定你的道路:

    Join-Path (Join-Path $pwd fred\frog) '..\frag'
    

    C:\WINDOWS\system32\fred\frog\..\frag
    

    有了绝对基数,现在可以安全地调用。NET API GetFullPath :

    [System.IO.Path]::GetFullPath((Join-Path (Join-Path $pwd fred\frog) '..\frag'))
    

    这为您提供了完全合格的路径 ..

    C:\WINDOWS\system32\fred\frag
    

    就我个人而言,这也不复杂,我鄙视依赖外部脚本的解决方案,这是一个简单的问题,通过 加入路径 ( GetFullPath 只是为了让它变得漂亮)。如果你只想保持 只有相对的部分 .Substring($pwd.Path.Trim('\').Length + 1)

    fred\frag
    

    更新

    C:\ 边缘案例。

        3
  •  26
  •   Charlie    17 年前

    Path.GetFullPath ,尽管(与Dan R的答案一样)这将为您提供整个路径。用法如下:

    [IO.Path]::GetFullPath( "fred\frog\..\frag" )
    

    或者更有趣的是

    [IO.Path]::GetFullPath( (join-path "fred\frog" "..\frag") )
    

    这两者都会产生以下结果(假设您当前的目录是D:\):

    D:\fred\frag
    

    请注意,此方法并不试图确定fred或frag是否确实存在。

        4
  •  24
  •   Sean Hanna    13 年前

    function Get-AbsolutePath ($Path)
    {
        # System.IO.Path.Combine has two properties making it necesarry here:
        #   1) correctly deals with situations where $Path (the second term) is an absolute path
        #   2) correctly deals with situations where $Path (the second term) is relative
        # (join-path) commandlet does not have this first property
        $Path = [System.IO.Path]::Combine( ((pwd).Path), ($Path) );
    
        # this piece strips out any relative path modifiers like '..' and '.'
        $Path = [System.IO.Path]::GetFullPath($Path);
    
        return $Path;
    }
    
        5
  •  13
  •   Jason Stangroome    15 年前

    此外,您可能已经发现,PowerShell的Resolve Path和Convert Path cmdlet对于将相对路径(包含“..”的路径)转换为驱动器限定的绝对路径非常有用,但如果引用的路径不存在,则会失败。

    以下非常简单的cmdlet应该适用于不存在的路径。它将转换“fred\frog”。.\frag'到'd:\fred\frag',即使找不到'fred'或'frag'文件或文件夹(当前PowerShell驱动器为'd:')。

    function Get-AbsolutePath {
        [CmdletBinding()]
        param (
            [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
            [string[]]
            $Path
        )
    
        process {
            $Path | ForEach-Object {
                $PSCmdlet.SessionState.Path.GetUnresolvedProviderPathFromPSPath($_)
            }
        }
    }
    
        6
  •  4
  •   WileCau    7 年前

    x0n's answer to Powershell: resolve path that might not exist? 将使路径正常化。如果路径不包含限定符,它仍将被规范化,但将返回相对于当前目录的完全限定路径,这可能不是您想要的。

    $p = 'X:\fred\frog\..\frag'
    $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($p)
    X:\fred\frag
    
    $p = '\fred\frog\..\frag'
    $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($p)
    C:\fred\frag
    
    $p = 'fred\frog\..\frag'
    $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($p)
    C:\Users\WileCau\fred\frag
    
        7
  •  3
  •   dan-gph    13 年前

    这个图书馆不错: NDepend.Helpers.FileDirectoryPath .

    编辑: 这是我想到的:

    [Reflection.Assembly]::LoadFrom("path\to\NDepend.Helpers.FileDirectoryPath.dll") | out-null
    
    Function NormalizePath ($path)
    {
        if (-not $path.StartsWith('.\'))  # FilePathRelative requires relative paths to begin with '.'
        {
            $path = ".\$path"
        }
    
        if ($path -eq '.\.')  # FilePathRelative can't deal with this case
        {
            $result = '.'
        }
        else
        {
            $relPath = New-Object NDepend.Helpers.FileDirectoryPath.FilePathRelative($path)
            $result = $relPath.Path
        }
    
        if ($result.StartsWith('.\')) # remove '.\'. 
        {
            $result = $result.SubString(2)
        }
    
        $result
    }
    

    这样称呼它:

    > NormalizePath "fred\frog\..\frag"
    fred\frag
    

        8
  •  1
  •   dan-gph    17 年前

    如果路径存在,并且您不介意返回绝对路径,则可以使用 Join-Path -Resolve 参数:

    Join-Path 'fred\frog' '..\frag' -Resolve
    
        9
  •  1
  •   M.Hubers    14 年前

    (gci 'fred\frog\..\frag').FullName
    

    这给出了相对于当前目录的路径:

    (gci 'fred\frog\..\frag').FullName.Replace((gl).Path + '\', '')
    

    出于某种原因,它们只在以下情况下有效 frag 是文件,不是 directory .

        10
  •  0
  •   Dan R    17 年前

    function RemoveDotsInPath {
      [cmdletbinding()]
      Param( [Parameter(Position=0,  Mandatory=$true)] [string] $PathString = '' )
    
      $newPath = $PathString -creplace '(?<grp>[^\n\\]+\\)+(?<-grp>\.\.\\)+(?(grp)(?!))', ''
      return $newPath
    }
    

    前任:

    $a = 'fooA\obj\BusinessLayer\..\..\bin\BusinessLayer\foo.txt'
    RemoveDotsInPath $a
    'fooA\bin\BusinessLayer\foo.txt'
    

        11
  •  0
  •   TNT    8 年前

    由于以下原因,这些答案都不完全可接受。

    • 它必须支持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
    

    从PSPath获取未解析的提供者路径 不幸地 GetUPFP 取决于电流 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
    

    我还做了其他几次尝试,因为当你是一名科学家时,你就是这么做的。
    从PSPath获取未解析的提供者路径 如果你不相信,不,由于递归,你不能用正则表达式做到这一点。

    https://gist.github.com/Luiz-Monad/d5aea290087a89c070da6eec84b33742#file-normalize-path-ps-md

        12
  •  -1
  •   EBGreen    17 年前

    如果你需要摆脱它。.part,您可以使用系统。IO.DirectoryInfo对象。使用“fred\frog”。构造函数中的.\frag'。FullName属性将为您提供规范化的目录名。

    唯一的缺点是它会给你整个路径(例如c:\test\fred\frag)。

    推荐文章