Як використовувати 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: "Файли програми" "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

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

Відповіді:


171

Ви можете використовувати клас .NET Path :

[IO.Path]::Combine('C:\', 'Foo', 'Bar')

3
Безумовно, найкоротша форма та належним чином обробляє роздільники шляхів та кінці / провідні риски на фрагментах контуру, на що поточна прийнята відповідь (базове з'єднання рядків) не робить.
David Keaveny

3
Для виконання вищевказаної команди в моєму повноваженні ise отримання цієї помилки. Неможливо знайти перевантаження для "Комбінувати" і кількість аргументів: "3". У рядку: 1 char: 19 + [io.path] :: об’єднати <<<< ('c: \', 'foo', 'bar') + CategoryInfo: NotSpecified: (:) [], MethodException + FullyQualifiedErrorId: MethodCountCouldNotFindBest
Aamol

@Aamol Яку версію CLR ви використовуєте ( $PSVersionTable)? Чи [io.path]::combine([string[]]('c:\','foo','bar'))працює?
Марек Томан

1
Здається, що межа параметра 3, після 3 перший параметр ігнорується. (тут принаймні, пс 5.1, клр 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, оскільки він буде працювати у всіх повноваженнях 2,3,4, проблема з [io.path] :: Combine API відрізняється від .net Framework 3,4
Ram

18

Оскільки 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\')не працюватиме так, як очікувалося, і моє це враховуватиме. Для обох потрібні відповідні рядки для введення, оскільки ви не можете врахувати всі сценарії. Розглянемо обидва підходи, але, так, інша відповідь вищого рейтингу є більш лаконічною, і я навіть не знав, що вона існує.

Також я хотів би зазначити, моя відповідь пояснює, як те, що робила ОП, було не так, крім надання пропозиції щодо вирішення основної проблеми.


2
Це неправильно, оскільки, хоча кілька послідовних \ у шляху працюватимуть, це некрасиво і може викликати проблеми.
Михайло Орлов

@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: \> Шляхи приєднання 'C:', 'Файли програм', 'Microsoft Office'
C: \ Файли програм \ 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: \ Файли програм \ 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"
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.