Wie normalisiere ich einen Pfad in PowerShell?

93

Ich habe zwei Wege:

fred\frog

und

..\frag

Ich kann sie in PowerShell folgendermaßen zusammenfügen:

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

Das gibt mir folgendes:

fred\frog\..\frag

Aber das will ich nicht. Ich möchte einen normalisierten Pfad ohne die doppelten Punkte, wie folgt:

fred\frag

Wie kann ich das bekommen?

dan-gph
quelle
1
Ist frag ein Unterordner von Frosch? Wenn nicht, erhalten Sie durch Kombinieren des Pfads fred \ frog \ frag. Wenn ja, ist das eine ganz andere Frage.
EBGreen

Antworten:

80

Sie können eine Kombination von verwenden pwd, Join-Pathund [System.IO.Path]::GetFullPatheinen vollständig qualifizierten gespreizten Weg zu bekommen.

Da cd( Set-Location) das aktuelle Arbeitsverzeichnis des Prozesses nicht ändert, kann die einfache Übergabe eines relativen Dateinamens an eine .NET-API, die den PowerShell-Kontext nicht versteht, unbeabsichtigte Nebenwirkungen haben, z. B. das Auflösen in einen Pfad, der auf der anfänglichen Arbeit basiert Verzeichnis (nicht Ihr aktueller Standort).

Was Sie tun, ist, dass Sie zuerst Ihren Weg qualifizieren:

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

Dies ergibt (angesichts meines aktuellen Standorts):

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

Mit einer absoluten Basis ist es sicher, die .NET-API aufzurufen GetFullPath:

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

Welches gibt Ihnen den voll qualifizierten Weg und mit dem ..entfernten:

C:\WINDOWS\system32\fred\frag

