Comment normaliser un chemin dans PowerShell?

94

J'ai deux chemins:

fred\frog

et

..\frag

Je peux les joindre dans PowerShell comme ceci:

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

Cela me donne ceci:

fred\frog\..\frag

Mais je ne veux pas de ça. Je veux un chemin normalisé sans les doubles points, comme ceci:

fred\frag

Comment puis-je l'obtenir?

dan-gph
la source
1
Frag est-il un sous-dossier de grenouille? Sinon, la combinaison du chemin vous donnerait fred \ frog \ frag. Si c'est le cas, c'est une question très différente.
EBGreen

Réponses:

81

Vous pouvez utiliser une combinaison de pwd, Join-Pathet[System.IO.Path]::GetFullPath d'obtenir un chemin dilatée 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 inattendus, tels que la résolution d'un chemin basé sur le travail initial répertoire (pas votre emplacement actuel).

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

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

Cela donne (étant donné ma position actuelle):

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 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 bien par Join-Pathet pwd( GetFullPathc'est juste pour le rendre joli). Si vous ne souhaitez conserver que la partie relative , il vous suffit d'ajouter .Substring((pwd).Path.Trim('\').Length + 1)et le tour est joué!

fred\frag

METTRE À JOUR

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

John Leidegren
la source
La dernière étape ne fonctionne pas si pwd est "C: \". Dans ce cas, j'obtiens "red \ frag".
dan-gph
@Dangph - Je ne suis pas sûr de comprendre ce que vous voulez dire, ce qui précède semble fonctionner correctement? Quelle version de PowerShell utilisez-vous? J'utilise la version 3.0.
John Leidegren
1
Je veux dire la dernière étape: cd c:\; "C:\fred\frag".Substring((pwd).Path.Length + 1). Ce n'est pas grave; juste quelque chose dont il faut être conscient.
dan-gph
Ah, bonne prise, nous pourrions corriger cela en ajoutant un appel de trim. Essayez cd c:\; "C:\fred\frag".Substring((pwd).Path.Trim('\').Length + 1). Mais ça devient un peu long.
John Leidegren
2
Ou utilisez simplement: $ ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath (". \ Nonexist \ foo.txt") Fonctionne également avec des chemins inexistants. "x0n" mérite le crédit pour ce btw. Comme il le note, cela se résout en PSPaths, pas en chemins flilesystem, mais si vous utilisez les chemins dans PowerShell, qui s'en soucie? stackoverflow.com/questions/3038337/…
Joe the Coder
106

Vous pouvez développer .. \ frag jusqu'à son chemin complet avec le chemin de résolution:

PS > resolve-path ..\frag 

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

[io.path]::Combine("fred\frog",(resolve-path ..\frag).path)
Shay Levy
la source
Que faire si votre chemin est C:\Windowsvs C:\Windows\ le même chemin mais deux résultats différents
Joe Phillips
2
Les paramètres de [io.path]::Combinesont inversés. Mieux encore, utilisez la Join-Pathcommande native PowerShell: Join-Path (Resolve-Path ..\frag).Path 'fred\frog'notez également que, au moins à partir de PowerShell v3, Resolve-Pathprend désormais en charge le -Relativecommutateur pour la résolution d'un chemin relatif au dossier actuel. Comme mentionné, Resolve-Pathne fonctionne qu'avec les chemins existants, contrairement à [IO.Path]::GetFullPath().
mklement0
25

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") )

qui donnent tous deux ce qui suit (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.

Charlie
la source
Cela se rapproche, mais quand je l'essaye, j'obtiens "H: \ fred \ frag" même si mon répertoire actuel est "C: \ scratch", ce qui est faux. (Il ne devrait pas faire cela selon le MSDN.) Cela m'a cependant donné une idée. Je vais l'ajouter comme réponse.
dan-gph
8
Votre problème est que vous devez définir le répertoire actuel dans .NET. [System.IO.Directory]::SetCurrentDirectory(((Get-Location -PSProvider FileSystem).ProviderPath))
JasonMArcher
2
Juste pour le dire explicitement: [IO.Path]::GetFullPath()contrairement au natif de PowerShell Resolve-Path, fonctionne également avec des chemins inexistants. Son inconvénient est la nécessité de synchroniser d'abord le dossier de travail de .NET avec celui de PS, comme le souligne @JasonMArcher.
mklement0
Join-Pathprovoque une exception si vous faites référence à un lecteur qui n'existe pas.
Tahir Hassan
20

La réponse acceptée a été d'une grande aide, mais elle ne «normalise» pas correctement un chemin absolu aussi. 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;
}
Sean Hanna
la source
Compte tenu de toutes les différentes solutions, celle-ci fonctionne pour tous les types de chemins. Par exemple, [IO.Path]::GetFullPath()ne détermine pas correctement le répertoire d'un nom de fichier simple.
Jari Turkia
10

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 être le répertoire de travail du processus.

De plus, 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 les chemins relatifs (ceux contenant des '..') en chemins absolus qualifiés par 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 un fichier ou un dossier 'fred' ou 'frag' est introuvable (et que 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($_)
        }
    }
}
Jason Stangroome
la source
2
Cela ne fonctionne pas pour les chemins inexistants où la lettre de lecteur n'existe pas, par exemple je n'ai pas de lecteur Q :. Get-AbsolutePath q:\foo\bar\..\bazéchoue même s'il s'agit d'un chemin valide. Eh bien, en fonction de votre définition d'un chemin valide. :-) FWIW, même le intégré Test-Path <path> -IsValidéchoue sur des chemins enracinés dans des lecteurs qui n'existent pas.
Keith Hill
2
@KeithHill En d'autres termes, PowerShell considère qu'un chemin sur une racine inexistante est invalide. Je pense que c'est assez raisonnable puisque PowerShell utilise la racine pour décider du type de fournisseur à utiliser lorsque vous travaillez avec. Par exemple, HKLM:\SOFTWAREest un chemin valide dans PowerShell, faisant référence à la SOFTWAREclé dans la ruche de registre de l'ordinateur local. Mais pour savoir s'il est valide, il doit déterminer quelles sont les règles des chemins de registre.
jpmc26
3

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

EDIT: C'est 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 ça:

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

Notez que cet extrait de code nécessite le chemin d'accès à la DLL. Il existe 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 pourrais utiliser, alors je viens de l'utiliser.

dan-gph
la source
Je ne sais pas pourquoi cela a obtenu un vote défavorable. Cette bibliothèque est vraiment bonne pour faire des manipulations de chemin. C'est ce que j'ai fini par utiliser dans mon projet.
dan-gph
Moins 2. Toujours perplexe. J'espère que les gens se rendent compte qu'il est facile d'utiliser les assemblys .Net de PowerShell.
dan-gph
Cela ne semble pas être la meilleure solution, mais elle est parfaitement valable.
JasonMArcher
@Jason, je ne me souviens pas des détails, mais à l'époque c'était la meilleure solution car c'était la seule qui résolvait mon problème particulier. Il est possible cependant que depuis lors, une autre solution meilleure soit venue.
dan-gph
2
avoir une DLL tierce est un gros inconvénient de cette solution
Louis Kottmann
1

Cela donne le chemin complet:

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

Cela donne le chemin relatif au répertoire courant:

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

Pour une raison quelconque, ils ne fonctionnent que s'il frags'agit d'un fichier, pas d'un fichier directory.

dan-gph
la source
1
gci est un alias pour get-childitem. Les enfants d'un répertoire sont son contenu. Remplacez gci par gi et cela devrait fonctionner pour les deux.
zdan
2
Get-Item a bien fonctionné. Mais encore une fois, cette approche nécessite que les dossiers existent.
Peter Lillevold
1

Créez une fonction. Cette fonction normalisera un chemin 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.

M.Hubers
la source
Notez que cela ne fonctionne pas pour les chemins comme somepaththing\.\filename.txtcar il conserve ce point unique
Mark Schultheiss
1

Si le chemin comprend un qualificatif (lettre de lecteur), 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 d'accès complet relatif au répertoire courant, 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
WileCau
la source
0

Si vous devez supprimer 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).

Dan R
la source
0

Les parties utiles des commentaires ici combinées de manière à unifier 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($_) }

production:

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
TNT
la source
-1

Eh bien, une solution serait:

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

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

EBGreen
la source
"Frag est-il un sous-dossier de grenouille?" Non. Le .. signifie monter d'un niveau. frag est un sous-dossier (ou un fichier) dans fred.
dan-gph