如何使用Join-Path将超过两个字符串组合成文件路径?

166

如果我想将两个字符串组合成文件路径,我会使用 Join-Path,用法如下:

$path = Join-Path C: "Program Files"
Write-Host $path

那将打印 "C:\Program Files"。但如果我想对超过两个字符串执行此操作:

$path = Join-Path C: "Program Files" "Microsoft Office"
Write-Host $path

PowerShell出现错误:

Join-Path:找不到接受参数'Microsoft Office'的位置参数。
在 D:\users\ma\my_script.ps1:1 字符:18
+ $path = join-path <<<< C: "Program Files" "Microsoft Office"
+ CategoryInfo : InvalidArgument: (:) [Join-Path], ParameterBindingException
+ FullyQualifiedErrorId : PositionalParameterNotFound,Microsoft.PowerShell
.Commands.JoinPathCommand

我尝试使用字符串数组:

[string[]] $pieces = "C:", "Program Files", "Microsoft Office"
$path = Join-Path $pieces
Write-Host $path

但是 PowerShell 提示我输入子路径(因为我没有指定 -childpath 参数),例如 "somepath",然后创建三个文件路径。

C:\somepath
Program Files\somepath
Microsoft Office\somepath

这也不是正确的。


5
请注意,自 PowerShell 6 版本起,您直观的第一次尝试现在已经按预期工作并正确处理路径的前导/末尾分隔符。 - Marcus Mangelsdorf
这是我的问题。我使用的是一个只接受2个参数的旧版PowerShell。 - Darren Hoehna
10个回答

