¿Cómo utilizo Join-Path para combinar más de dos cadenas en una ruta de archivo?

105

Si quiero combinar dos cadenas en una ruta de archivo, uso Join-Pathasí:

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

Eso imprime "C:\Program Files". Sin embargo, si quiero hacer esto por más de dos cadenas:

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

PowerShell arroja un error:

Join-Path: no se puede encontrar un parámetro posicional que acepte el argumento 'Microsoft Office'.
En D: \ users \ ma \ my_script.ps1: 1 char: 18
+ $ path = join-path <<<< C: "Archivos de programa" "Microsoft Office"
+ CategoryInfo: InvalidArgument: (:) [Join-Path] , ParameterBindingException
+ FullyQualifiedErrorId: PositionalParameterNotFound, Microsoft.PowerShell
.Commands.JoinPathCommand

Intenté usar una matriz de cadenas:

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

Pero PowerShell me pide que ingrese el childpath (ya que no especifiqué el -childpathargumento), por ejemplo, "somepath", y luego crea tres rutas de archivos,

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

lo cual tampoco está bien.

Michael A
fuente

Respuestas:

171

Puede usar la clase .NET Path :

[IO.Path]::Combine('C:\', 'Foo', 'Bar')
Marek Toman
fuente
3
Ciertamente, la forma más concisa, y maneja los separadores de ruta y las barras diagonales finales / iniciales en los fragmentos de ruta correctamente, lo que la respuesta aceptada actual (concatenación de cadenas básica) no lo hace.
David Keaveny
3
Para ejecutar el comando anterior en mi PowerShell, recibo este error: No se puede encontrar una sobrecarga para "Combinar" y el recuento de argumentos: "3". En la línea: 1 carácter: 19 + [io.path] :: combine <<<< ('c: \', 'foo', 'bar') + CategoryInfo: NotSpecified: (:) [], MethodException + FullyQualifiedErrorId: MethodCountCouldNotFindBest
Aamol
@Aamol ¿Qué versión de CLR está ejecutando ( $PSVersionTable)? ¿ [io.path]::combine([string[]]('c:\','foo','bar'))Funciona?
Marek Toman
1
Parece que el límite del parámetro es 3, después de 3 se ignora el primer parámetro. (aquí al menos, ps 5.1, clr 4.0)
ehiller
4
@DavidKeaveny "maneja los separadores de ruta y las barras diagonales finales / iniciales en los fragmentos de ruta correctamente" - No realmente. join-pathhace lo que espera, join-path "C:\" "\foo"salidas C:\foo, Path.Combinesin embargo ignora el primer argumento siempre que el segundo argumento contiene un separador inicial: [io.path]::combine('c:\', '\foo')salidas molestas \foo.
Quantic
99

Dado que Join-Path puede canalizar un valor de ruta, puede canalizar varias declaraciones Join-Path juntas:

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

No es tan conciso como probablemente le gustaría que fuera, pero es completamente PowerShell y es relativamente fácil de leer.

David Keaveny
fuente
3
+1 dado que funcionará en todos los powershell 2,3,4, el problema con [io.path] :: Combine API es diferente para .net framework 3,4
Ram
18

Desde PowerShell 6.0, Join-Path tiene un nuevo parámetro llamado -AdditionalChildPathy puede combinar varias partes de una ruta de forma inmediata . O proporcionando el parámetro adicional o simplemente proporcionando una lista de elementos.

Ejemplo de la documentación :

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

Entonces, en PowerShell 6.0 y superior, su variante

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

funciona como se esperaba!

Marcus Mangelsdorf
fuente
17

Join-Path no es exactamente lo que está buscando. Tiene múltiples usos pero no el que estás buscando. Un ejemplo de 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

Verá que acepta una matriz de cadenas y concatena la cadena secundaria a cada una de las cuales crea rutas completas. En su ejemplo $path = join-path C: "Program Files" "Microsoft Office",. Obtiene el error ya que está pasando tres argumentos posicionales y join-pathsolo acepta dos. Lo que está buscando es un -join, y puedo ver que se trata de un malentendido. En cambio, considere esto con su ejemplo:

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

-Jointoma la matriz de elementos y los concatena \en una sola cadena.

C:\Program Files\Microsoft Office

Intento menor de salvamento

Sí, estaré de acuerdo en que esta respuesta es mejor, pero la mía aún podría funcionar. Los comentarios sugieren que podría haber un problema con las barras, por lo que para seguir con mi enfoque de concatenación, también podría hacer esto.

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

Entonces, si hay problemas con barras diagonales adicionales, se podría manejar siempre que no estén al principio de la cadena (permite rutas UNC ). [io.path]::combine('c:\', 'foo', '\bar\')no funcionaría como se esperaba y el mío lo explicaría. Ambos requieren cadenas adecuadas para la entrada, ya que no puede tener en cuenta todos los escenarios. Considere ambos enfoques, pero, sí, la otra respuesta de mayor calificación es más concisa y ni siquiera sabía que existía.

Además, me gustaría señalar que mi respuesta explica cómo lo que hizo el OP estuvo mal además de proporcionar una sugerencia para abordar el problema central.

Mate
fuente
2
Esto es incorrecto porque aunque varias rutas \ in consecutivas funcionarán, es feo y puede causar problemas potencialmente.
Mikhail Orlov
@MikhailOrlov ¿Puede describir un problema potencial que se supone que solo sugiere que podría suceder? ¿Tienes otra sugerencia? Pregunto porque no veo ningún problema. Si algo está mal, me gustaría abordarlo.
Matt
2
He estado manejando mucho código de baja calidad recientemente, la gente compara rutas por String.Equals y analiza rutas con String.Split ('\\') sin eliminar cadenas vacías. No puedo pensar en nada más peligroso en consecuencias, principalmente estoy siendo paranoico. Gracias por tu edición.
Mikhail Orlov
3
Incluir el separador de ruta de forma explícita puede causar problemas con la portabilidad multiplataforma. Si bien PowerShell actualmente solo se ejecuta en Windows, es probable que eso cambie en un futuro no muy lejano, y es una buena idea desarrollar buenos hábitos lo antes posible. Sin mencionar que estos hábitos se pueden transferir a otros idiomas.
bshacklett
10

Si todavía está utilizando .NET 2.0, [IO.Path]::Combineno tendrá la params string[]sobrecarga que necesita para unir más de dos partes, y verá el error No se puede encontrar una sobrecarga para "Combinar" y el recuento de argumentos: "3".

Un poco menos elegante, pero una solución pura de PowerShell es agregar manualmente partes de la ruta:

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

o

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

Aquí hay algo que hará lo que desearía cuando use una matriz de cadenas para ChildPath.

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

Que salidas

C:\Program Files\Microsoft Office

La única advertencia que encontré es que el valor inicial de $ path debe tener un valor (no puede ser nulo o vacío).

Mike Fair
fuente
4

Aquí hay dos formas más de escribir una función pura de PowerShell para unir un número arbitrario de componentes en una ruta.

Esta primera función usa una única matriz para almacenar todos los componentes y luego un bucle foreach para combinarlos:

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
}

Dado que los componentes de la ruta son elementos de una matriz y todos forman parte de un único argumento, deben estar separados por comas. El uso es el siguiente:

PS C: \> Join-Paths 'C:', 'Archivos de programa', 'Microsoft Office'
C: \ Archivos de programa \ Microsoft Office


Una forma más minimalista de escribir esta función es usar la $argsvariable incorporada y luego contraer el bucle foreach en una sola línea usando el método de Mike Fair.

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

A diferencia de la versión anterior de la función, cada componente de ruta es un argumento separado, por lo que solo se necesita un espacio para separar los argumentos:

PS C: \> Join-Paths2 'C:' 'Archivos de programa' 'Microsoft Office'
C: \ Archivos de programa \ Microsoft Office
Jon
fuente
2

El siguiente enfoque es más conciso que canalizar declaraciones Join-Path:

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

$ p luego contiene la ruta concatenada 'a \ b \ c \ d'.

(Acabo de notar que este es exactamente el mismo enfoque que el de Mike Fair, lo siento).

Daniel
fuente
1

O podría escribir su propia función para ello (que es lo que terminé haciendo).

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

A continuación, puede llamar a la función de esta manera:

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

Esto tiene la ventaja de tener exactamente el mismo comportamiento que la función Join-Path normal y no depende de .NET Framework.

Kevin
fuente
0

Puedes usarlo de esta manera:

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