Comment utiliser Join-Path pour combiner plus de deux chaînes dans un chemin de fichier?

105

Si je veux combiner deux chaînes dans un chemin de fichier, j'utilise Join-Pathcomme ceci:

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

Cela s'imprime "C:\Program Files". Si je veux faire cela pour plus de deux chaînes cependant:

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

PowerShell génère une erreur:

Join-Path: Impossible de trouver un paramètre de position qui accepte l'argument «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

J'ai essayé d'utiliser un tableau de chaînes:

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

Mais PowerShell m'invite à entrer le chemin enfant (puisque je n'ai pas spécifié l' -childpathargument), par exemple "somepath", puis crée trois chemins de fichiers,

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

ce qui n'est pas juste non plus.

Michael A
la source

Réponses:

171

Vous pouvez utiliser la classe .NET Path :

[IO.Path]::Combine('C:\', 'Foo', 'Bar')
Marek Toman
la source
3
Certainement la forme la plus concise, et gère correctement les séparateurs de chemin et les barres obliques de fin / de début sur les fragments de chemin, ce que la réponse acceptée actuelle (concaténation de chaînes de base) ne fait pas.
David Keaveny
3
Pour exécuter la commande ci-dessus dans mon PowerShell, c'est obtenir cette erreur - Impossible de trouver une surcharge pour "Combine" et le nombre d'arguments: "3". À la ligne: 1 char: 19 + [io.path] :: combine <<<< ('c: \', 'foo', 'bar') + CategoryInfo: NotSpecified: (:) [], MethodException + FullyQualifiedErrorId: MethodCountCouldNotFindBest
Aamol
@Aamol Quelle version de CLR vous utilisez ( $PSVersionTable)? Ça [io.path]::combine([string[]]('c:\','foo','bar'))marche?
Marek Toman
1
Il semble que la limite du paramètre est de 3, après 3, le premier paramètre est ignoré. (ici au moins, ps 5.1, clr 4.0)
ehiller
4
@DavidKeaveny "gère correctement les séparateurs de chemin et les barres obliques de fin / de tête sur les fragments de chemin" - Pas vraiment. join-pathfait ce que vous attendez, join-path "C:\" "\foo"génère C:\foo, Path.Combinemais ignore le premier argument chaque fois que le deuxième argument contient un séparateur de début: [io.path]::combine('c:\', '\foo')affiche de manière ennuyeuse \foo.
Quantic
99

Puisque Join-Path peut recevoir une valeur de chemin, vous pouvez diriger plusieurs instructions Join-Path ensemble:

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

Ce n'est pas aussi laconique que vous le souhaiteriez probablement, mais c'est entièrement PowerShell et est relativement facile à lire.

David Keaveny
la source
3
+1 étant donné qu'il fonctionnera sur tous les PowerShell 2,3,4, le problème avec l'API [io.path] :: Combine est différent pour .net framework 3,4
Ram
18

Depuis PowerShell 6.0, Join-Path a un nouveau paramètre appelé -AdditionalChildPathet peut combiner plusieurs parties d'un chemin hors de la boîte . Soit en fournissant le paramètre supplémentaire, soit en fournissant simplement une liste d'éléments.

Exemple de la documentation :

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

Donc dans PowerShell 6.0 et au-dessus de votre variante

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

fonctionne comme prévu!

Marcus Mangelsdorf
la source
17

Join-Path n'est pas exactement ce que vous recherchez. Il a de multiples usages mais pas celui que vous recherchez. Un exemple de la fête avec 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

Vous voyez qu'il accepte un tableau de chaînes, et il concatène la chaîne enfant à chaque création de chemins complets. Dans votre exemple $path = join-path C: "Program Files" "Microsoft Office",. Vous obtenez l'erreur car vous passez trois arguments de position et join-pathn'en acceptez que deux. Ce que vous recherchez, c'est un -join, et je pourrais voir que c'est un malentendu. Considérez plutôt ceci avec votre exemple:

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

-Joinprend le tableau d'éléments et les concatène avec \en une seule chaîne.

C:\Program Files\Microsoft Office

Petite tentative de sauvetage

Oui, je conviendrai que cette réponse est meilleure, mais la mienne pourrait encore fonctionner. Les commentaires suggèrent qu'il pourrait y avoir un problème avec les barres obliques, donc pour rester avec mon approche de concaténation, vous pouvez également le faire.

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

Donc, s'il y a des problèmes avec des barres obliques supplémentaires, cela peut être géré tant qu'ils ne sont pas au début de la chaîne (autorise les chemins UNC ). [io.path]::combine('c:\', 'foo', '\bar\')ne fonctionnerait pas comme prévu et le mien en serait responsable. Les deux nécessitent des chaînes appropriées pour l'entrée car vous ne pouvez pas tenir compte de tous les scénarios. Considérez les deux approches, mais, oui, l'autre réponse mieux notée est plus laconique, et je ne savais même pas qu'elle existait.

De plus, je tiens à souligner que ma réponse explique en quoi ce que le PO a fait était mal en plus de fournir une suggestion pour résoudre le problème central.

Mat
la source
2
C'est faux car même si plusieurs chemins \ in consécutifs fonctionnent, ils sont moche et peuvent potentiellement causer des problèmes.
Mikhail Orlov
@MikhailOrlov Pouvez-vous décrire un problème potentiel comme supposant simplement qu'il pourrait se produire? Avez-vous une autre suggestion? Je demande car je ne vois pas de problème. Si quelque chose ne va pas, j'aimerais y remédier.
Matt
2
J'ai récemment manipulé beaucoup de code de mauvaise qualité, les gens comparent les chemins par String.Equals et analysent les chemins avec String.Split ('\\') sans supprimer les chaînes vides. Je ne peux penser à rien de plus dangereux en termes de conséquences, surtout je suis juste paranoïaque. Merci pour votre modification.
Mikhail Orlov
3
L'inclusion explicite du séparateur de chemin peut entraîner des problèmes de portabilité multiplateforme. Bien que PowerShell ne fonctionne actuellement que sur Windows, cela est susceptible de changer dans un avenir pas trop lointain, et c'est une bonne idée de développer de bonnes habitudes le plus tôt possible. Sans compter que ces habitudes peuvent se transférer dans d'autres langues.
bshacklett
10

Si vous utilisez toujours .NET 2.0, vous [IO.Path]::Combinen'aurez pas la params string[]surcharge dont vous avez besoin pour joindre plus de deux parties, et vous verrez l'erreur Impossible de trouver une surcharge pour "Combine" et le nombre d'arguments: "3".

Un peu moins élégant, mais une solution pure PowerShell consiste à agréger manuellement les parties de chemin:

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

ou

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

Voici quelque chose qui fera ce que vous voudriez lorsque vous utilisez un tableau de chaînes pour ChildPath.

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

Quelles sorties

C:\Program Files\Microsoft Office

La seule mise en garde que j'ai trouvée est que la valeur initiale de $ path doit avoir une valeur (ne peut pas être nulle ou vide).

Mike Fair
la source
4

Voici deux autres façons d'écrire une fonction PowerShell pure pour joindre un nombre arbitraire de composants dans un chemin.

Cette première fonction utilise un seul tableau pour stocker tous les composants, puis une boucle foreach pour les combiner:

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
}

Étant donné que les composants du chemin sont des éléments d'un tableau et font tous partie d'un seul argument, ils doivent être séparés par des virgules. L'utilisation est la suivante:

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


Une manière plus minimaliste d'écrire cette fonction consiste à utiliser la $argsvariable intégrée , puis à réduire la boucle foreach en une seule ligne en utilisant la méthode de Mike Fair.

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

Contrairement à la version précédente de la fonction, chaque composant de chemin est un argument distinct, donc seul un espace est nécessaire pour séparer les arguments:

PS C: \> Join-Paths2 'C:' 'Program Files' 'Microsoft Office'
C: \ Program Files \ Microsoft Office
Jon
la source
2

L'approche suivante est plus concise que le piping d'instructions Join-Path:

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

$ p contient alors le chemin concaténé 'a \ b \ c \ d'.

(Je viens de remarquer que c'est exactement la même approche que celle de Mike Fair, désolé.)

Daniel
la source
1

Ou vous pouvez écrire votre propre fonction (ce que j'ai fini par faire).

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

Vous pouvez alors appeler la fonction comme ceci:

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

Cela présente l'avantage d'avoir exactement le même comportement que la fonction Join-Path normale et ne dépend pas du .NET Framework.

Kevin
la source
0

Vous pouvez l'utiliser de cette façon:

$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
la source