226
您可以使用.NET Path 类:
[IO.Path]::Combine('C:\', 'Foo', 'Bar')

4
这是最简洁的形式,可以正确处理路径分隔符和路径段落前后的斜杠,而目前被接受的答案(基本字符串拼接)则不能做到。 - David Keaveny
3
在我的PowerShell ISE中执行上述命令时,出现了以下错误 - 无法找到“Combine”的重载且参数计数为:“3”。 在第1行第19个字符处:
  • [io.path]::combine <<<< ('c:', 'foo', 'bar')
    • CategoryInfo : NotSpecified: (:) [], MethodException
    • FullyQualifiedErrorId : MethodCountCouldNotFindBest
- Aamol
@Aamol 在 .NET 2.0 中,Combine 方法似乎只有两个参数:https://dev59.com/VXRA5IYBdhLWcg3w_C8w。请尝试升级,并检查 \Windows\System32\WindowsPowerShell\v1.0\powershell_ise.exe.config 中的 supportedRuntime 版本,将其设置为 v4.0 - Mark Toman
1
似乎参数限制为3个,在第3个之后,第一个参数将被忽略。(至少在这里,ps 5.1,clr 4.0) - ehiller
6
@DavidKeaveny说“正确处理路径分隔符和路径片段的尾部/开头斜杠”- 实际上并不是这样。join-path做到了你所期望的,join-path "C:\" "\foo"输出C:\foo,但是 Path.Combine却无视第一个参数,每当第二个参数包含一个前导分隔符时:[io.path]::combine('c:\', '\foo') 令人烦恼地输出\foo - Quantic
显示剩余5条评论

131

由于 Join-Path 可以接受路径值的管道输入,因此您可以将多个 Join-Path 语句连接在一起:

Join-Path "C:" -ChildPath "Windows" | Join-Path -ChildPath "system32" | Join-Path -ChildPath "drivers"

它可能不像您希望的那样简洁,但它完全是PowerShell编写的,并且相对容易阅读。


9
+1是因为它可以在所有PowerShell 2,3,4版本中使用。[io.path] :: Combine API的问题在于它在.NET Framework 3,4中会有所不同。 - Ram

59
自PowerShell 6.0以来,Join-Path有一个名为-AdditionalChildPath新参数,可以直接组合路径中的多个部分。可以通过提供额外参数或仅提供元素列表来实现。
来自文档的示例:
Join-Path a b c d e f g
a\b\c\d\e\f\g

因此在 PowerShell 6.0 及以上版本中,您的直觉猜测

$path = Join-Path C: "Program Files" "Microsoft Office"

一切正常!


17

Join-Path并不完全符合您的需求。它有多种用途,但不是您要寻找的那个。以下是来自与Join-Path一起聚会的示例:

Join-Path C:\hello,d:\goodbye,e:\hola,f:\adios world
C:\hello\world
d:\goodbye\world
e:\hola\world
f:\adios\world

你会发现它接受一个字符串数组,并将子字符串连接到每个字符串创建完整路径。在你的例子中,$path = join-path C: "Program Files" "Microsoft Office"。你收到错误是因为你传递了三个位置参数,而join-path仅接受两个参数。你要找的是-join,我可以理解这可能会引起误解。相反,请考虑使用以下内容来替换你的示例:

"C:","Program Files","Microsoft Office" -join "\"

-Join将项目数组连接起来, 用\ 分隔成一个字符串。

C:\Program Files\Microsoft Office

尝试挽救一下

是的,我同意这个答案更好,不过我的方法仍然可行。评论中指出可能存在斜杠问题,因此为了保持我的连接方法,您也可以这样做。

"C:","\\Program Files\","Microsoft Office\" -join "\" -replace "(?!^\\)\\{2,}","\"

所以,如果有多余的斜杠问题,只要它们不在字符串开头(可以允许UNC路径),就可以处理。[io.path]::combine('c:\', 'foo', '\bar\') 不会按预期工作,而我的建议会考虑到这一点。两种方法都需要适当的输入字符串,因为你不能考虑所有情况。请考虑这两种方法,但是是的,其他更高评分的答案更简洁,而我甚至不知道它的存在。

另外,我想指出,我的答案解释了OP做错了什么,并提供了解决核心问题的建议。


3
这是错误的,因为即使在路径中使用多个连续的 \ 会起作用,但它很丑陋并有可能引起问题。 - Mikhail Orlov
@MikhailOrlov,你能描述一下可能存在的问题,而不仅仅是建议它可能发生吗?你有其他建议吗?我问这个问题是因为我没有看到问题。如果有什么问题,我想解决它。 - Matt
3
最近我在处理很多低质量的代码,人们使用String.Equals比较路径,并使用String.Split('\')解析路径而不删除空字符串。对于后果来说,我想不到有什么更危险的了,大多数时候我只是有点神经质。谢谢您的编辑。 - Mikhail Orlov
3
明确定义路径分隔符可能会导致跨平台可移植性问题。虽然PowerShell目前仅在Windows上运行,但这很可能会在不久的将来发生改变,因此尽早养成良好的习惯是个好主意。更不用说这些习惯可以转移到其他语言中。 - bshacklett
1
你的链接已经失效了。这是更新后的链接: https://devblogs.microsoft.com/powershell/partying-with-join-path/ - John Dyer

17
如果你仍在使用.NET 2.0,那么[IO.Path]::Combine将没有params string[]重载,你需要连接超过两个部分,并且你会看到错误信息Cannot find an overload for "Combine" and the argument count: "3". 略显笨拙,但是一个纯PowerShell的解决方案是手动聚合路径部件:
Join-Path C: (Join-Path  "Program Files" "Microsoft Office")
或者
Join-Path  (Join-Path  C: "Program Files") "Microsoft Office"

7

下面是两种编写纯PowerShell函数以将任意数量的组件组合成路径的方法。

第一个函数使用单个数组存储所有组件,然后使用foreach循环将它们组合起来:

function Join-Paths {
    Param(
        [Parameter(mandatory)]
        [String[]]
        $Paths
    )
    $output = $Paths[0]
    foreach($path in $Paths[1..$Paths.Count]) {
        $output = Join-Path $output -ChildPath $path
    }
    $output
}

由于路径组件是数组中的元素,并且都是单个参数的一部分,因此它们必须用逗号分隔。用法如下:
PS C:\> Join-Paths 'C:', 'Program Files', 'Microsoft Office'
C:\Program Files\Microsoft Office
更简洁的编写此函数的方法是使用内置的$args变量,然后使用Mike Fair的方法将foreach循环折叠成一行。
function Join-Paths2 {
    $path = $args[0]
    $args[1..$args.Count] | %{ $path = Join-Path $path $_ }
    $path
}

与该函数的先前版本不同,每个路径组件都是单独的参数,因此只需要使用空格分隔参数:
PS C:\> Join-Paths2 'C:' 'Program Files' 'Microsoft Office'
C:\Program Files\Microsoft Office

6
这里有一个方案,可以满足使用字符串数组作为ChildPath时的需求。
$path = "C:"
@( "Program Files", "Microsoft Office" ) | %{ $path = Join-Path $path $_ }
Write-Host $path

哪些是输出

C:\Program Files\Microsoft Office

我发现唯一的注意事项是,$path的初始值必须有一个值(不能为空或空白)。

2
以下方法比使用Join-Path语句更加简洁:
$p = "a"; "b", "c", "d" | ForEach-Object -Process { $p = Join-Path $p $_ }

$p现在保存了连接后的路径'a\b\c\d'。

(我刚刚注意到这与Mike Fair的方法完全相同,抱歉。)


1
你可以这样使用它:

$root = 'C:'
$folder1 = 'Program Files (x86)'
$folder2 = 'Microsoft.NET'

if (-Not(Test-Path $(Join-Path $root -ChildPath $folder1 | Join-Path -ChildPath $folder2)))
{
   "Folder does not exist"
}
else 
{
   "Folder exist"
}

1

或者您可以为此编写自己的函数(这也是我最终做的事情)。

function Join-Path-Recursively($PathParts) {
    $NumberOfPathParts = $PathParts.Length;

    if ($NumberOfPathParts -eq 0) {
        return $null
    } elseif ($NumberOfPathParts -eq 1) {
        return $PathParts[0]
    } else {
        return Join-Path -Path $PathParts[0] -ChildPath $(Join-Path-Recursively -PathParts $PathParts[1..($NumberOfPathParts-1)])
    }
}

您可以这样调用函数:

You could then call the function like this:

Join-Path-Recursively -PathParts  @("C:", "Program Files", "Microsoft Office")
Join-Path-Recursively  @("C:", "Program Files", "Microsoft Office")

这样做的优点是具有与正常Join-Path函数完全相同的行为,并且不依赖于.NET Framework。

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