วิธีทำให้เส้นทางใน PowerShell เป็นปกติ


96

ฉันมีสองเส้นทาง:

fred\frog

และ

..\frag

ฉันสามารถรวมเข้าด้วยกันใน PowerShell ดังนี้:

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

นั่นทำให้ฉันมีสิ่งนี้:

fred\frog\..\frag

แต่ฉันไม่ต้องการอย่างนั้น ฉันต้องการเส้นทางที่ทำให้เป็นมาตรฐานโดยไม่มีจุดสองจุดดังนี้:

fred\frag

ฉันจะได้รับสิ่งนั้นได้อย่างไร?


1
Frag เป็นโฟลเดอร์ย่อยของ Frog หรือไม่? ถ้าไม่รวมเส้นทางจะทำให้คุณได้รับ fred \ frog \ frag ถ้าเป็นเช่นนั้นเป็นคำถามที่แตกต่างกันมาก
EBGreen

คำตอบ:


83

คุณสามารถใช้การรวมกันของpwd, Join-Pathและ[System.IO.Path]::GetFullPathจะได้รับการขยายเส้นทางที่มีคุณสมบัติครบถ้วน

เนื่องจากcd( Set-Location) ไม่ได้เปลี่ยนไดเร็กทอรีการทำงานปัจจุบันของกระบวนการเพียงแค่ส่งชื่อไฟล์สัมพัทธ์ไปยัง. NET API ที่ไม่เข้าใจบริบทของ PowerShell อาจมีผลข้างเคียงที่ไม่ได้ตั้งใจเช่นการแก้ไขไปยังเส้นทางตามการทำงานครั้งแรก ไดเรกทอรี (ไม่ใช่ตำแหน่งปัจจุบันของคุณ)

สิ่งที่คุณทำคือคุณต้องมีคุณสมบัติตามเส้นทางของคุณก่อน:

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

สิ่งนี้ให้ผล (ระบุตำแหน่งปัจจุบันของฉัน):

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

ด้วยฐานที่สมบูรณ์จึงปลอดภัยที่จะเรียก. NET API GetFullPath:

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

ซึ่งให้เส้นทางที่มีคุณสมบัติครบถ้วนและเมื่อ..นำออก:

C:\WINDOWS\system32\fred\frag

