Wie verwende ich Join-Path, um mehr als zwei Zeichenfolgen zu einem Dateipfad zu kombinieren?

105

Wenn ich zwei Zeichenfolgen zu einem Dateipfad kombinieren möchte, verwende ich Folgendes Join-Path:

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

Das druckt "C:\Program Files". Wenn ich dies jedoch für mehr als zwei Zeichenfolgen tun möchte:

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

PowerShell gibt einen Fehler aus:

Join-Pfad: Es kann kein Positionsparameter gefunden werden, der das Argument 'Microsoft Office' akzeptiert.
Unter D: \ users \ ma \ my_script.ps1: 1 char: 18
+ $ path = Join-Pfad <<<< C: "Programme" "Microsoft Office"
+ CategoryInfo: InvalidArgument: (:) [Join-Path] , ParameterBindingException
+ FullyQualifiedErrorId: PositionalParameterNotFound, Microsoft.PowerShell
.Commands.JoinPathCommand

Ich habe versucht, ein String-Array zu verwenden:

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

PowerShell fordert mich jedoch auf, den untergeordneten Pfad einzugeben (da ich das -childpathArgument nicht angegeben habe ), z. B. "somepath", und erstellt dann drei Dateipfade.

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

was auch nicht richtig ist.

Michael A.
quelle

Antworten:

171

Sie können die .NET Path- Klasse verwenden:

