Jak znormalizować ścieżkę w PowerShell?

96

Mam dwie ścieżki:

fred\frog

i

..\frag

Mogę połączyć je razem w PowerShell w ten sposób:

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

To daje mi to:

fred\frog\..\frag

Ale ja tego nie chcę. Chcę znormalizowanej ścieżki bez podwójnych kropek, na przykład:

fred\frag

Jak mogę to uzyskać?

dan-gph
źródło
1
Czy frag jest podfolderem żaby? Jeśli nie, połączenie ścieżki dałoby ci fred \ frog \ frag. Jeśli tak, to jest to zupełnie inna kwestia.
EBGreen

Odpowiedzi:

83

Można użyć kombinacji pwd, Join-Pathi [System.IO.Path]::GetFullPathaby uzyskać pełną rozszerzoną ścieżkę.

Ponieważ cd( Set-Location) nie zmienia bieżącego katalogu roboczego procesu, po prostu przekazanie względnej nazwy pliku do interfejsu API .NET, które nie rozumie kontekstu programu PowerShell, może mieć niezamierzone skutki uboczne, takie jak rozpoznawanie ścieżki na podstawie początkowej pracy katalog (nie bieżąca lokalizacja).

Najpierw kwalifikujesz swoją ścieżkę:

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

To daje (biorąc pod uwagę moją obecną lokalizację):

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

Mając absolutną podstawę, można bezpiecznie wywołać interfejs .NET API GetFullPath:

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

Co daje w pełni kwalifikowaną ścieżkę i ..usunięte:

C:\WINDOWS\system32\fred\frag