มันไม่ซับซ้อนโดยส่วนตัวฉันดูถูกวิธีแก้ปัญหาที่ขึ้นอยู่กับสคริปต์ภายนอกสำหรับสิ่งนี้มันเป็นปัญหาง่ายๆที่แก้ไขได้ค่อนข้างเหมาะสมโดยJoin-Pathและpwd( GetFullPathเป็นเพียงการทำให้มันสวย) หากคุณต้องการเก็บเฉพาะส่วนที่สัมพันธ์กันคุณเพียงแค่เพิ่ม.Substring((pwd).Path.Trim('\').Length + 1)และ voila!

fred\frag

อัปเดต

ขอบคุณ @Dangph ที่ชี้ให้เห็นถึงC:\ขอบคดี


ขั้นตอนสุดท้ายจะใช้ไม่ได้หาก pwd เป็น "C: \" ในกรณีนั้นฉันจะได้รับ "red \ frag"
dan-gph

@Dangph - ไม่แน่ใจว่าฉันเข้าใจที่คุณหมายถึงข้างต้นดูเหมือนจะใช้งานได้ดี คุณใช้ PowerShell เวอร์ชันใด ฉันใช้เวอร์ชัน 3.0
John Leidegren

1
ฉันหมายถึงขั้นตอนสุดท้าย: cd c:\; "C:\fred\frag".Substring((pwd).Path.Length + 1). ไม่ใช่เรื่องใหญ่ เป็นเพียงสิ่งที่ต้องระวัง
dan-gph

อ่าจับได้ดีเราสามารถแก้ไขได้โดยเพิ่มการเรียกตัด ลองcd c:\; "C:\fred\frag".Substring((pwd).Path.Trim('\').Length + 1). มันค่อนข้างนาน
John Leidegren

2
หรือเพียงแค่ใช้: $ ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath (". \ nonexist \ foo.txt") ทำงานร่วมกับพา ธ ที่ไม่มีอยู่ได้เช่นกัน "x0n" สมควรได้รับเครดิตสำหรับ btw นี้ ในขณะที่เขาตั้งข้อสังเกตมันจะแก้ไขเป็น PSPaths ไม่ใช่เส้นทางของระบบ flilesystem แต่ถ้าคุณใช้เส้นทางใน PowerShell ใครจะสนล่ะ? stackoverflow.com/questions/3038337/…
Joe the Coder

106

คุณสามารถขยาย .. \ frag เป็นเส้นทางแบบเต็มโดยใช้วิธีแก้ปัญหา:

PS > resolve-path ..\frag 

พยายามทำให้เส้นทางเป็นปกติโดยใช้วิธีการรวม ():

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

จะเกิดอะไรขึ้นถ้าเส้นทางของคุณC:\WindowsเทียบC:\Windows\ กับเส้นทางเดียวกัน แต่ผลลัพธ์สองอย่างแตกต่างกัน
Joe Phillips

2
พารามิเตอร์สำหรับ[io.path]::Combineจะกลับรายการ ยังดีกว่าให้ใช้Join-Pathคำสั่ง PowerShell ดั้งเดิม: Join-Path (Resolve-Path ..\frag).Path 'fred\frog'โปรดทราบว่าอย่างน้อยที่สุดใน PowerShell v3 Resolve-Pathตอนนี้สนับสนุน-Relativeสวิตช์สำหรับการแก้ไขเส้นทางที่สัมพันธ์กับโฟลเดอร์ปัจจุบัน เป็นที่กล่าวถึงจะทำงานเฉพาะกับเส้นทางที่มีอยู่ซึ่งแตกต่างจากResolve-Path [IO.Path]::GetFullPath()
mklement0

25

คุณยังสามารถใช้Path.GetFullPathแม้ว่า (เช่นเดียวกับคำตอบของ Dan R) สิ่งนี้จะให้เส้นทางทั้งหมดแก่คุณ การใช้งานจะเป็นดังนี้:

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

หรือน่าสนใจมากขึ้น

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

ซึ่งทั้งสองให้ผลลัพธ์ต่อไปนี้ (สมมติว่าไดเรกทอรีปัจจุบันของคุณคือ D: \):

D:\fred\frag

โปรดทราบว่าวิธีนี้ไม่ได้พยายามระบุว่า fred หรือ frag มีอยู่จริง


มันใกล้เข้ามาแล้ว แต่เมื่อฉันลองฉันได้รับ "H: \ fred \ frag" แม้ว่าไดเรกทอรีปัจจุบันของฉันจะเป็น "C: \ scratch" ซึ่งไม่ถูกต้อง (ไม่ควรทำเช่นนั้นตาม MSDN) ทำให้ฉันมีความคิด ฉันจะเพิ่มเป็นคำตอบ
dan-gph

8
ปัญหาของคุณคือคุณต้องตั้งค่าไดเรกทอรีปัจจุบันใน. NET [System.IO.Directory]::SetCurrentDirectory(((Get-Location -PSProvider FileSystem).ProviderPath))
JasonMArcher

2
เพียงเพื่อระบุอย่างชัดเจน: [IO.Path]::GetFullPath()ซึ่งแตกต่างจากเนทีฟของ PowerShell Resolve-Pathทำงานกับพา ธ ที่ไม่มีอยู่จริงเช่นกัน ข้อเสียคือต้องซิงค์โฟลเดอร์การทำงานของ. NET กับ PS 'ก่อนตามที่ @JasonMArcher ชี้ให้เห็น
mklement0

Join-Pathทำให้เกิดข้อยกเว้นหากอ้างถึงไดรฟ์ที่ไม่มีอยู่
Tahir Hassan

20

คำตอบที่ได้รับการยอมรับคือความช่วยเหลือที่ดี แต่มันก็ไม่ได้ 'ทำให้ปกติ' เป็นเส้นทางที่แน่นอนเกินไป ค้นหางานอนุพันธ์ของฉันด้านล่างซึ่งทำให้เส้นทางทั้งสัมบูรณ์และสัมพัทธ์เป็นปกติ

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

ด้วยวิธีการแก้ปัญหาที่แตกต่างกันทั้งหมดนี้ใช้ได้กับเส้นทางประเภทต่างๆทั้งหมด ตามตัวอย่าง[IO.Path]::GetFullPath()ไม่ได้ระบุไดเร็กทอรีสำหรับชื่อไฟล์ธรรมดาอย่างถูกต้อง
Jari Turkia

10

ฟังก์ชันการจัดการพา ธ ที่ไม่ใช่ PowerShell (เช่นใน System.IO.Path) จะไม่น่าเชื่อถือจาก PowerShell เนื่องจากโมเดลผู้ให้บริการของ PowerShell อนุญาตให้พา ธ ปัจจุบันของ PowerShell แตกต่างจากที่ Windows คิดว่าไดเร็กทอรีการทำงานของกระบวนการคือ

นอกจากนี้ในขณะที่คุณได้ค้นพบแล้ว cmdlet ของ Resolve-Path และ Convert-Path ของ PowerShell มีประโยชน์สำหรับการแปลงเส้นทางสัมพัทธ์ (ที่มี ".. " เป็นพา ธ สัมบูรณ์ที่มีคุณสมบัติตรงตามไดรฟ์ แต่จะล้มเหลวหากไม่มีเส้นทางที่อ้างถึง

cmdlet ที่เรียบง่ายต่อไปนี้ควรใช้ได้กับพา ธ ที่ไม่อยู่ไกล มันจะแปลง 'fred \ frog \ .. \ frag' เป็น 'd: \ fred \ frag' แม้ว่าจะไม่พบไฟล์หรือโฟลเดอร์ 'fred' หรือ 'frag' (และไดรฟ์ PowerShell ปัจจุบันคือ 'd:') .

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

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

2
วิธีนี้ใช้ไม่ได้กับเส้นทางที่ไม่มีอยู่ซึ่งไม่มีอักษรระบุไดรฟ์เช่นฉันไม่มี Q: drive Get-AbsolutePath q:\foo\bar\..\bazล้มเหลวแม้ว่าจะเป็นเส้นทางที่ถูกต้อง ขึ้นอยู่กับการกำหนดเส้นทางที่ถูกต้องของคุณ :-) FWIW แม้ในตัวจะTest-Path <path> -IsValidล้มเหลวบนเส้นทางที่รูทในไดรฟ์ที่ไม่มีอยู่
Keith Hill

2
@KeithHill กล่าวอีกนัยหนึ่ง PowerShell ถือว่าเส้นทางบนรูทที่ไม่มีอยู่จริงนั้นไม่ถูกต้อง ฉันคิดว่ามันค่อนข้างสมเหตุสมผลเนื่องจาก PowerShell ใช้รูทเพื่อตัดสินใจว่าจะใช้ผู้ให้บริการประเภทใดเมื่อทำงานกับมัน เช่นHKLM:\SOFTWAREเป็นเส้นทางที่ถูกต้องใน PowerShell โดยอ้างถึงSOFTWAREคีย์ในกลุ่มรีจิสทรีของ Local Machine แต่หากต้องการทราบว่าถูกต้องหรือไม่จำเป็นต้องพิจารณาว่ากฎสำหรับเส้นทางรีจิสทรีคืออะไร
jpmc26

3

ห้องสมุดนี้เป็นสิ่งที่ดี: NDepend.Helpers.FileDirectoryPath

แก้ไข:นี่คือสิ่งที่ฉันคิดขึ้น:

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

เรียกแบบนี้ว่า

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

โปรดทราบว่าข้อมูลโค้ดนี้ต้องการพา ธ ไปยัง DLL มีเคล็ดลับที่คุณสามารถใช้เพื่อค้นหาโฟลเดอร์ที่มีสคริปต์ที่กำลังดำเนินการอยู่ แต่ในกรณีของฉันฉันมีตัวแปรสภาพแวดล้อมที่ฉันสามารถใช้ได้ดังนั้นฉันจึงใช้มัน


ฉันไม่รู้ว่าทำไมถึงได้รับการโหวตลดลง ห้องสมุดนั้นเหมาะสำหรับการจัดการเส้นทางจริงๆ มันคือสิ่งที่ฉันใช้ในโครงการของฉัน
dan-gph

ลบ 2 ยังงง. ฉันหวังว่าผู้คนจะรู้ว่าการใช้งาน. Net ประกอบจาก PowerShell เป็นเรื่องง่าย
dan-gph

ดูเหมือนจะไม่ใช่วิธีแก้ปัญหาที่ดีที่สุด แต่ก็ใช้ได้อย่างสมบูรณ์แบบ
JasonMArcher

@ เจสันฉันจำรายละเอียดไม่ได้ แต่ในเวลานั้นมันเป็นทางออกที่ดีที่สุดเพราะเป็นวิธีเดียวที่แก้ปัญหาเฉพาะของฉันได้ อย่างไรก็ตามเป็นไปได้ว่าตั้งแต่นั้นเป็นต้นมาทางออกที่ดีกว่าก็มาพร้อมกัน
dan-gph

2
การมี DLL ของบุคคลที่สามเป็นข้อเสียเปรียบอย่างมากสำหรับโซลูชันนี้
Louis Kottmann

1

สิ่งนี้ให้เส้นทางแบบเต็ม:

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

สิ่งนี้ให้เส้นทางที่สัมพันธ์กับไดเร็กทอรีปัจจุบัน:

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

ด้วยเหตุผลบางประการพวกเขาใช้งานได้fragก็ต่อเมื่อเป็นไฟล์ไม่ใช่ไฟล์directory.


1
gci เป็นนามแฝงสำหรับ get-childitem ลูกของไดเร็กทอรีคือเนื้อหา แทนที่ gci ด้วย gi และควรใช้ทั้งสองอย่าง
zdan

2
Get-Item ทำงานได้ดี แต่อีกครั้งวิธีนี้ต้องการให้มีโฟลเดอร์
Peter Lillevold

1

สร้างฟังก์ชัน ฟังก์ชันนี้จะทำให้เส้นทางที่ไม่มีอยู่ในระบบของคุณเป็นปกติและไม่ต้องเพิ่มอักษรชื่อไดรฟ์

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

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

เช่น:

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

ขอขอบคุณ Oliver Schadlich เพื่อขอความช่วยเหลือใน RegEx


โปรดทราบว่าสิ่งนี้ใช้ไม่ได้กับเส้นทางเช่นเดียวsomepaththing\.\filename.txtกับที่เก็บจุดเดียวไว้
Mark Schultheiss

1

หากเส้นทางมี qualifier (อักษรระบุไดรฟ์) แล้วx0n คำตอบสำหรับ Powershell: แก้ไขเส้นทางที่อาจไม่มีอยู่? จะทำให้เส้นทางเป็นปกติ หากพา ธ ไม่มี qualifier จะยังคงเป็น normalized แต่จะส่งคืนพา ธ แบบเต็มที่สัมพันธ์กับไดเร็กทอรีปัจจุบันซึ่งอาจไม่ใช่สิ่งที่คุณต้องการ

$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

หากคุณต้องการกำจัดส่วน .. คุณสามารถใช้อ็อบเจ็กต์ System.IO.DirectoryInfo ใช้ 'fred \ frog .. \ frag' ในตัวสร้าง คุณสมบัติ FullName จะให้ชื่อไดเร็กทอรีปกติ

ข้อเสียเปรียบเพียงประการเดียวคือจะให้เส้นทางทั้งหมดแก่คุณ (เช่น c: \ test \ fred \ frag)


0

ส่วนที่เหมาะสมของความคิดเห็นที่นี่รวมเข้าด้วยกันเพื่อรวมเส้นทางสัมพัทธ์และสัมบูรณ์:

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

ตัวอย่างบางส่วน:

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

เอาต์พุต:

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

วิธีหนึ่งคือ:

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

เดี๋ยวก่อนบางทีฉันอาจจะเข้าใจคำถามผิด ในตัวอย่างของคุณ frag เป็นโฟลเดอร์ย่อยของ frog หรือไม่?


"Frag เป็นโฟลเดอร์ย่อยของกบหรือไม่" ไม่ .. หมายถึงขึ้นไปหนึ่งระดับ Frag เป็นโฟลเดอร์ย่อย (หรือไฟล์) ใน fred
dan-gph
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.