如何在PowerShell中标准化路径?


94

我有两条路:

fred\frog

..\frag

我可以像这样在PowerShell中将它们加入在一起:

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

这给了我这个:

fred\frog\..\frag

但是我不想要那个。我想要一个没有双点的标准化路径,如下所示:

fred\frag

我该怎么办?


1
青蛙是青蛙的子文件夹吗?如果没有,那么结合路径将使您有fred \ frog \ frag。如果是这样,那就是一个非常不同的问题。
EBGreen

Answers:


81

您可以使用的组合pwdJoin-Path[System.IO.Path]::GetFullPath得到一个完全合格的扩展的路径。

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

您要做的是首先确定自己的道路:

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

就我个人而言,它也不复杂,我不屑于依赖于外部脚本的解决方案,这是一个简单的问题,用Join-Pathand 相当恰当地解决了pwdGetFullPath只是为了使其美观)。如果只想保留相对部分,只需添加.Substring((pwd).Path.Trim('\').Length + 1),瞧!

fred\frag

更新

感谢@Dangph指出了C:\边缘情况。


如果pwd为“ C:\”,则最后一步不起作用。在这种情况下,我会收到“ red \ frag”。
dan-gph

@Dangph-不知道我的意思,上面的方法似乎工作正常吗?您正在使用哪个版本的PowerShell?我正在使用3.0版。
约翰·莱德格伦

1
我的意思是最后一步:cd c:\; "C:\fred\frag".Substring((pwd).Path.Length + 1)。没什么大不了的; 只是要注意的事情。
dan-gph 2013年

嗯,不错,我们可以通过添加修剪命令来解决此问题。尝试cd c:\; "C:\fred\frag".Substring((pwd).Path.Trim('\').Length + 1)。虽然有点长。
约翰·莱德格伦

2
或者,只需使用:$ ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath(“。\ nonexist \ foo.txt”)也可以用于不存在的路径。“ x0n”值得称赞。正如他指出的那样,它解析为PSPath,而不是flilesystem路径,但是,如果您使用PowerShell中的路径,谁在乎呢?stackoverflow.com/questions/3038337/...
乔编码器

105

您可以使用resolve-path将.. \ frag扩展到其完整路径:

PS > resolve-path ..\frag 

尝试使用Combine()方法规范化路径:

[io.path]::Combine("fred\frog",(resolve-path ..\frag).path)

如果您的路径C:\WindowsC:\Windows\ 相同的路径却有两个不同的结果该怎么办
Joe Phillips

2
的参数[io.path]::Combine相反。更好的是,使用本机Join-PathPowerShell命令:Join-Path (Resolve-Path ..\frag).Path 'fred\frog'另外请注意,至少从PowerShell v3开始,Resolve-Path现在支持-Relative用于解析到相对于当前文件夹的路径的开关。如前所述,Resolve-Path与相比,仅适用于现有路径[IO.Path]::GetFullPath()
mklement0 2013年

25

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

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

或更有趣的是

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

两者都会产生以下内容(假设您的当前目录为D:\):

D:\fred\frag

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


那已经很近了,但是当我尝试时,即使我的当前目录是“ C:\ scratch”,我也会得到“ H:\ fred \ frag”,这是错误的。(根据MSDN,它不应该这样做。)但是,它给了我一个主意。我将其添加为答案。
dan-gph

8
您的问题是您需要在.NET中设置当前目录。 [System.IO.Directory]::SetCurrentDirectory(((Get-Location -PSProvider FileSystem).ProviderPath))
2011年

2
只是明确声明一下:[IO.Path]::GetFullPath()与PowerShell的本机不同Resolve-Path,它也适用于不存在的路径。@JasonMArcher指出,这样做的缺点是需要首先将.NET的工作文件夹与PS同步。
mklement0 2013年

Join-Path如果引用的驱动器不存在,则会导致异常。
塔希尔·哈桑

20

公认的答案是一个很大的帮助,但是它也不能正确地“标准化”绝对路径。在我的派生工作下面查找可归一化绝对路径和相对路径的导数。

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;
}

给定所有不同的解决方案,该解决方案适用于所有不同类型的路径。例如[IO.Path]::GetFullPath(),无法正确确定纯文件名的目录。
加里·特尔基亚

10

任何非PowerShell路径操作功能(例如System.IO.Path中的功能)都无法从PowerShell可靠,因为PowerShell的提供程序模型允许PowerShell的当前路径与Windows认为该进程的工作目录不同。

另外,您可能已经发现,PowerShell的Resolve-Path和Convert-Path cmdlet可用于将相对路径(包含'..'的相对路径)转换为驱动器限定的绝对路径,但是如果引用的路径不存在,它们将失败。

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

function Get-AbsolutePath {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [string[]]
        $Path
    )

    process {
        $Path | ForEach-Object {
            $PSCmdlet.SessionState.Path.GetUnresolvedProviderPathFromPSPath($_)
        }
    }
}

2
这对于不存在驱动器号的不存在的路径不起作用,例如我没有Q:驱动器。 Get-AbsolutePath q:\foo\bar\..\baz即使是有效路径也失败。好吧,这取决于您对有效路径的定义。:-) FWIW,即使内置Test-Path <path> -IsValid驱动器在以不存在的驱动器为根的路径上也失败。
基思·希尔

2
@KeithHill换句话说,PowerShell认为不存在的根目录上的路径无效。我认为这是相当合理的,因为PowerShell使用根来决定使用它时使用哪种提供程序。例如,HKLM:\SOFTWARE是PowerShell中的有效路径,是指SOFTWARE本地计算机注册表配置单元中的键。但是要弄清楚它是否有效,就需要弄清楚注册表路径的规则是什么。
jpmc26

3

这个库很好: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

请注意,此代码段需要DLL的路径。您可以使用一种技巧来找到包含当前正在执行的脚本的文件夹,但是在我的情况下,我有一个可以使用的环境变量,因此我只是使用了它。


我不知道为什么那票选不好。该库确实非常适合进行路径操作。这就是我最终在项目中使用的。
丹·格夫2009年

减号2。仍然感到困惑。我希望人们意识到使用PowerShell中的.Net程序集很容易。
丹GPH

这似乎不是最好的解决方案,但这是完全有效的。
2011年

@Jason,我不记得详细信息,但是当时这是最好的解决方案,因为它是解决我特定问题的唯一解决方案。但是,从那时起,有可能出现另一个更好的解决方案。
dan-gph

2
拥有第三方DLL是此解决方案的一大缺陷
Louis Kottmann

1

这给出了完整的路径:

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

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

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

由于某些原因,它们仅在frag是文件而不是时才起作用directory


1
gci是get-childitem的别名。目录的子级是其内容。将gci替换为gi,并且对这两者均适用。
zdan

2
Get-Item运作良好。但是同样,此方法要求文件夹存在。
Peter Lillevold

1

创建一个函数。此功能将规范化系统上不存在的路径,并且不添加驱动器号。

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'

感谢Oliver Schadlich在RegEx中提供的帮助。


请注意,这对于像somepaththing\.\filename.txt保留单个点这样的路径不起作用
Mark Schultheiss

1

如果路径包含限定符(驱动器号),则x0n对Powershell的回答:解析可能不存在的路径?将规范化路径。如果该路径不包含限定符,则仍将对其进行规范化,但将返回相对于当前目录的完全限定路径,这可能不是您想要的。

$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

0

如果需要删除..部分,则可以使用System.IO.DirectoryInfo对象。在构造函数中使用“ fred \ frog .. \ frag”。FullName属性将为您提供标准化的目录名称。

唯一的缺点是它将为您提供整个路径(例如c:\ test \ fred \ frag)。


0

这里的注释的权宜之计结合起来,使它们统一了相对路径和绝对路径:

[System.IO.Directory]::SetCurrentDirectory($pwd)
[IO.Path]::GetFullPath($dapath)

一些样本:

$fps = '.', 'file.txt', '.\file.txt', '..\file.txt', 'c:\somewhere\file.txt'
$fps | % { [IO.Path]::GetFullPath($_) }

输出:

C:\Users\thelonius\tests
C:\Users\thelonius\tests\file.txt
C:\Users\thelonius\tests\file.txt
C:\Users\thelonius\file.txt
c:\somewhere\file.txt

-1

好吧,一种方法是:

Join-Path 'fred\frog' '..\frag'.Replace('..', '')

等等,也许我误会了这个问题。在您的示例中,frag是青蛙的子文件夹吗?


“碎片是青蛙的子文件夹吗?” 不。..表示上升一个级别。frag是fred中的子文件夹(或文件)。
dan-gph
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.