我有两个路径:
fred\frog
和
..\frag
我可以在PowerShell中像这样将它们连接起来:
join-path 'fred\frog' '..\frag'
那给我这个:
fred\frog\..\frag
但我不想那样。我想要一个没有双点的规范化路径,就像这样:
fred\frag
我该如何获取那个?
我有两个路径:
fred\frog
和
..\frag
我可以在PowerShell中像这样将它们连接起来:
join-path 'fred\frog' '..\frag'
那给我这个:
fred\frog\..\frag
但我不想那样。我想要一个没有双点的规范化路径,就像这样:
fred\frag
我该如何获取那个?
你可以使用resolve-path将..\frag扩展为其完整路径:
PS > resolve-path ..\frag
尝试使用combine()方法规范化路径:
[io.path]::Combine("fred\frog",(resolve-path ..\frag).path)
C:\Windows
跟 C:\Windows\
,它们虽然相同但却会产生两个不同的结果。 - Joe Phillips[io.path]::Combine
的参数顺序是反的。更好的做法是使用本机的 Join-Path
PowerShell 命令:Join-Path (Resolve-Path ..\frag).Path 'fred\frog'
。另外需要注意的是,至少在 PowerShell v3 中,Resolve-Path
现在支持 -Relative
开关来解析相对于当前文件夹的路径。正如提到的那样,与 [IO.Path]::GetFullPath()
不同,Resolve-Path
仅适用于已存在的路径。 - mklement0-Relative
开关似乎是在 PSv2 中添加的。 - Prid您可以使用组合的 $pwd
, Join-Path
以及 [System.IO.Path]::GetFullPath
命令获取完全限定的扩展路径。
因为 cd
(Set-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-Path
和 $pwd
相当巧妙地解决了这个简单的问题(GetFullPath
只是为了让它看起来漂亮)。如果你只想保留相对部分,只需添加.Substring($pwd.Path.Trim('\').Length + 1)
,就大功告成了!
fred\frag
感谢 @Dangph 指出了 C:\
边缘情况。
cd c:\; "C:\fred\frag".Substring((pwd).Path.Length + 1)
。这不是什么大问题,只是需要注意一下。 - dan-gphcd c:\; "C:\fred\frag".Substring((pwd).Path.Trim('\').Length + 1)
。不过这个命令有点长了。 - John Leidegren你也可以使用 Path.GetFullPath,不过这个方法(与 Dan R 的答案类似)会返回整个路径。用法如下:
[IO.Path]::GetFullPath( "fred\frog\..\frag" )
更有趣的是[IO.Path]::GetFullPath( (join-path "fred\frog" "..\frag") )
假设您的当前目录为D:\,则两者都会产生以下结果:
D:\fred\frag
请注意,此方法不尝试确定fred或frag是否实际存在。
[System.IO.Directory]::SetCurrentDirectory(((Get-Location -PSProvider FileSystem).ProviderPath))
可以实现这一功能。 - JasonMArcher[IO.Path]::GetFullPath()
与PowerShell本地的Resolve-Path
不同,它也可以处理不存在的路径。但缺点是需要先将.NET的工作文件夹与PS同步,正如@JasonMArcher所指出的那样。 - mklement0Join-Path
会引发异常。 - Tahir Hassan接受的答案非常有帮助,但它没有正确“规范化”绝对路径。请看我的派生作品,可以同时规范化绝对路径和相对路径。
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()
无法正确确定纯文件名的目录。 - Jari Turkia由于PowerShell的提供者模型允许PowerShell的当前路径与Windows进程工作目录不同,因此任何非PowerShell路径操作函数(例如System.IO.Path中的函数)在PowerShell中将不可靠。
此外,正如您可能已经发现的那样,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($_)
}
}
}
Get-AbsolutePath q:\foo\bar\..\baz
也会失败,具体取决于您对有效路径的定义。值得一提的是,即使是内置的 Test-Path <path> -IsValid
在根源于不存在驱动器的路径上也会失败。 - Keith HillHKLM:\SOFTWARE
是PowerShell中有效的路径,指的是本地计算机注册表中的 SOFTWARE
键。但为了确定它是否有效,它需要弄清楚注册表路径的规则。 - jpmc26$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
这个库很好用: 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
出于以下原因,没有一个答案是完全可以接受的。
基于以下原因,我列出了每个方法的预期结果,如下:
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
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
GetUnresolvedProviderPathFromPSPath
的代码,而且不,你不能使用正则表达式因为它会出现递归问题。这将给出完整路径:
(gci 'fred\frog\..\frag').FullName
这将返回相对于当前目录的路径:
(gci 'fred\frog\..\frag').FullName.Replace((gl).Path + '\', '')
由于某种原因,它们只能在 frag
是文件而不是 目录
的情况下起作用。