Bagaimana cara menormalkan jalur di PowerShell?

96

Saya memiliki dua jalur:

fred\frog

dan

..\frag

Saya dapat menggabungkan mereka bersama di PowerShell seperti ini:

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

Itu memberi saya ini:

fred\frog\..\frag

Tapi saya tidak menginginkan itu. Saya ingin jalur yang dinormalisasi tanpa titik ganda, seperti ini:

fred\frag

Bagaimana saya bisa mendapatkannya?

dan-gph
sumber
1
Apakah frag merupakan subfolder dari katak? Jika tidak, maka menggabungkan jalur akan membuat Anda fred \ frog \ frag. Jika demikian maka itu adalah pertanyaan yang sangat berbeda.
EBGreen

Jawaban:

83

Anda dapat menggunakan kombinasi dari pwd, Join-Pathdan [System.IO.Path]::GetFullPathuntuk mendapatkan jalur yang diperluas sepenuhnya.

Karena cd( Set-Location) tidak mengubah proses direktori kerja saat ini, cukup meneruskan nama file relatif ke .NET API yang tidak memahami konteks PowerShell, dapat memiliki efek samping yang tidak diinginkan, seperti menyelesaikan ke jalur berdasarkan pekerjaan awal direktori (bukan lokasi Anda saat ini).

Apa yang Anda lakukan adalah pertama-tama Anda memenuhi syarat untuk jalur Anda:

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

Hasil ini (mengingat lokasi saya saat ini):

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

Dengan basis absolut, aman untuk memanggil .NET API GetFullPath:

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

Yang memberi Anda jalur yang sepenuhnya memenuhi syarat dan dengan yang ..dihapus:

C:\WINDOWS\system32\fred\frag

