Como faço para usar o Join-Path para combinar mais de duas strings em um caminho de arquivo?

105

Se eu quiser combinar duas strings em um caminho de arquivo, uso Join-Pathassim:

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

Isso imprime "C:\Program Files". Se eu quiser fazer isso por mais de duas strings:

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

PowerShell gera um erro:

Join-Path: Não foi encontrado um parâmetro posicional que aceite o argumento 'Microsoft Office'.
Em D: \ users \ ma \ my_script.ps1: 1 char: 18
+ $ path = join-path <<<< C: "Arquivos de programas" "Microsoft Office"
+ CategoryInfo: InvalidArgument: (:) [Join-Path] , ParameterBindingException
+ FullyQualifiedErrorId: PositionalParameterNotFound, Microsoft.PowerShell
.Commands.JoinPathCommand

Tentei usar uma matriz de string:

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

Mas o PowerShell me pede para inserir o childpath (já que não especifiquei o -childpathargumento), por exemplo, "somepath", e então cria três caminhos de arquivos,

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

o que também não está certo.

Michael A
fonte

Respostas:

171

Você pode usar a classe .NET Path :

[IO.Path]::Combine('C:\', 'Foo', 'Bar')
Marek Toman
fonte
3
Certamente a forma mais concisa e lida corretamente com separadores de caminho e barras finais / iniciais em fragmentos de caminho, o que a resposta aceita atualmente (concatenação de string básica) não faz.
David Keaveny
3
Por executar o comando acima em meu PowerShell ise recebendo este erro - Não consigo encontrar uma sobrecarga para "Combinar" e a contagem de argumentos: "3". Na linha: 1 char: 19 + [io.path] :: combine <<<< ('c: \', 'foo', 'bar') + CategoryInfo: NotSpecified: (:) [], MethodException + FullyQualifiedErrorId: MethodCountCouldNotFindBest
Aamol
@Aamol Qual versão do CLR você está executando ( $PSVersionTable)? Funciona [io.path]::combine([string[]]('c:\','foo','bar'))?
Marek Toman
1
Parece que o limite do parâmetro é 3, após 3 o primeiro parâmetro é ignorado. (aqui, pelo menos, ps 5.1, clr 4.0)
ehiller
4
@DavidKeaveny "lida com separadores de caminho e barras finais / iniciais em fragmentos de caminho corretamente" - Na verdade, não. join-pathfaz o que você espera, join-path "C:\" "\foo"gera resultados C:\foo, Path.Combineporém ignora o primeiro argumento sempre que o segundo argumento contém um separador inicial: [io.path]::combine('c:\', '\foo')saídas irritantes \foo.
Quantic
99

Como o Join-Path pode ser canalizado para um valor de caminho, você pode canalizar várias instruções Join-Path juntas:

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

Não é tão conciso quanto você provavelmente gostaria que fosse, mas é totalmente PowerShell e é relativamente fácil de ler.

David Keaveny
fonte
3
+1, uma vez que funcionará em todo o PowerShell 2,3,4, o problema com a API [io.path] :: Combine API é diferente para o framework .net 3,4
Ram
18

Desde o PowerShell 6.0, Join-Path tem um novo parâmetro chamado -AdditionalChildPathe pode combinar várias partes de um caminho pronto para uso . Fornecendo o parâmetro extra ou apenas fornecendo uma lista de elementos.

Exemplo da documentação :

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

Portanto, no PowerShell 6.0 e acima, sua variante

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

funciona como esperado!

Marcus Mangelsdorf
fonte
17

O Join-Path não é exatamente o que você está procurando. Tem múltiplos usos, mas não aquele que você procura. Um exemplo 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

Você vê que ele aceita uma matriz de strings e concatena a string filho para cada um criando caminhos completos. No seu exemplo $path = join-path C: "Program Files" "Microsoft Office",. Você está recebendo o erro porque está passando três argumentos posicionais e join-pathaceita apenas dois. O que você está procurando é um -join, e posso ver que isso é um mal-entendido. Em vez disso, considere o seguinte com seu exemplo:

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

-Joinpega a matriz de itens e os concatena \em uma única string.

C:\Program Files\Microsoft Office

Pequena tentativa de salvamento

Sim, concordo que essa resposta é melhor, mas a minha ainda pode funcionar. Os comentários sugerem que pode haver um problema com barras, portanto, para manter minha abordagem de concatenação, você também pode fazer isso.

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

Portanto, se houver problemas com barras extras, isso pode ser tratado, desde que não estejam no início da string (permite caminhos UNC ). [io.path]::combine('c:\', 'foo', '\bar\')não funcionaria como esperado e o meu seria responsável por isso. Ambos requerem cadeias de caracteres adequadas para entrada, pois você não pode levar em conta todos os cenários. Considere as duas abordagens, mas, sim, a outra resposta com classificação mais alta é mais concisa, e eu nem sabia que ela existia.

Além disso, gostaria de salientar que minha resposta explica como o que o OP estava fazendo estava errado, além de fornecer uma sugestão para resolver o problema central.

Matt
fonte
2
Isso está errado porque, embora vários caminhos \ in consecutivos funcionem, é feio e pode causar problemas potencialmente.
Mikhail Orlov
@MikhailOrlov Você pode descrever um problema potencial apenas como uma sugestão de que pode acontecer? Você tem outra sugestão? Estou perguntando porque não vejo problema. Se algo estiver errado, eu gostaria de resolver isso.
Matt
2
Tenho lidado com muitos códigos de baixa qualidade recentemente, as pessoas comparam caminhos por String.Equals e analisam caminhos com String.Split ('\\') sem remover strings vazias. Não consigo pensar em nada mais perigoso em conseqüências, principalmente estou sendo paranóico. Obrigado por sua edição.
Mikhail Orlov
3
Incluir o separador de caminho explicitamente pode causar problemas com a portabilidade de plataforma cruzada. Embora o PowerShell atualmente só seja executado no Windows, isso provavelmente mudará em um futuro não muito distante, e é uma boa ideia desenvolver bons hábitos o mais cedo possível. Sem falar que esses hábitos podem ser transferidos para outras línguas.
bshacklett
10

Se você ainda estiver usando .NET 2.0, [IO.Path]::Combinenão terá a params string[]sobrecarga necessária para unir mais de duas partes e verá o erro Não é possível encontrar uma sobrecarga para "Combinar" e a contagem de argumentos: "3".

Um pouco menos elegante, mas uma solução pura do PowerShell é agregar manualmente as partes do caminho:

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

ou

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

Aqui está algo que fará o que você deseja ao usar uma matriz de string para o ChildPath.

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

Quais saídas

C:\Program Files\Microsoft Office

A única ressalva que descobri é que o valor inicial para $ path deve ter um valor (não pode ser nulo ou vazio).

Mike Fair
fonte
4

Aqui estão mais duas maneiras de escrever uma função PowerShell pura para unir um número arbitrário de componentes em um caminho.

Esta primeira função usa uma única matriz para armazenar todos os componentes e, em seguida, um loop foreach para combiná-los:

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
}

Como os componentes do caminho são elementos em uma matriz e todos fazem parte de um único argumento, eles devem ser separados por vírgulas. O uso é o seguinte:

PS C: \> Join-Paths 'C:', 'Arquivos de programas', 'Microsoft Office'
C: \ Arquivos de programas \ Microsoft Office


Uma maneira mais minimalista de escrever essa função é usar a $argsvariável embutida e, em seguida, recolher o loop foreach em uma única linha usando o método de Mike Fair.

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

Ao contrário da versão anterior da função, cada componente do caminho é um argumento separado, portanto, apenas um espaço é necessário para separar os argumentos:

PS C: \> Join-Paths2 'C:' 'Arquivos de programas' 'Microsoft Office'
C: \ Arquivos de programas \ Microsoft Office
Jon
fonte
2

A abordagem a seguir é mais concisa do que a tubulação de instruções Join-Path:

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

$ p então contém o caminho concatenado 'a \ b \ c \ d'.

(Acabei de notar que esta é exatamente a mesma abordagem que a de Mike Fair, desculpe.)

Daniel
fonte
1

Ou você pode escrever sua própria função para ele (que é o que acabei fazendo).

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

Você poderia então chamar a função assim:

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

Isso tem a vantagem de ter exatamente o mesmo comportamento da função Join-Path normal e não depender do .NET Framework.

Kevin
fonte
0

Você pode usá-lo desta forma:

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