Как использовать Join-Path для объединения более двух строк в путь к файлу?

105

Если я хочу объединить две строки в путь к файлу, я использую 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 char: 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

что тоже неправильно.

Майкл А
источник
Обратите внимание, что, начиная с PowerShell 6, ваша интуитивно понятная первая попытка теперь работает должным образом и правильно обрабатывает разделители конечного / ведущего пути в некоторых частях пути 🎉
Маркус Мангельсдорф

Ответы:

171

Вы можете использовать класс .NET Path :

[IO.Path]::Combine('C:\', 'Foo', 'Bar')
Марек Томан
источник
3
Безусловно, наиболее краткая форма, которая правильно обрабатывает разделители путей и конечные / ведущие косые черты во фрагментах пути, чего не делает текущий принятый ответ (базовая конкатенация строк).
Дэвид Кивени
3
Для выполнения вышеуказанной команды в моем PowerShell возникает эта ошибка - Не удается найти перегрузку для «Объединить» и количество аргументов: «3». В строке: 1 символ: 19 + [io.path] :: comb <<<< ('c: \', 'foo', 'bar') + CategoryInfo: NotSpecified: (:) [], MethodException + FullyQualifiedErrorId: MethodCountCouldNotFindBest
Aamol
@Aamol Какую версию CLR вы используете ( $PSVersionTable)? Работает [io.path]::combine([string[]]('c:\','foo','bar'))?
Марек Томан
1
Кажется, что предел параметра равен 3, после 3 первый параметр игнорируется. (здесь, по крайней мере, ps 5.1, clr 4.0)
ehiller
4
@DavidKeaveny "правильно обрабатывает разделители путей и конечные / ведущие косые черты во фрагментах пути" - не совсем. join-pathделает то, что вы ожидаете, join-path "C:\" "\foo"выводит C:\foo, Path.Combineоднако игнорирует первый аргумент всякий раз, когда второй аргумент содержит начальный разделитель: [io.path]::combine('c:\', '\foo')досадно выводит \foo.
Quantic
99

Поскольку Join-Path можно передать по конвейеру значение пути, вы можете передать несколько операторов Join-Path вместе:

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

Это не так лаконично, как вам, вероятно, хотелось бы, но это полностью PowerShell, и его относительно легко читать.

Дэвид Кивени
источник
3
+1, поскольку он будет работать во всех PowerShell 2,3,4, проблема с [io.path] :: Combine API отличается от .net framework 3,4
Ram
19

Начиная с 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, было неправильным, помимо предложения по решению основной проблемы.

Мэтт
источник
2
Это неправильно, потому что даже если несколько последовательных путей \ in будут работать, это некрасиво и потенциально может вызвать проблемы.
Михаил Орлов
@MikhailOrlov Можете ли вы описать потенциальную проблему, просто предположив, что она может произойти? У вас есть другое предложение? Спрашиваю, потому что не вижу проблемы. Если что-то не так, я хотел бы заняться этим.
Мэтт
2
В последнее время я обрабатываю много некачественного кода, люди сравнивают пути с помощью String.Equals и анализируют пути с помощью String.Split ('\\'), не удаляя пустые строки. Я не могу придумать ничего более опасного по последствиям, в основном я просто параноик. Спасибо за ваше редактирование.
Михаил Орлов
3
Включение разделителя путей явно может вызвать проблемы с кроссплатформенной переносимостью. Хотя PowerShell в настоящее время работает только в Windows, это, вероятно, изменится в не столь отдаленном будущем, и рекомендуется как можно раньше выработать хорошие привычки. Не говоря уже о том, что эти привычки могут быть перенесены на другие языки.
bshacklett
10

Если вы все еще используете .NET 2.0, у [IO.Path]::Combineвас не будет params string[]перегрузки, которая вам понадобится для объединения более двух частей, и вы увидите ошибку Не удается найти перегрузку для «Объединить» и количество аргументов: «3».

Чуть менее элегантное, но чистое решение PowerShell - это вручную агрегировать части пути:

Join-Path C: (Join-Path  "Program Files" "Microsoft Office")

или

Join-Path  (Join-Path  C: "Program Files") "Microsoft Office"
Константин Спирин
источник
5

Вот что-то, что будет делать то, что вам нужно при использовании строкового массива для ChildPath.

$path = "C:"
@( "Program Files", "Microsoft Office" ) | %{ $path = Join-Path $path $_ }
Write-Host $path

Какие выходы

C:\Program Files\Microsoft Office

Единственное предостережение, которое я обнаружил, заключается в том, что начальное значение для $ path должно иметь значение (не может быть нулевым или пустым).

Майк Фэйр
источник
4

Вот еще два способа написать чистую функцию 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переменную, а затем свернуть цикл foreach в одну строку с помощью метода Майка Фэйра.

function Join-Paths2 {
    $path = $args[0]
    $args[1..$args.Count] | %{ $path = Join-Path $path $_ }
    $path
}

В отличие от предыдущей версии функции, каждый компонент пути является отдельным аргументом, поэтому для разделения аргументов необходим только пробел:

PS C: \> Join-Paths2 'C:' 'Программные файлы' Microsoft Office '
C: \ Program Files \ Microsoft Office
Джон
источник
2

Следующий подход более лаконичен, чем конвейерные операторы Join-Path:

$p = "a"; "b", "c", "d" | ForEach-Object -Process { $p = Join-Path $p $_ }

Затем $ p содержит объединенный путь 'a \ b \ c \ d'.

(Я только что заметил, что это тот же подход, что и у Майка Фэйра, извините.)

Даниэль
источник
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)])
    }
}

Затем вы можете вызвать функцию следующим образом:

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

Это имеет то преимущество, что имеет то же поведение, что и обычная функция Join-Path, и не зависит от .NET Framework.

Кевин
источник
0

Вы можете использовать это так:

$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"
}
Франческо
источник