[IO.Path]::Combine('C:\', 'Foo', 'Bar')
Marek Toman
quelle
3
Sicherlich die prägnanteste Form und behandelt Pfadtrennzeichen und nachgestellte / führende Schrägstriche auf Pfadfragmenten ordnungsgemäß, was die aktuell akzeptierte Antwort (grundlegende Zeichenfolgenverkettung) nicht tut.
David Keaveny
3
Für die Ausführung des obigen Befehls in meiner Powershell wird dieser Fehler angezeigt. Es kann keine Überladung für "Kombinieren" und die Anzahl der Argumente gefunden werden: "3". In Zeile: 1 Zeichen: 19 + [io.path] :: kombinieren <<<< ('c: \', 'foo', 'bar') + CategoryInfo: NotSpecified: (:) [], MethodException + FullyQualifiedErrorId: MethodCountCouldNotFindBest
Aamol
@Aamol Welche CLR-Version verwenden Sie ( $PSVersionTable)? Funktioniert [io.path]::combine([string[]]('c:\','foo','bar'))?
Marek Toman
1
Die Parametergrenze scheint 3 zu sein, nach 3 wird der erste Parameter ignoriert. (hier zumindest ps 5.1, clr 4.0)
ehiller
4
@DavidKeaveny "behandelt Pfadtrennzeichen und nachgestellte / führende Schrägstriche auf Pfadfragmenten richtig" - Nicht wirklich. join-pathtut, was Sie erwarten, join-path "C:\" "\foo"gibt aus C:\foo, Path.Combineignoriert jedoch das erste Argument, wenn das zweite Argument ein führendes Trennzeichen enthält: [io.path]::combine('c:\', '\foo')ärgerlich Ausgaben \foo.
Quantic
99

Da Join-Path einen Pfadwert weiterleiten kann, können Sie mehrere Join-Path-Anweisungen zusammen leiten:

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

Es ist nicht so knapp, wie Sie es wahrscheinlich gerne hätten, aber es ist vollständig PowerShell und relativ einfach zu lesen.

David Keaveny
quelle
3
+1 Da es über alle Powershell 2,3,4 funktioniert, ist das Problem mit der [io.path] :: Combine API für das .net Framework 3,4
Ram
18

Seit PowerShell 6.0 hat Join-Path einen neuen Parameter namens -AdditionalChildPathund kann mehrere Teile eines sofort einsatzbereiten Pfads kombinieren . Entweder durch Angabe des zusätzlichen Parameters oder durch Angabe einer Liste von Elementen.

Beispiel aus der Dokumentation :

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

Also in PowerShell 6.0 und höher Ihre Variante

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

funktioniert wie erwartet!

Marcus Mangelsdorf
quelle
17

Join-Path ist nicht genau das, wonach Sie suchen. Es hat mehrere Verwendungszwecke, aber nicht den, den Sie suchen. Ein Beispiel aus dem Feiern mit 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

Sie sehen, dass es ein Array von Zeichenfolgen akzeptiert und die untergeordnete Zeichenfolge mit jeder verknüpften vollständigen Pfad verknüpft. In Ihrem Beispiel $path = join-path C: "Program Files" "Microsoft Office". Sie erhalten den Fehler, da Sie drei Positionsargumente übergeben und join-pathnur zwei akzeptieren. Was Sie suchen, ist ein -join, und ich könnte sehen, dass dies ein Missverständnis ist. Betrachten Sie dies stattdessen anhand Ihres Beispiels:

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

-JoinNimmt das Array von Elementen und verkettet sie mit \einer einzigen Zeichenfolge.

C:\Program Files\Microsoft Office

Kleiner Versuch einer Bergung

Ja, ich werde zustimmen, dass diese Antwort besser ist, aber meine könnte noch funktionieren. Kommentare deuten darauf hin, dass möglicherweise ein Problem mit Schrägstrichen vorliegt. Um meinen Verkettungsansatz beizubehalten, können Sie dies auch tun.

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

Wenn es also Probleme mit zusätzlichen Schrägstrichen gibt, kann dies behoben werden, solange sie sich nicht am Anfang der Zeichenfolge befinden (erlaubt UNC- Pfade). [io.path]::combine('c:\', 'foo', '\bar\')würde nicht wie erwartet funktionieren und meine würde das erklären. Für beide sind geeignete Zeichenfolgen für die Eingabe erforderlich, da Sie nicht alle Szenarien berücksichtigen können. Betrachten Sie beide Ansätze, aber ja, die andere höher bewertete Antwort ist knapper, und ich wusste nicht einmal, dass es sie gibt.

Ich möchte auch darauf hinweisen, dass meine Antwort erklärt, wie das OP falsch war, zusätzlich zu einem Vorschlag zur Lösung des Kernproblems.

Matt
quelle
2
Dies ist falsch, da es zwar mehrere aufeinanderfolgende \ in-Pfade funktioniert, aber hässlich ist und möglicherweise Probleme verursachen kann.
Mikhail Orlov
@MikhailOrlov Kannst du ein potenzielles Problem so beschreiben, dass es nur darauf hindeutet, dass es passieren könnte? Hast du noch einen Vorschlag? Ich frage, da ich kein Problem sehe. Wenn etwas nicht stimmt, würde ich es gerne ansprechen.
Matt
2
Ich habe in letzter Zeit viel Code von geringer Qualität verarbeitet. Die Leute vergleichen Pfade nach String.Equals und analysieren Pfade mit String.Split ('\\'), ohne leere Strings zu entfernen. Ich kann mir nichts Gefährlicheres vorstellen, meistens bin ich nur paranoid. Vielen Dank für Ihre Bearbeitung.
Mikhail Orlov
3
Das explizite Einfügen des Pfadtrennzeichens kann Probleme mit der plattformübergreifenden Portabilität verursachen. Während PowerShell derzeit nur unter Windows ausgeführt wird, wird sich dies wahrscheinlich in nicht allzu ferner Zukunft ändern. Es ist eine gute Idee, so früh wie möglich gute Gewohnheiten zu entwickeln. Ganz zu schweigen davon, dass diese Gewohnheiten auf andere Sprachen übertragen werden können.
bshacklett
10

Wenn Sie immer noch .NET 2.0 verwenden, [IO.Path]::Combinewird nicht die params string[]Überladung vorhanden sein, die Sie zum Verbinden von mehr als zwei Teilen benötigen, und es wird der Fehler "Überladung für" Kombinieren "und die Argumentanzahl" 3 "wird nicht gefunden.

Etwas weniger elegant, aber eine reine PowerShell-Lösung besteht darin, Pfadteile manuell zu aggregieren:

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

oder

Join-Path  (Join-Path  C: "Program Files") "Microsoft Office"
Konstantin Spirin
quelle
5

Hier ist etwas, das genau das tut, was Sie möchten, wenn Sie ein String-Array für ChildPath verwenden.

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

Welche Ausgänge

C:\Program Files\Microsoft Office

Die einzige Einschränkung, die ich gefunden habe, ist, dass der Anfangswert für $ path einen Wert haben muss (darf nicht null oder leer sein).

Mike Fair
quelle
4

Hier sind zwei weitere Möglichkeiten, eine reine PowerShell-Funktion zu schreiben, um eine beliebige Anzahl von Komponenten zu einem Pfad zusammenzufügen.

Diese erste Funktion verwendet ein einzelnes Array, um alle Komponenten zu speichern, und dann eine foreach-Schleife, um sie zu kombinieren:

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
}

Da die Pfadkomponenten Elemente in einem Array sind und alle Teil eines einzelnen Arguments sind, müssen sie durch Kommas getrennt werden. Die Verwendung ist wie folgt:

PS C: \> Join-Pfade 'C:', 'Programme', 'Microsoft Office'
C: \ Programme \ Microsoft Office


Eine minimalistischere Methode zum Schreiben dieser Funktion besteht darin, die integrierte $argsVariable zu verwenden und dann die foreach-Schleife mithilfe der Methode von Mike Fair in eine einzelne Zeile zu reduzieren.

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

Im Gegensatz zur vorherigen Version der Funktion ist jede Pfadkomponente ein separates Argument, sodass nur ein Leerzeichen erforderlich ist, um die Argumente zu trennen:

PS C: \> Join-Paths2 'C:' 'Programme' 'Microsoft Office'
C: \ Programme \ Microsoft Office
Jon
quelle
2

Der folgende Ansatz ist prägnanter als das Weiterleiten von Join-Path-Anweisungen:

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

$ p enthält dann den verketteten Pfad 'a \ b \ c \ d'.

(Mir ist gerade aufgefallen, dass dies genau der gleiche Ansatz ist wie der von Mike Fair, sorry.)

Daniel
quelle
1

Oder Sie könnten Ihre eigene Funktion dafür schreiben (was ich letztendlich getan habe).

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

Sie können die Funktion dann folgendermaßen aufrufen:

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

Dies hat den Vorteil, dass es genau das gleiche Verhalten wie die normale Join-Path-Funktion aufweist und nicht vom .NET Framework abhängt.

Kevin
quelle
0

Sie können es folgendermaßen verwenden:

$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
quelle