Come normalizzare un percorso in PowerShell?


94

Ho due percorsi:

fred\frog

e

..\frag

Posso unirli insieme in PowerShell in questo modo:

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

Questo mi dà questo:

fred\frog\..\frag

Ma non lo voglio. Voglio un percorso normalizzato senza i doppi punti, come questo:

fred\frag

Come posso ottenerlo?


1
Frag è una sottocartella di frog? In caso contrario, combinando il percorso si otterrebbe fred \ frog \ frag. Se è così allora è una domanda molto diversa.
EBGreen

Risposte:


81

È possibile utilizzare una combinazione di pwd, Join-Pathe [System.IO.Path]::GetFullPathper ottenere un percorso espanso completo.

Poiché cd( Set-Location) non modifica la directory di lavoro corrente del processo, il semplice passaggio di un nome file relativo a un'API .NET che non comprende il contesto di PowerShell, può avere effetti collaterali indesiderati, come la risoluzione di un percorso basato sul lavoro iniziale directory (non la posizione corrente).

Quello che fai è prima qualificare il tuo percorso:

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

Questo produce (data la mia posizione attuale):

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

Con una base assoluta, è sicuro chiamare l'API .NET GetFullPath:

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

Che ti dà il percorso completo e con il ..rimosso:

C:\WINDOWS\system32\fred\frag