To też nie jest skomplikowane, osobiście gardzę rozwiązaniami, które w tym zakresie zależą od zewnętrznych skryptów, jest to prosty problem rozwiązany dość trafnie przez Join-Pathi pwd( GetFullPathma tylko uczynić go ładnym). Jeśli chcesz zachować tylko względną część , po prostu dodaj .Substring((pwd).Path.Trim('\').Length + 1)i voila!

fred\frag

AKTUALIZACJA

Podziękowania dla @Dangph za wskazanie C:\przypadku krawędzi.

John Leidegren
źródło
Ostatni krok nie działa, jeśli pwd to „C: \”. W takim przypadku otrzymuję „czerwony \ frag”.
dan-gph
@Dangph - Nie jestem pewien, czy rozumiem, co masz na myśli, wydaje się, że powyższe działa dobrze? Jakiej wersji programu PowerShell używasz? Używam wersji 3.0.
John Leidegren
1
Mam na myśli ostatni krok: cd c:\; "C:\fred\frag".Substring((pwd).Path.Length + 1). To nic wielkiego; po prostu coś, o czym należy pamiętać.
dan-gph
Ach, dobry chwyt, możemy to naprawić, dodając wywołanie trymowania. Spróbuj cd c:\; "C:\fred\frag".Substring((pwd).Path.Trim('\').Length + 1). Ale robi się trochę długo.
John Leidegren
2
Lub po prostu użyj: $ ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath (". \ Nonexist \ foo.txt") Działa również z nieistniejącymi ścieżkami. Przy okazji, "x0n" zasługuje na uznanie. Jak zauważa, rozwiązuje się to do ścieżek PSPath, a nie ścieżek systemu flilesystemu, ale jeśli używasz ścieżek w PowerShell, kogo to obchodzi? stackoverflow.com/questions/3038337/…
Joe the Coder
106

Możesz rozwinąć .. \ frag do pełnej ścieżki za pomocą rozwiązania-path:

PS > resolve-path ..\frag 

Spróbuj znormalizować ścieżkę za pomocą metody connect ():

[io.path]::Combine("fred\frog",(resolve-path ..\frag).path)
Shay Levy
źródło
Co jeśli twoja ścieżka jest C:\Windowsprzeciwko C:\Windows\ tej samej ścieżce, ale dwa różne wyniki
Joe Phillips
2
Parametry dla [io.path]::Combinesą odwrócone. Jeszcze lepiej, użyj natywnego Join-Pathpolecenia programu PowerShell: należy Join-Path (Resolve-Path ..\frag).Path 'fred\frog'również zauważyć, że przynajmniej od wersji PowerShell v3 Resolve-Pathobsługuje teraz -Relativeprzełącznik do rozpoznawania ścieżki względem bieżącego folderu. Jak wspomniano, Resolve-Pathdziała tylko z istniejącymi ścieżkami, w przeciwieństwie do [IO.Path]::GetFullPath().
mklement0
25

Możesz także użyć Path.GetFullPath , chociaż (podobnie jak w przypadku odpowiedzi Dana R.) da ci to całą ścieżkę. Sposób użycia byłby następujący:

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

lub co ciekawsze

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

oba dają następujące wyniki (zakładając, że twój bieżący katalog to D: \):

D:\fred\frag

Zauważ, że ta metoda nie próbuje określić, czy fred lub frag rzeczywiście istnieją.

Charlie
źródło
To się zbliża, ale kiedy próbuję, otrzymuję „H: \ fred \ frag”, mimo że mój bieżący katalog to „C: \ scratch”, co jest błędne. (Według MSDN nie powinno to robić). Dało mi to jednak pomysł. Dodam to jako odpowiedź.
dan-gph
8
Twój problem polega na tym, że musisz ustawić bieżący katalog w .NET. [System.IO.Directory]::SetCurrentDirectory(((Get-Location -PSProvider FileSystem).ProviderPath))
JasonMArcher,
2
Mówiąc wprost: [IO.Path]::GetFullPath()w przeciwieństwie do natywnego programu PowerShell Resolve-Pathdziała również z nieistniejącymi ścieżkami. Jego wadą jest konieczność zsynchronizowania najpierw folderu roboczego .NET z PS, jak wskazuje @JasonMArcher.
mklement0
Join-Pathpowoduje wyjątek, jeśli odnoszą się do dysku, który nie istnieje.
Tahir Hassan
20

Przyjęta odpowiedź była bardzo pomocna, ale nie „normalizuje” również właściwie ścieżki absolutnej. Znajdź poniżej moją pracę pochodną, ​​która normalizuje zarówno ścieżki bezwzględne, jak i względne.

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
źródło
Biorąc pod uwagę różne rozwiązania, ten działa na wszystkich różnych typach ścieżek. Na przykład [IO.Path]::GetFullPath()nie określa poprawnie katalogu dla zwykłej nazwy pliku.
Jari Turkia
10

Wszelkie funkcje manipulacji ścieżkami inne niż PowerShell (takie jak te w System.IO.Path) nie będą niezawodne w programie PowerShell, ponieważ model dostawcy programu PowerShell pozwala, aby bieżąca ścieżka programu PowerShell różniła się od tego, co system Windows uważa za katalog roboczy procesu.

Ponadto, jak być może już odkryłeś, polecenia cmdlet programu PowerShell Resolve-Path i Convert-Path są przydatne do konwertowania ścieżek względnych (zawierających „..”) na ścieżki bezwzględne kwalifikowane przez dysk, ale kończą się niepowodzeniem, jeśli ścieżka, do której się odwołujesz, nie istnieje.

Poniższe bardzo proste polecenie cmdlet powinno działać w przypadku nieistniejących ścieżek. Konwertuje „fred \ frog \ .. \ frag” na „d: \ fred \ frag”, nawet jeśli nie można znaleźć pliku lub folderu „fred” lub „frag” (a bieżący dysk PowerShell to „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
źródło
2
Nie działa to w przypadku nieistniejących ścieżek, w których litera dysku nie istnieje, np. Nie mam dysku Q:. Get-AbsolutePath q:\foo\bar\..\bazkończy się niepowodzeniem, mimo że jest to prawidłowa ścieżka. Cóż, w zależności od twojej definicji ważnej ścieżki. :-) FWIW, nawet wbudowany Test-Path <path> -IsValidnie działa na ścieżkach zakorzenionych w dyskach, które nie istnieją.
Keith Hill
2
@KeithHill Innymi słowy, PowerShell uważa ścieżkę na nieistniejącym katalogu głównym za nieprawidłową. Myślę, że jest to całkiem rozsądne, ponieważ PowerShell używa roota, aby zdecydować, jakiego rodzaju dostawcy użyć podczas pracy z nim. Na przykład HKLM:\SOFTWAREjest to prawidłowa ścieżka w programie PowerShell, odnosząca się do SOFTWAREklucza w gałęzi rejestru komputera lokalnego. Ale aby dowiedzieć się, czy jest prawidłowy, musi dowiedzieć się, jakie są reguły dotyczące ścieżek rejestru.
jpmc26
3

Ta biblioteka jest dobra: NDepend.Helpers.FileDirectoryPath .

EDYCJA: Oto, co wymyśliłem:

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

Nazwij to tak:

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

Należy zauważyć, że ten fragment kodu wymaga ścieżki do biblioteki DLL. Istnieje sztuczka, której możesz użyć, aby znaleźć folder zawierający aktualnie wykonywany skrypt, ale w moim przypadku miałem zmienną środowiskową, której mogłem użyć, więc po prostu jej użyłem.

dan-gph
źródło
Nie wiem, dlaczego otrzymałem głos przeciw. Ta biblioteka naprawdę jest dobra do manipulacji ścieżkami. Właśnie tego użyłem w moim projekcie.
dan-gph
Minus 2. Wciąż zdziwiony. Mam nadzieję, że ludzie zdają sobie sprawę, że jest to łatwe w użyciu zestawów .Net z PowerShell.
dan-gph
Nie wydaje się to najlepszym rozwiązaniem, ale jest całkowicie uzasadnione.
JasonMArcher
@Jason, nie pamiętam szczegółów, ale w tamtym czasie było to najlepsze rozwiązanie, ponieważ było jedynym, które rozwiązało mój konkretny problem. Możliwe jednak, że od tego czasu pojawiło się inne, lepsze rozwiązanie.
dan-gph
2
posiadanie biblioteki DLL innej firmy jest dużą wadą tego rozwiązania
Louis Kottmann,
1

To daje pełną ścieżkę:

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

To daje ścieżkę względną do bieżącego katalogu:

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

Z jakiegoś powodu działają tylko wtedy, gdy fragjest to plik, a nie directory.

dan-gph
źródło
1
gci jest aliasem dla get-childitem. Elementami podrzędnymi katalogu są jego zawartość. Zamień gci na gi i powinno działać w obu przypadkach.
zdan
2
Get-Item działał dobrze. Ale znowu, to podejście wymaga istnienia folderów.
Peter Lillevold
1

Utwórz funkcję. Ta funkcja znormalizuje ścieżkę, której nie ma w systemie, a także nie doda liter dysków.

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

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

Dawny:

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

Podziękowania dla Olivera Schadlicha za pomoc w RegEx.

M.Hubers
źródło
Zauważ, że to nie działa dla ścieżek, takich somepaththing\.\filename.txtjak to, że zachowuje tę pojedynczą kropkę
Mark Schultheiss
1

Jeśli ścieżka zawiera kwalifikator (literę dysku), to odpowiedź x0n na Powershell: rozwiązać ścieżkę, która może nie istnieć? znormalizuje ścieżkę. Jeśli ścieżka nie zawiera kwalifikatora, nadal będzie znormalizowana, ale zwróci w pełni kwalifikowaną ścieżkę względem bieżącego katalogu, co może nie być tym, czego chcesz.

$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
źródło
0

Jeśli chcesz pozbyć się części .., możesz użyć obiektu System.IO.DirectoryInfo. Użyj 'fred \ frog .. \ frag' w konstruktorze. Właściwość FullName poda znormalizowaną nazwę katalogu.

Jedyną wadą jest to, że poda Ci całą ścieżkę (np. C: \ test \ fred \ frag).

Dan R.
źródło
0

Celowe części komentarzy zostały połączone w taki sposób, że ujednoliciły ścieżki względne i bezwzględne:

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

Niektóre próbki:

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

wynik:

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
źródło
-1

Cóż, jednym ze sposobów byłoby:

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

Czekaj, może źle zrozumiałem pytanie. Czy w twoim przykładzie frag jest podfolderem żaby?

EBGreen
źródło
„Czy frag jest podfolderem żaby?” Nie .. oznacza przejście o jeden poziom wyżej. frag to podfolder (lub plik) we fred.
dan-gph