web-dev-qa-db-fra.com

Comment normaliser un chemin dans PowerShell?

J'ai deux chemins:

fred\frog

et

..\frag

Je peux les réunir dans PowerShell comme ceci:

join-path 'fred\frog' '..\frag'

Cela me donne ceci:

fred\frog\..\frag

Mais je ne veux pas ça. Je veux un chemin normalisé sans double point, comme ceci:

fred\frag

Comment puis-je l'obtenir?

82
dan-gph

Vous pouvez utiliser une combinaison de pwd, Join-Path Et [System.IO.Path]::GetFullPath Pour obtenir un chemin développé complet.

Étant donné que cd (Set-Location) Ne modifie pas le répertoire de travail actuel du processus, le simple fait de passer un nom de fichier relatif à une API .NET qui ne comprend pas le contexte PowerShell peut avoir des effets secondaires involontaires , comme la résolution d'un chemin basé sur le répertoire de travail initial (pas votre emplacement actuel).

Ce que vous faites, c'est d'abord que vous qualifiez votre chemin:

Join-Path (Join-Path (pwd) fred\frog) '..\frag'

Cela donne (compte tenu de mon emplacement actuel):

C:\WINDOWS\system32\fred\frog\..\frag

Avec une base absolue, il est sûr d'appeler l'API .NET GetFullPath:

[System.IO.Path]::GetFullPath((Join-Path (Join-Path (pwd) fred\frog) '..\frag'))

Ce qui vous donne le chemin d'accès complet et avec le .. Supprimé:

C:\WINDOWS\system32\fred\frag

Ce n'est pas compliqué non plus, personnellement, je dédaigne les solutions qui dépendent de scripts externes pour cela, c'est un problème simple résolu assez judicieusement par Join-Path Et pwd (GetFullPath c'est juste pour faire c'est joli). Si vous voulez seulement garder seulement la partie relative, il vous suffit d'ajouter .Substring((pwd).Path.Trim('\').Length + 1) et le tour est joué!

fred\frag

MISE À JOUR

Merci à @Dangph d'avoir souligné le cas C:\ Edge.

75
John Leidegren

Vous pouvez étendre ..\frag à son chemin complet avec resolve-path:

PS > resolve-path ..\frag 

Essayez de normaliser le chemin en utilisant la méthode combine ():

[io.path]::Combine("fred\frog",(resolve-path ..\frag).path)
98
Shay Levy

Vous pouvez également utiliser Path.GetFullPath , bien que (comme avec la réponse de Dan R) cela vous donnera le chemin complet. L'utilisation serait la suivante:

[IO.Path]::GetFullPath( "fred\frog\..\frag" )

ou plus intéressant

[IO.Path]::GetFullPath( (join-path "fred\frog" "..\frag") )

les deux produisent les éléments suivants (en supposant que votre répertoire actuel est D: \):

D:\fred\frag

Notez que cette méthode ne tente pas de déterminer si fred ou frag existent réellement.

23
Charlie

La réponse acceptée a été d'une grande aide, mais elle ne "normalise" pas correctement un chemin absolu. Retrouvez ci-dessous mon travail dérivé qui normalise les chemins absolus et relatifs.

function Get-AbsolutePath ($Path)
{
    # System.IO.Path.Combine has two properties making it necesarry here:
    #   1) correctly deals with situations where $Path (the second term) is an absolute path
    #   2) correctly deals with situations where $Path (the second term) is relative
    # (join-path) commandlet does not have this first property
    $Path = [System.IO.Path]::Combine( ((pwd).Path), ($Path) );

    # this piece strips out any relative path modifiers like '..' and '.'
    $Path = [System.IO.Path]::GetFullPath($Path);

    return $Path;
}
17
Sean Hanna

Toutes les fonctions de manipulation de chemin non PowerShell (telles que celles de System.IO.Path) ne seront pas fiables à partir de PowerShell car le modèle de fournisseur de PowerShell permet au chemin actuel de PowerShell de différer de ce que Windows pense que le répertoire de travail du processus est.

En outre, comme vous l'avez peut-être déjà découvert, les applets de commande Resolve-Path et Convert-Path de PowerShell sont utiles pour convertir des chemins relatifs (ceux contenant des "..") en chemins absolus qualifiés de lecteur, mais ils échouent si le chemin référencé n'existe pas.

L'applet de commande très simple suivante devrait fonctionner pour les chemins inexistants. Il convertira "fred\frog\..\frag" en "d:\fred\frag" même si aucun fichier ou dossier "fred" ou "frag" ne peut être trouvé (et le lecteur PowerShell actuel est "d:") .