Non è nemmeno complicato, personalmente, disdegno le soluzioni che dipendono da script esterni per questo, è un problema semplice risolto piuttosto opportunamente da Join-Pathe pwd( GetFullPathè solo per renderlo carino). Se vuoi solo mantenere solo la parte relativa , aggiungi .Substring((pwd).Path.Trim('\').Length + 1)e voilà!

fred\frag

AGGIORNARE

Grazie a @Dangph per aver sottolineato il C:\caso limite.


L'ultimo passaggio non funziona se pwd è "C: \". In tal caso ottengo "red \ frag".
dan-gph

@Dangph - Non sono sicuro di aver capito cosa intendi, quanto sopra sembra funzionare bene? Quale versione di PowerShell stai usando? Sto usando la versione 3.0.
John Leidegren

1
Voglio dire l'ultimo passo: cd c:\; "C:\fred\frag".Substring((pwd).Path.Length + 1). Non è un grosso problema; solo qualcosa di cui essere consapevoli.
dan-gph

Ah, buona cattura, potremmo risolverlo aggiungendo una chiamata di assetto. Prova cd c:\; "C:\fred\frag".Substring((pwd).Path.Trim('\').Length + 1). Sta diventando un po 'lungo però.
John Leidegren

2
Oppure usa semplicemente: $ ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath (". \ Nonexist \ foo.txt") Funziona anche con percorsi inesistenti. "x0n" merita il merito di questo btw. Come osserva, si risolve in PSPaths, non in percorsi flilesystem, ma se stai usando i percorsi in PowerShell, chi se ne frega?stackoverflow.com/questions/3038337/…
Joe the Coder

105

Puoi espandere .. \ frag al suo percorso completo con il percorso di risoluzione:

PS > resolve-path ..\frag 

Prova a normalizzare il percorso usando il metodo combination ():

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

E se il tuo percorso è C:\Windowscontro C:\Windows\ lo stesso percorso ma due risultati diversi
Joe Phillips

2
I parametri per [io.path]::Combinesono invertiti. Meglio ancora, usa il Join-Pathcomando nativo di PowerShell: Join-Path (Resolve-Path ..\frag).Path 'fred\frog'tieni presente inoltre che, almeno a partire da PowerShell v3, Resolve-Pathora supporta lo -Relativeswitch per la risoluzione in un percorso relativo alla cartella corrente. Come accennato, Resolve-Pathfunziona solo con percorsi esistenti, a differenza di [IO.Path]::GetFullPath().
mklement0

25

Puoi anche usare Path.GetFullPath , sebbene (come con la risposta di Dan R) questo ti darà l'intero percorso. L'utilizzo sarebbe il seguente:

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

o più interessante

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

entrambi producono quanto segue (assumendo che la directory corrente sia D: \):

D:\fred\frag

Nota che questo metodo non tenta di determinare se fred o frag esistono effettivamente.


Ci si sta avvicinando, ma quando lo provo ottengo "H: \ fred \ frag" anche se la mia directory corrente è "C: \ scratch", il che è sbagliato. (Non dovrebbe farlo secondo MSDN.) Tuttavia mi ha dato un'idea. Lo aggiungerò come risposta.
dan-gph

8
Il tuo problema è che devi impostare la directory corrente in .NET. [System.IO.Directory]::SetCurrentDirectory(((Get-Location -PSProvider FileSystem).ProviderPath))
JasonMArcher

2
Giusto per dirlo esplicitamente [IO.Path]::GetFullPath():, a differenza del nativo di PowerShell Resolve-Path, funziona anche con percorsi inesistenti. Il suo svantaggio è la necessità di sincronizzare prima la cartella di lavoro di .NET con PS, come sottolinea @JasonMArcher.
mklement0

Join-Pathprovoca un'eccezione se si fa riferimento a un'unità che non esiste.
Tahir Hassan

20

La risposta accettata è stata di grande aiuto, tuttavia non "normalizza" correttamente anche un percorso assoluto. Trova di seguito il mio lavoro derivato che normalizza i percorsi sia assoluti che relativi.

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

Date tutte le diverse soluzioni, questa funziona per tutti i diversi tipi di percorso. Ad esempio [IO.Path]::GetFullPath(), non determina correttamente la directory per un nome di file semplice.
Jari Turkia

10

Qualsiasi funzione di manipolazione del percorso non PowerShell (come quelle in System.IO.Path) non sarà affidabile da PowerShell perché il modello di provider di PowerShell consente al percorso corrente di PowerShell di differire da ciò che Windows pensa che sia la directory di lavoro del processo.

Inoltre, come potresti aver già scoperto, i cmdlet Resolve-Path e Convert-Path di PowerShell sono utili per convertire i percorsi relativi (quelli contenenti "..") in percorsi assoluti qualificati per l'unità, ma falliscono se il percorso a cui si fa riferimento non esiste.

Il seguente cmdlet molto semplice dovrebbe funzionare per percorsi inesistenti. Convertirà "fred \ frog \ .. \ frag" in "d: \ fred \ frag" anche se non è possibile trovare un file o una cartella "fred" o "frag" (e l'unità PowerShell corrente è "d:") .

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

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

2
Questo non funziona per percorsi inesistenti in cui la lettera di unità non esiste, ad esempio non ho unità Q :. Get-AbsolutePath q:\foo\bar\..\bazfallisce anche se è un percorso valido. Bene, a seconda della tua definizione di un percorso valido. :-) FWIW, anche il built-in Test-Path <path> -IsValidfallisce su percorsi radicati in unità che non esistono.
Keith Hill

2
@KeithHill In altre parole, PowerShell considera non valido un percorso su una radice inesistente. Penso che sia abbastanza ragionevole poiché PowerShell utilizza la radice per decidere quale tipo di provider utilizzare quando si lavora con esso. Ad esempio, HKLM:\SOFTWAREè un percorso valido in PowerShell, che fa riferimento alla SOFTWAREchiave nell'hive del registro del computer locale. Ma per capire se è valido, è necessario capire quali sono le regole per i percorsi del registro.
jpmc26

3

Questa libreria è buona: NDepend.Helpers.FileDirectoryPath .

EDIT: Questo è quello che mi è venuto in mente:

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

Chiamalo così:

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

Tieni presente che questo frammento richiede il percorso della DLL. C'è un trucco che puoi usare per trovare la cartella contenente lo script attualmente in esecuzione, ma nel mio caso avevo una variabile d'ambiente che potevo usare, quindi l'ho usata.


Non so perché abbia ottenuto un voto negativo. Quella libreria è davvero utile per manipolare i percorsi. È quello che ho finito per usare nel mio progetto.
dan-gph

Meno 2. Ancora perplesso. Spero che le persone si rendano conto che è facile usare gli assembly .Net da PowerShell.
dan-gph

Questa non sembra la soluzione migliore, ma è perfettamente valida.
JasonMArcher

@ Jason, non ricordo i dettagli, ma all'epoca era la soluzione migliore perché era l'unica che risolveva il mio particolare problema. È possibile tuttavia che da allora sia arrivata un'altra soluzione migliore.
dan-gph

2
avere una DLL di terze parti è un grosso svantaggio di questa soluzione
Louis Kottmann

1

Questo dà il percorso completo:

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

Questo fornisce il percorso relativo alla directory corrente:

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

Per qualche motivo funzionano solo se fragè un file, non un file directory.


1
gci è un alias per get-childitem. I figli di una directory sono i suoi contenuti. Sostituisci gci con gi e dovrebbe funzionare per entrambi.
zdan

2
Get-Item ha funzionato bene. Ma ancora una volta, questo approccio richiede che le cartelle esistano.
Peter Lillevold

1

Crea una funzione. Questa funzione normalizzerà un percorso che non esiste sul tuo sistema e non aggiungerà lettere di unità.

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'

Grazie a Oliver Schadlich per l'aiuto nelle RegEx.


Nota che questo non funziona per percorsi come somepaththing\.\filename.txtil fatto che mantiene quel singolo punto
Mark Schultheiss

1

Se il percorso include un qualificatore (lettera di unità), la risposta di x0n a Powershell: risolvere il percorso che potrebbe non esistere? normalizzerà il percorso. Se il percorso non include il qualificatore, verrà comunque normalizzato ma restituirà il percorso completo relativo alla directory corrente, che potrebbe non essere quello desiderato.

$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

0

Se è necessario eliminare la parte .., è possibile utilizzare un oggetto System.IO.DirectoryInfo. Usa "fred \ frog .. \ frag" nel costruttore. La proprietà FullName ti darà il nome della directory normalizzato.

L'unico inconveniente è che ti darà l'intero percorso (ad esempio c: \ test \ fred \ frag).


0

Le parti utili dei commenti qui combinate in modo tale da unificare i percorsi relativi e assoluti:

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

Alcuni esempi:

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

produzione:

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

-1

Bene, un modo sarebbe:

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

Aspetta, forse ho frainteso la domanda. Nel tuo esempio, frag è una sottocartella di frog?


"frag è una sottocartella di frog?" No. Il .. significa salire di un livello. frag è una sottocartella (o un file) in fred.
dan-gph
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.