Es ist auch nicht kompliziert, persönlich, ich verachte die Lösungen, die von externen Skripten abhängen, es ist ein einfaches Problem, das ziemlich treffend durch Join-Pathund gelöst wird pwd( GetFullPathnur um es hübsch zu machen). Wenn Sie nur den relativen Teil behalten möchten, fügen Sie einfach hinzu .Substring((pwd).Path.Trim('\').Length + 1)und voila!

fred\frag

AKTUALISIEREN

Vielen Dank an @Dangph für den Hinweis auf den Randfall C:\.

John Leidegren
quelle
Der letzte Schritt funktioniert nicht, wenn pwd "C: \" ist. In diesem Fall bekomme ich "red \ frag".
Dan-Gph
@Dangph - Nicht sicher, ob ich verstehe, was du meinst, das oben genannte scheint gut zu funktionieren? Welche Version von PowerShell verwenden Sie? Ich benutze Version 3.0.
John Leidegren
1
Ich meine den letzten Schritt : cd c:\; "C:\fred\frag".Substring((pwd).Path.Length + 1). Es ist keine große Sache; nur etwas zu beachten.
Dan-Gph
Ah, guter Fang, wir könnten das beheben, indem wir einen Trimmaufruf hinzufügen. Versuchen Sie es cd c:\; "C:\fred\frag".Substring((pwd).Path.Trim('\').Length + 1). Es wird allerdings etwas lang.
John Leidegren
2
Oder verwenden Sie einfach: $ ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath (". \ Nonexist \ foo.txt") Funktioniert auch mit nicht vorhandenen Pfaden. "x0n" verdient die Anerkennung für diese übrigens. Wie er bemerkt, wird es in PSPaths aufgelöst, nicht in Flilesystem-Pfade, aber wenn Sie die Pfade in PowerShell verwenden, wen interessiert das? stackoverflow.com/questions/3038337/…
Joe the Coder
103

Sie können .. \ frag mit dem Auflösungspfad auf den vollständigen Pfad erweitern:

PS > resolve-path ..\frag 

Versuchen Sie, den Pfad mit der Methode comb () zu normalisieren:

[io.path]::Combine("fred\frog",(resolve-path ..\frag).path)
Shay Levy
quelle
Was ist, wenn Ihr Pfad C:\Windowsgegen C:\Windows\ denselben Pfad ist, aber zwei unterschiedliche Ergebnisse
Joe Phillips
2
Die Parameter für [io.path]::Combinesind umgekehrt. Verwenden Sie noch besser den nativen Join-PathPowerShell-Befehl: Join-Path (Resolve-Path ..\frag).Path 'fred\frog'Beachten Sie außerdem, dass zumindest ab PowerShell v3 Resolve-Pathjetzt der -RelativeSchalter zum Auflösen in einen Pfad relativ zum aktuellen Ordner unterstützt wird. Wie bereits erwähnt, Resolve-Pathfunktioniert im Gegensatz dazu nur mit vorhandenen Pfaden [IO.Path]::GetFullPath().
mklement0
25

Sie können auch Path.GetFullPath verwenden , obwohl dies (wie bei der Antwort von Dan R) den gesamten Pfad angibt . Die Verwendung wäre wie folgt:

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

oder interessanter

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

Beide ergeben Folgendes (vorausgesetzt, Ihr aktuelles Verzeichnis ist D: \):

D:\fred\frag

Beachten Sie, dass diese Methode nicht versucht festzustellen, ob Fred oder Frag tatsächlich vorhanden sind.

Charlie
quelle
Das kommt näher, aber wenn ich es versuche, bekomme ich "H: \ fred \ frag", obwohl mein aktuelles Verzeichnis "C: \ Scratch" ist, was falsch ist. (Laut MSDN sollte das nicht so sein.) Es gab mir jedoch eine Idee. Ich werde es als Antwort hinzufügen.
Dan-Gph
8
Ihr Problem ist, dass Sie das aktuelle Verzeichnis in .NET festlegen müssen. [System.IO.Directory]::SetCurrentDirectory(((Get-Location -PSProvider FileSystem).ProviderPath))
JasonMArcher
2
Nur um es explizit [IO.Path]::GetFullPath()auszudrücken : Im Gegensatz zu PowerShell Resolve-Pathfunktioniert nativ auch mit nicht vorhandenen Pfaden. Der Nachteil ist, dass der Arbeitsordner von .NET zuerst mit dem von PS synchronisiert werden muss, wie @JasonMArcher hervorhebt.
mklement0
Join-Pathverursacht eine Ausnahme, wenn auf ein Laufwerk verwiesen wird, das nicht vorhanden ist.
Tahir Hassan
20

Die akzeptierte Antwort war eine große Hilfe, aber sie "normalisiert" auch einen absoluten Pfad nicht richtig. Finden Sie unten meine abgeleitete Arbeit, die sowohl absolute als auch relative Pfade normalisiert.

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
quelle
Angesichts der verschiedenen Lösungen funktioniert diese für alle Arten von Pfaden. Beispielsweise [IO.Path]::GetFullPath()wird das Verzeichnis für einen einfachen Dateinamen nicht korrekt ermittelt.
Jari Turkia
10

Alle Funktionen zur Manipulation von Nicht-PowerShell-Pfaden (z. B. in System.IO.Path) sind in PowerShell nicht zuverlässig, da das PowerShell-Anbietermodell es ermöglicht, dass der aktuelle Pfad von PowerShell von dem abweicht, was Windows für das Arbeitsverzeichnis des Prozesses hält.

Wie Sie vielleicht bereits festgestellt haben, sind die Cmdlets Resolve-Path und Convert-Path von PowerShell nützlich, um relative Pfade (solche mit '..') in Laufwerk-qualifizierte absolute Pfade zu konvertieren. Sie schlagen jedoch fehl, wenn der angegebene Pfad nicht vorhanden ist.

Das folgende sehr einfache Cmdlet sollte für nicht vorhandene Pfade funktionieren. Es konvertiert 'fred \ frog \ .. \ frag' in 'd: \ fred \ frag', auch wenn eine 'fred'- oder' frag'-Datei oder ein Ordner nicht gefunden werden kann (und das aktuelle PowerShell-Laufwerk 'd:' ist). .

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

    process {
        $Path | ForEach-Object {
            $PSCmdlet.SessionState.Path.GetUnresolvedProviderPathFromPSPath($_)
        }
    }
}
Jason Stangroome
quelle
2
Dies funktioniert nicht für nicht vorhandene Pfade, in denen der Laufwerksbuchstabe nicht vorhanden ist, z. B. ich habe kein Q: -Laufwerk. Get-AbsolutePath q:\foo\bar\..\bazschlägt fehl, obwohl es sich um einen gültigen Pfad handelt. Nun, abhängig von Ihrer Definition eines gültigen Pfades. :-) FWIW, sogar die integrierte Funktion Test-Path <path> -IsValidschlägt auf Pfaden fehl, die auf nicht vorhandenen Laufwerken basieren .
Keith Hill
2
@KeithHill Mit anderen Worten, PowerShell betrachtet einen Pfad auf einem nicht vorhandenen Stamm als ungültig. Ich denke, das ist ziemlich vernünftig, da PowerShell das Stammverzeichnis verwendet, um zu entscheiden, welche Art von Anbieter bei der Arbeit verwendet werden soll. Ist HKLM:\SOFTWAREbeispielsweise ein gültiger Pfad in PowerShell, der auf den SOFTWARESchlüssel in der Registrierungsstruktur des lokalen Computers verweist . Um herauszufinden, ob es gültig ist, müssen die Regeln für Registrierungspfade ermittelt werden.
jpmc26
3

Diese Bibliothek ist gut: NDepend.Helpers.FileDirectoryPath .

EDIT: Das habe ich mir ausgedacht :

[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
}

Nennen Sie es so:

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

Beachten Sie, dass für dieses Snippet der Pfad zur DLL erforderlich ist. Es gibt einen Trick, mit dem Sie den Ordner finden können, der das aktuell ausgeführte Skript enthält, aber in meinem Fall hatte ich eine Umgebungsvariable, die ich verwenden konnte, also habe ich diese einfach verwendet.

dan-gph
quelle
Ich weiß nicht, warum das abgelehnt wurde. Diese Bibliothek ist wirklich gut für Pfadmanipulationen. Es ist das, was ich letztendlich in meinem Projekt verwendet habe.
dan-gph
Minus 2. Immer noch verwirrt. Ich hoffe, dass die Leute erkennen, dass es einfach ist, .NET-Assemblys von PowerShell zu verwenden.
Dan-Gph
Dies scheint nicht die beste Lösung zu sein, ist aber vollkommen gültig.
JasonMArcher
@ Jason, ich erinnere mich nicht an die Details, aber zu der Zeit war es die beste Lösung, weil es die einzige war, die mein spezielles Problem löste. Es ist jedoch möglich, dass seitdem eine andere, bessere Lösung gefunden wurde.
Dan-Gph
2
Eine DLL eines Drittanbieters ist ein großer Nachteil dieser Lösung
Louis Kottmann
1

Dies gibt den vollständigen Weg:

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

Dies gibt den Pfad relativ zum aktuellen Verzeichnis an:

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

Aus irgendeinem Grund funktionieren sie nur, wenn frages sich um eine Datei handelt, nicht um eine directory.

dan-gph
quelle
1
gci ist ein Alias ​​für get-childitem. Die untergeordneten Elemente eines Verzeichnisses sind dessen Inhalt. Ersetzen Sie gci durch gi und es sollte für beide funktionieren.
zdan
2
Get-Item hat gut funktioniert. Dieser Ansatz setzt jedoch voraus, dass die Ordner vorhanden sind.
Peter Lillevold
1

Erstellen Sie eine Funktion. Diese Funktion normalisiert einen Pfad, der auf Ihrem System nicht vorhanden ist, und fügt keine Laufwerksbuchstaben hinzu.

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'

Vielen Dank an Oliver Schadlich für die Hilfe bei der RegEx.

M.Hubers
quelle
Beachten Sie, dass dies nicht für Pfade funktioniert, somepaththing\.\filename.txtda dieser einzelne Punkt beibehalten wird
Mark Schultheiss,
1

Wenn der Pfad ein Qualifikationsmerkmal (Laufwerksbuchstabe) enthält, lautet die Antwort von x0n auf Powershell: Pfad auflösen, der möglicherweise nicht vorhanden ist? normalisiert den Pfad. Wenn der Pfad das Qualifikationsmerkmal nicht enthält, wird er weiterhin normalisiert, gibt jedoch den vollständig qualifizierten Pfad relativ zum aktuellen Verzeichnis zurück, was möglicherweise nicht Ihren Wünschen entspricht.

$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
quelle
0

Wenn Sie den Teil .. entfernen müssen, können Sie ein System.IO.DirectoryInfo-Objekt verwenden. Verwenden Sie im Konstruktor 'fred \ frog .. \ frag'. Die FullName-Eigenschaft gibt Ihnen den normalisierten Verzeichnisnamen.

Der einzige Nachteil ist, dass Sie den gesamten Pfad erhalten (z. B. c: \ test \ fred \ frag).

Dan R.
quelle
0

Die zweckmäßigen Teile der Kommentare hier wurden so kombiniert, dass sie relative und absolute Pfade vereinen:

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

Einige Beispiele:

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

Ausgabe:

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
quelle
-1

Ein Weg wäre:

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

Warten Sie, vielleicht verstehe ich die Frage falsch. Ist frag in Ihrem Beispiel ein Unterordner von Frosch?

EBGreen
quelle
"Ist frag ein Unterordner von Frosch?" Nein. Das .. bedeutet eine Ebene höher. frag ist ein Unterordner (oder eine Datei) in fred.
Dan-Gph