Ini juga tidak rumit, secara pribadi, saya meremehkan solusi yang bergantung pada skrip eksternal untuk ini, ini masalah sederhana diselesaikan dengan tepat oleh Join-Pathdan pwd( GetFullPathhanya untuk membuatnya cantik). Jika Anda hanya ingin menyimpan bagian yang relatif saja , Anda tinggal menambahkan .Substring((pwd).Path.Trim('\').Length + 1)dan voila!

fred\frag

MEMPERBARUI

Terima kasih kepada @Dangph karena telah menunjukkan C:\case edge.

John Leidegren
sumber
Langkah terakhir tidak berhasil jika pwd adalah "C: \". Dalam hal ini saya mendapatkan "red \ frag".
dan-gph
@Dangph - Tidak yakin saya mengerti apa yang Anda maksud, di atas tampaknya berfungsi dengan baik? Versi PowerShell apa yang Anda gunakan? Saya menggunakan versi 3.0.
John Leidegren
1
Maksudku langkah terakhir: cd c:\; "C:\fred\frag".Substring((pwd).Path.Length + 1). Itu bukan masalah besar; hanya sesuatu yang harus diperhatikan.
dan-gph
Ah, tangkapan bagus, kita bisa memperbaikinya dengan menambahkan panggilan trim. Coba cd c:\; "C:\fred\frag".Substring((pwd).Path.Trim('\').Length + 1). Ini agak lama.
John Leidegren
2
Atau, cukup gunakan: $ ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath (". \ Nonexist \ foo.txt") Bekerja dengan jalur yang tidak ada juga. "x0n" layak mendapatkan pujian untuk btw ini. Seperti yang dia catat, itu menyelesaikan ke PSPath, bukan jalur sistem fliles, tetapi jika Anda menggunakan jalur di PowerShell, siapa yang peduli? stackoverflow.com/questions/3038337/…
Joe the Coder
106

Anda dapat memperluas .. \ frag ke jalur lengkapnya dengan jalur penyelesaian:

PS > resolve-path ..\frag 

Cobalah untuk menormalkan jalur menggunakan metode gabung ():

[io.path]::Combine("fred\frog",(resolve-path ..\frag).path)
Shay Levy
sumber
Bagaimana jika jalur Anda C:\Windowsvs C:\Windows\ jalur yang sama tetapi dua hasil yang berbeda
Joe Phillips
2
Parameter untuk [io.path]::Combinedibalik. Lebih baik lagi, gunakan Join-Pathperintah PowerShell asli : Join-Path (Resolve-Path ..\frag).Path 'fred\frog'Perhatikan juga bahwa, setidaknya pada PowerShell v3, Resolve-Pathsekarang mendukung -Relativesakelar untuk menyelesaikan ke jalur yang terkait dengan folder saat ini. Seperti yang disebutkan, Resolve-Pathhanya berfungsi dengan jalur yang ada, tidak seperti [IO.Path]::GetFullPath().
mklement0
25

Anda juga bisa menggunakan Path.GetFullPath , meskipun (seperti jawaban Dan R) ini akan memberi Anda seluruh jalur. Penggunaannya adalah sebagai berikut:

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

atau lebih menariknya

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

keduanya menghasilkan yang berikut (dengan asumsi direktori Anda saat ini adalah D: \):

D:\fred\frag

Perhatikan bahwa metode ini tidak mencoba menentukan apakah fred atau frag benar-benar ada.

Charlie
sumber
Itu semakin dekat, tetapi ketika saya mencobanya saya mendapatkan "H: \ fred \ frag" meskipun direktori saya saat ini adalah "C: \ scratch", yang salah. (Seharusnya tidak melakukan itu menurut MSDN.) Namun itu memberi saya ide. Saya akan menambahkannya sebagai jawaban.
dan-gph
8
Masalah Anda adalah Anda perlu mengatur direktori saat ini di .NET. [System.IO.Directory]::SetCurrentDirectory(((Get-Location -PSProvider FileSystem).ProviderPath))
JasonMArcher
2
Hanya untuk menyatakannya secara eksplisit [IO.Path]::GetFullPath():, tidak seperti PowerShell asli Resolve-Path, bekerja dengan jalur yang tidak ada juga. Sisi negatifnya adalah kebutuhan untuk menyinkronkan folder kerja .NET dengan PS 'terlebih dahulu, seperti yang ditunjukkan @JasonMArcher.
mklement0
Join-Pathmenyebabkan pengecualian jika merujuk ke drive yang tidak ada.
Tahir Hassan
20

Jawaban yang diterima sangat membantu namun tidak dengan benar 'menormalkan' jalur absolut juga. Temukan di bawah karya turunan saya yang menormalkan jalur absolut dan relatif.

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
sumber
Mengingat semua solusi yang berbeda, yang satu ini berfungsi untuk semua jenis jalur yang berbeda. Sebagai contoh [IO.Path]::GetFullPath()tidak benar menentukan direktori untuk nama file biasa.
Jari Turkia
10

Semua fungsi manipulasi jalur non-PowerShell (seperti yang ada di System.IO.Path) tidak akan dapat diandalkan dari PowerShell karena model penyedia PowerShell memungkinkan jalur PowerShell saat ini berbeda dari apa yang menurut Windows adalah direktori kerja proses.

Juga, seperti yang mungkin telah Anda temukan, cmdlet Resolve-Path dan Convert-Path PowerShell berguna untuk mengubah jalur relatif (yang berisi '..') ke jalur absolut berkualifikasi drive tetapi gagal jika jalur yang dirujuk tidak ada.

Cmdlet yang sangat sederhana berikut harus berfungsi untuk jalur yang tidak ada. Ini akan mengubah 'fred \ frog \ .. \ frag' menjadi 'd: \ fred \ frag' meskipun file atau folder 'fred' atau 'frag' tidak dapat ditemukan (dan drive PowerShell saat ini adalah '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
sumber
2
Ini tidak berfungsi untuk jalur yang tidak ada di mana huruf drive tidak ada, misalnya saya tidak memiliki drive Q :. Get-AbsolutePath q:\foo\bar\..\bazgagal meskipun itu adalah jalur yang valid. Tergantung pada definisi Anda tentang jalur yang valid. :-) FWIW, bahkan built-in Test-Path <path> -IsValidgagal pada jalur yang di-root pada drive yang tidak ada.
Keith Hill
2
@KeHeMeDengan kata lain, PowerShell menganggap jalur pada root yang tidak ada menjadi tidak valid. Saya pikir itu cukup masuk akal karena PowerShell menggunakan root untuk memutuskan penyedia jenis apa yang akan digunakan saat bekerja dengannya. Misalnya, HKLM:\SOFTWAREadalah jalur yang valid di PowerShell, mengacu pada SOFTWAREkunci di kumpulan registri Mesin Lokal. Tetapi untuk mengetahui apakah itu valid, perlu untuk mencari tahu apa aturan untuk jalur registri.
jpmc26
3

Library ini bagus: NDepend.Helpers.FileDirectoryPath .

EDIT: Inilah yang saya dapatkan:

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

Sebut saja seperti ini:

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

Perhatikan bahwa cuplikan ini memerlukan jalur ke DLL. Ada trik yang dapat Anda gunakan untuk menemukan folder yang berisi skrip yang sedang dieksekusi, tetapi dalam kasus saya, saya memiliki variabel lingkungan yang dapat saya gunakan, jadi saya hanya menggunakannya.

dan-gph
sumber
Saya tidak tahu mengapa itu mendapat suara negatif. Perpustakaan itu benar-benar bagus untuk melakukan manipulasi jalur. Itu yang akhirnya saya gunakan dalam proyek saya.
dan-gph
Minus 2. Masih bingung. Saya berharap orang-orang menyadari bahwa mudah menggunakan rakitan .Net dari PowerShell.
dan-gph
Ini sepertinya bukan solusi terbaik, tetapi ini sangat valid.
JasonMArcher
@Jason, saya tidak ingat detailnya, tetapi pada saat itu itu adalah solusi terbaik karena itu satu-satunya yang memecahkan masalah khusus saya. Namun mungkin saja sejak saat itu solusi lain yang lebih baik telah datang.
dan-gph
2
memiliki DLL pihak ke-3 adalah kelemahan besar untuk solusi ini
Louis Kottmann
1

Ini memberikan jalur lengkap:

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

Ini memberikan jalur relatif ke direktori saat ini:

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

Untuk beberapa alasan, mereka hanya berfungsi jika fragberupa file, bukan directory.

dan-gph
sumber
1
gci adalah alias untuk get-childitem. Anak-anak dari sebuah direktori adalah isinya. Ganti gci dengan gi dan itu akan bekerja untuk keduanya.
zdan
2
Get-Item bekerja dengan baik. Tetapi sekali lagi, pendekatan ini mengharuskan folder tersebut ada.
Peter Lillevold
1

Buat sebuah fungsi. Fungsi ini akan menormalkan jalur yang tidak ada di sistem Anda serta tidak menambahkan huruf drive.

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'

Terima kasih kepada Oliver Schadlich atas bantuannya di RegEx.

M.Hubers
sumber
Perhatikan ini tidak berfungsi untuk jalur seperti somepaththing\.\filename.txtkarena itu membuat titik tunggal itu
Mark Schultheiss
1

Jika jalur menyertakan kualifikasi (huruf kandar) maka jawaban x0n untuk Powershell: selesaikan jalur yang mungkin tidak ada? akan menormalkan jalur. Jika jalur tidak menyertakan kualifikasi, itu akan tetap dinormalisasi tetapi akan mengembalikan jalur yang sepenuhnya memenuhi syarat relatif ke direktori saat ini, yang mungkin bukan yang Anda inginkan.

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

Jika Anda perlu membuang bagian .., Anda dapat menggunakan objek System.IO.DirectoryInfo. Gunakan 'fred \ frog .. \ frag' di konstruktor. Properti FullName akan memberi Anda nama direktori yang dinormalisasi.

Satu-satunya kelemahan adalah ia akan memberi Anda seluruh jalur (misalnya c: \ test \ fred \ frag).

Dan R
sumber
0

Bagian berguna dari komentar di sini digabungkan sedemikian rupa sehingga menyatukan jalur relatif dan absolut:

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

Beberapa contoh:

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

keluaran:

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

Nah, salah satu caranya adalah:

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

Tunggu, mungkin saya salah paham dengan pertanyaan itu. Dalam contoh Anda, apakah frag merupakan subfolder dari katak?

EBGreen
sumber
"apakah frag merupakan subfolder dari katak?" No The .. berarti naik satu tingkat. frag adalah subfolder (atau file) di fred.
dan-gph