Jak używać Join-Path do łączenia więcej niż dwóch ciągów w ścieżkę pliku?

105

Jeśli chcę połączyć dwa ciągi w ścieżkę do pliku, używam w Join-Pathten sposób:

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

To drukuje "C:\Program Files". Jeśli jednak chcę to zrobić dla więcej niż dwóch ciągów:

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

PowerShell zgłasza błąd:

Join-Path: Nie można znaleźć parametru pozycyjnego, który akceptuje argument „Microsoft Office”.
W 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

Próbowałem użyć tablicy ciągów:

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

Ale PowerShell prosi mnie o wpisanie ścieżki potomnej (ponieważ nie podałem -childpathargumentu), np. „Somepath”, a następnie tworzy trzy ścieżki do plików,

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

co też nie jest właściwe.

Michael A.
źródło

Odpowiedzi:

171

Możesz użyć klasy .NET Path :

[IO.Path]::Combine('C:\', 'Foo', 'Bar')
Marek Toman
źródło
3
Z pewnością najbardziej zwięzła forma i poprawnie obsługuje separatory ścieżek i końcowe / wiodące ukośniki na fragmentach ścieżek, czego nie obsługuje obecnie przyjęta odpowiedź (podstawowa konkatenacja ciągów).
David Keaveny
3
W celu wykonania powyższego polecenia w mojej powłoce PowerShell pojawia się ten błąd -Nie można znaleźć przeciążenia dla opcji „Połącz” i liczby argumentów: „3”. W linii: 1 znak: 19 + [io.path] :: połącz <<<< ('c: \', 'foo', 'bar') + CategoryInfo: NotSpecified: (:) [], MethodException + FullyQualifiedErrorId: MethodCountCouldNotFindBest
Aamol
@Aamol Jakiej wersji CLR używasz ( $PSVersionTable)? Działa [io.path]::combine([string[]]('c:\','foo','bar'))?
Marek Toman
1
Wydaje się, że limit parametru wynosi 3, po 3 pierwszy parametr jest ignorowany. (tutaj przynajmniej ps 5.1, clr 4.0)
ehiller
4
@DavidKeaveny "poprawnie obsługuje separatory ścieżek i końcowe / wiodące ukośniki na fragmentach ścieżek" - Niezupełnie. join-pathrobi to, czego się spodziewasz, join-path "C:\" "\foo"generuje C:\foo, Path.Combinejednak ignoruje pierwszy argument, gdy drugi argument zawiera wiodący separator: [io.path]::combine('c:\', '\foo')denerwujące wyjścia \foo.
Quantic
99

Ponieważ Join-Path można przesłać potokiem do wartości ścieżki, można potokować wiele instrukcji Join-Path razem:

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

Nie jest tak zwięzły, jak byś chciał, ale jest w pełni PowerShell i jest stosunkowo łatwy do odczytania.

David Keaveny
źródło
3
+1, ponieważ będzie działać we wszystkich programach PowerShell 2, 3, 4, problem z [io.path] :: Combine API jest inny dla .net framework 3,4
Ram
18

Od PowerShell 6,0 Join-Path ma nowy parametr o nazwie -AdditionalChildPathi może łączyć wiele części ścieżki po wyjęciu z pudełka . Albo przez podanie dodatkowego parametru, albo po prostu podając listę elementów.

Przykład z dokumentacji :

Join-Path a b c d e f g
a\b\c\d\e\f\g

Więc w PowerShell 6.0 i nowszych wersjach

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

działa zgodnie z oczekiwaniami!

Marcus Mangelsdorf
źródło
17

Join-Path nie jest dokładnie tym, czego szukasz. Ma wiele zastosowań, ale nie to, czego szukasz. Przykład z Partying with 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

Widzisz, że akceptuje tablicę ciągów i łączy ciąg potomny z każdym tworzącym pełne ścieżki. W twoim przykładzie $path = join-path C: "Program Files" "Microsoft Office". Otrzymujesz błąd, ponieważ przekazujesz trzy argumenty pozycyjne i join-pathakceptujesz tylko dwa. To, czego szukasz, to -joini widzę, że to nieporozumienie. Zamiast tego rozważ to w swoim przykładzie:

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

-Joinpobiera tablicę elementów i łączy je \w jeden ciąg.

C:\Program Files\Microsoft Office

Niewielka próba ratunku

Tak, zgodzę się, że ta odpowiedź jest lepsza, ale moja może nadal działać. Komentarze sugerują, że może występować problem z ukośnikami, więc aby zachować moje podejście do konkatenacji, możesz to zrobić.

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

Więc jeśli występują problemy z dodatkowymi ukośnikami, można je rozwiązać, o ile nie znajdują się one na początku ciągu (zezwala na ścieżki UNC ). [io.path]::combine('c:\', 'foo', '\bar\')nie działałby zgodnie z oczekiwaniami, a mój tłumaczy to. Oba wymagają odpowiednich ciągów danych wejściowych, ponieważ nie można uwzględnić wszystkich scenariuszy. Rozważ oba podejścia, ale tak, druga wysoko oceniana odpowiedź jest bardziej zwięzła i nawet nie wiedziałem, że istnieje.

Chciałbym również zwrócić uwagę, że moja odpowiedź wyjaśnia, w jaki sposób to, co robił PO, było złe, oprócz przedstawienia sugestii rozwiązania podstawowego problemu.

Matt
źródło
2
Jest to błędne, ponieważ nawet jeśli wiele kolejnych ścieżek \ in będzie działać, jest brzydka i może potencjalnie powodować problemy.
Michaił Orłow
@MikhailOrlov Czy możesz opisać potencjalny problem, sugerując, że może się zdarzyć? Masz inną sugestię? Pytam, bo nie widzę problemu. Jeśli coś jest nie tak, chciałbym się tym zająć.
Matt
2
Ostatnio zajmowałem się wieloma kodami niskiej jakości, ludzie porównują ścieżki za pomocą String.Equals i parsują ścieżki za pomocą String.Split ('\\') bez usuwania pustych ciągów. Nie przychodzi mi do głowy nic bardziej niebezpiecznego w konsekwencjach, głównie jestem paranoikiem. Dziękuję za twoją edycję.
Michaił Orłow
3
Jawne włączenie separatora ścieżek może spowodować problemy z przenośnością między platformami. Chociaż PowerShell obecnie działa tylko w systemie Windows, prawdopodobnie zmieni się to w niezbyt odległej przyszłości i dobrym pomysłem jest jak najwcześniejsze wypracowanie dobrych nawyków. Nie wspominając o tym, że te nawyki mogą przenieść się na inne języki.
bshacklett
10

Jeśli nadal używasz .NET 2.0, [IO.Path]::Combinenie będzie params string[]przeciążenia wymaganego do połączenia więcej niż dwóch części, a zobaczysz błąd Nie można znaleźć przeciążenia dla opcji „Połącz” i liczbę argumentów: „3”.

Nieco mniej eleganckie, ale czystym rozwiązaniem PowerShell jest ręczne agregowanie części ścieżki:

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

lub

Join-Path  (Join-Path  C: "Program Files") "Microsoft Office"
Konstantin Spirin
źródło
5

Oto coś, co zrobi to, co chcesz, używając tablicy ciągów dla ChildPath.

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

Które wyjścia

C:\Program Files\Microsoft Office

Jedynym zastrzeżeniem, które znalazłem, jest to, że początkowa wartość ścieżki $ path musi mieć wartość (nie może być pusta ani zerowa).

Mike Fair
źródło
4

Oto dwa inne sposoby na napisanie czystej funkcji PowerShell, aby połączyć dowolną liczbę komponentów w ścieżkę.

Ta pierwsza funkcja używa pojedynczej tablicy do przechowywania wszystkich składników, a następnie pętli foreach do ich połączenia:

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
}

Ponieważ składniki ścieżki są elementami tablicy i wszystkie są częścią pojedynczego argumentu, muszą być oddzielone przecinkami. Sposób użycia jest następujący:

PS C: \> Join-Paths 'C:', 'Program Files', 'Microsoft Office'
C: \ Program Files \ Microsoft Office


Bardziej minimalistycznym sposobem napisania tej funkcji jest użycie $argszmiennej wbudowanej , a następnie zwinięcie pętli foreach do jednej linii przy użyciu metody Mike'a Fair'a.

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

W przeciwieństwie do poprzedniej wersji funkcji, każdy składnik ścieżki jest oddzielnym argumentem, więc do oddzielenia argumentów potrzebna jest tylko spacja:

PS C: \> Join-Paths2 'C:' 'Program Files' 'Microsoft Office'
C: \ Program Files \ Microsoft Office
Jon
źródło
2

Poniższe podejście jest bardziej zwięzłe niż potokowanie instrukcji Join-Path:

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

$ p przechowuje następnie połączoną ścieżkę „a \ b \ c \ d”.

(Właśnie zauważyłem, że jest to dokładnie to samo podejście, co Mike Fair, przepraszam).

Daniel
źródło
1

Lub możesz napisać dla niego własną funkcję (co ostatecznie zrobiłem).

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)])
    }
}

Możesz wtedy wywołać funkcję w ten sposób:

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

Ma to tę zaletę, że zachowuje się dokładnie tak samo, jak normalna funkcja Join-Path i nie zależy od platformy .NET Framework.

Kevin
źródło
0

Możesz to wykorzystać w ten sposób:

$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"
}
Francesco
źródło