function Get-AbsolutePath {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [string[]]
        $Path
    )

    process {
        $Path | ForEach-Object {
            $PSCmdlet.SessionState.Path.GetUnresolvedProviderPathFromPSPath($_)
        }
    }
}
10
Jason Stangroome

Cette bibliothèque est bonne: NDepend.Helpers.FileDirectoryPath .

EDIT: Voici ce que j'ai trouvé:

[Reflection.Assembly]::LoadFrom("path\to\NDepend.Helpers.FileDirectoryPath.dll") | out-null

Function NormalizePath ($path)
{
    if (-not $path.StartsWith('.\'))  # FilePathRelative requires relative paths to begin with '.'
    {
        $path = ".\$path"
    }

    if ($path -eq '.\.')  # FilePathRelative can't deal with this case
    {
        $result = '.'
    }
    else
    {
        $relPath = New-Object NDepend.Helpers.FileDirectoryPath.FilePathRelative($path)
        $result = $relPath.Path
    }

    if ($result.StartsWith('.\')) # remove '.\'. 
    {
        $result = $result.SubString(2)
    }

    $result
}

Appelez ça comme ceci:

> NormalizePath "fred\frog\..\frag"
fred\frag

Notez que cet extrait nécessite le chemin d'accès à la DLL. Il y a une astuce que vous pouvez utiliser pour trouver le dossier contenant le script en cours d'exécution, mais dans mon cas, j'avais une variable d'environnement que je pouvais utiliser, donc je viens de l'utiliser.

3
dan-gph

Créez une fonction. Cette fonction normalisera un chemin d'accès qui n'existe pas sur votre système et n'ajoutera pas de lettres de lecteurs.

function RemoveDotsInPath {
  [cmdletbinding()]
  Param( [Parameter(Position=0,  Mandatory=$true)] [string] $PathString = '' )

  $newPath = $PathString -creplace '(?<grp>[^\n\\]+\\)+(?<-grp>\.\.\\)+(?(grp)(?!))', ''
  return $newPath
}

Ex:

$a = 'fooA\obj\BusinessLayer\..\..\bin\BusinessLayer\foo.txt'
RemoveDotsInPath $a
'fooA\bin\BusinessLayer\foo.txt'

Merci à Oliver Schadlich pour son aide dans le RegEx.

2
M.Hubers

Cela donne le chemin complet:

(gci 'fred\frog\..\frag').FullName

Cela donne le chemin par rapport au répertoire courant:

(gci 'fred\frog\..\frag').FullName.Replace((gl).Path + '\', '')

Pour une raison quelconque, ils ne fonctionnent que si frag est un fichier, pas un directory.

1
dan-gph

Si le chemin inclut un qualificatif (lettre de lecteur), alors la réponse de x0n à Powershell: résoudre le chemin qui pourrait ne pas exister? normalisera le chemin. Si le chemin n'inclut pas le qualificatif, il sera toujours normalisé mais renverra le chemin complet par rapport au répertoire actuel, ce qui peut ne pas être ce que vous voulez.

$p = 'X:\fred\frog\..\frag'
$ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($p)
X:\fred\frag

$p = '\fred\frog\..\frag'
$ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($p)
C:\fred\frag

$p = 'fred\frog\..\frag'
$ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($p)
C:\Users\WileCau\fred\frag
1
WileCau

Eh bien, une façon serait:

Join-Path 'fred\frog' '..\frag'.Replace('..', '')

Attendez, je comprends peut-être mal la question. Dans votre exemple, frag est-il un sous-dossier de grenouille?

0
EBGreen

Si vous devez vous débarrasser de la partie .., vous pouvez utiliser un objet System.IO.DirectoryInfo. Utilisez 'fred\frog ..\frag' dans le constructeur. La propriété FullName vous donnera le nom de répertoire normalisé.

Le seul inconvénient est qu'il vous donnera le chemin complet (par exemple c:\test\fred\frag).

0
Dan R

Les parties utiles des commentaires ici combinés de telle sorte qu'ils unifient les chemins relatifs et absolus:

[System.IO.Directory]::SetCurrentDirectory($pwd)
[IO.Path]::GetFullPath($dapath)

Certains échantillons:

$fps = '.', 'file.txt', '.\file.txt', '..\file.txt', 'c:\somewhere\file.txt'
$fps | % { [IO.Path]::GetFullPath($_) }

sortie:

C:\Users\thelonius\tests
C:\Users\thelonius\tests\file.txt
C:\Users\thelonius\tests\file.txt
C:\Users\thelonius\file.txt
c:\somewhere\file.txt
0
TNT