如何在PowerShell中将绝对路径转换为相对路径?

50
我想在PowerShell脚本中将路径转换为相对路径。如何使用PowerShell实现?
例如:
Path to convert: c:\documents\mynicefiles\afile.txt
Reference path:  c:\documents
Result:          mynicefiles\afile.txt

而且

Path to convert: c:\documents\myproject1\afile.txt
Reference path:  c:\documents\myproject2
Result:          ..\myproject1\afile.txt
6个回答

79

我找到了一个内置的东西,Resolve-Path

Resolve-Path -Relative

此方法返回相对于当前位置的路径。一个简单的用法:

$root = "C:\Users\Dave\"
$current = "C:\Users\Dave\Documents\"
$tmp = Get-Location
Set-Location $root
Resolve-Path -relative $current
Set-Location $tmp

28
不必使用临时变量,您也可以使用 Push-Location 和 Pop-Location 来设置位置,然后恢复到其原始值。这是相同的基本解决方案,但没有使用临时变量。 - John Bledsoe
4
如果根目录还不存在,这将失败。 - Scott
7
聪明,但我不喜欢更改工作目录的副作用(即使你把它切换回来了)。 - Ohad Schneider
5
为了确保即使Resolve-Path抛出异常(取决于$ErrorActionPreference),当前位置也将被恢复,请使用try/catch包装代码:try{ push-location $root; resolve-path -relative $current } finally{ pop-location } - zett42

18

1
我不同意“更安全”的说法,尽管它更简单。注意:这仅适用于 .Net 5.0 RC 和 .Net core。 - Dave Hillier
在Windows 10中,需要单独下载并安装PowerShell Core才能支持这个.Net Core方法。接受的答案可以直接使用。 - sw1337

1

这里有一个很好的答案在这里, 但它会改变你当前的目录(它会还原回去),但如果你真的需要隔离那个进程,下面的代码示例可以帮助。它做的事情是一样的,只是在一个新的PowerShell实例中。

function Get-RelativePath($path, $relativeTo) {
    $powershell = (Get-Process -PID $PID | Get-Item)
    if ([string]::IsNullOrEmpty($powershell)) {
        $powershell = "powershell.exe"
    }

    & $powershell -NoProfile -NonInteractive -ExecutionPolicy Unrestricted -Command "& { Set-Location `"$relativeTo`"; Resolve-Path `"$path`" -Relative}"
}

虽然它真的很慢,但除非你非常需要使用它,否则你应该使用其他版本。


1
有时我有多个“根目录”,想要生成相对文件路径。我发现在这种情况下Resolve-Path -Relative无法使用。更改全局设置,当前位置,以生成相对文件路径似乎容易出错,并且(如果您编写并行代码)可能不是线程安全的。
以下内容适用于早期或最新版本的Powershell和Powershell Core,甚至不会暂时更改您的当前目录,并且与操作系统无关且线程安全。
它不涉及来自OP的第二个示例(根据需要插入..)。
function Get-RelativePath {
    param($path, $relativeTo)
    # strip trailing slash
    $relativeTo = Join-Path `
                      (Split-Path -Parent $relativeTo) `
                      (Split-Path -Leaf $relativeTo)
    $relPath = Split-Path -Leaf $path
    $path = Split-Path -Parent $path
    do {    
        $leaf = Split-Path -Leaf $path
        $relPath = Join-Path $leaf $relPath
        $path = Split-Path -Parent $path
    } until (($path -eq $relativeTo) -Or ($path.Length -eq 0))
    $relPath
}

一个例子:

PS> $configRoot = 'C:\Users\P799634t\code\RMP\v2\AWD'
PS> $queryPath = 'C:\Users\P799634t\code\RMP\v2\AWD\config_queries\LOAD_UNQ_QUEUE_QUERY2.sql'
PS> Write-Host (Get-RelativePath $queryPath $configRoot)
config_queries\LOAD_UNQ_QUEUE_QUERY2.sql

当一个文件路径不是另一个文件路径的子路径时,它会表现得很合理:
PS> $root = 'C:\Users\P799634t\code\RMP\v2\AWD'
PS> $notRelated = 'E:\path\to\origami'
PS> Write-Host (Get-RelativePath $notRelated $root)
E:\path\to\origami

0
一个快速简单的方法是:
$current -replace [regex]::Escape($root), '.'

或者如果你想要相对于你当前的实际位置的路径

$path -replace [regex]::Escape((pwd).Path), '.'

假设您的所有路径都是有效的。


1
如果$root不是$current的根节点,则此方法无效(请参见问题2的示例)。 - PMF

-4

这里有一种替代方法

$pathToConvert1 = "c:\documents\mynicefiles\afile.txt"
$referencePath1 = "c:\documents"
$result1 = $pathToConvert1.Substring($referencePath1.Length + 1)
#$result1:  mynicefiles\afile.txt


并且

$pathToConvert2 = "c:\documents\myproject1\afile.txt"
#$referencePath2 = "c:\documents\myproject2"
$result2 = "..\myproject" + [regex]::Replace($pathToConvert2 , ".*\d+", '')
#$result2:          ..\myproject\afile.txt

注意:在第二种情况下,未使用引用路径。

我认为你误解了这两个例子。我想要一种方法来解决它们。 - Dave Hillier
是的,我误解了你的示例,因为它指向文件的绝对路径。 - yantaq
不是楼主,但这正是我寻找的解决方法。谢谢。 - B_Dubb42

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