Làm cách nào để chuẩn hóa một đường dẫn trong PowerShell?


94

Tôi có hai con đường:

fred\frog

..\frag

Tôi có thể kết hợp chúng với nhau trong PowerShell như sau:

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

Điều đó mang lại cho tôi điều này:

fred\frog\..\frag

Nhưng tôi không muốn điều đó. Tôi muốn một đường dẫn chuẩn hóa không có dấu chấm kép, như sau:

fred\frag

Làm thế nào tôi có thể nhận được điều đó?


1
Frag có phải là thư mục con của ếch không? Nếu không thì việc kết hợp đường dẫn sẽ giúp bạn có được fred \ ếch \ frag. Nếu đúng thì đó là một câu hỏi rất khác.
EBGreen

Câu trả lời:


81

Bạn có thể sử dụng kết hợp pwd, Join-Path[System.IO.Path]::GetFullPathđể có được một con đường nở đầy đủ.

cd( Set-Location) không thay đổi thư mục làm việc hiện tại của quy trình, chỉ cần chuyển tên tệp tương đối đến API .NET không hiểu ngữ cảnh PowerShell, có thể có các tác dụng phụ không mong muốn, chẳng hạn như phân giải thành một đường dẫn dựa trên hoạt động ban đầu thư mục (không phải vị trí hiện tại của bạn).

Những gì bạn làm là trước tiên bạn đủ điều kiện cho con đường của mình:

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

Điều này mang lại (với vị trí hiện tại của tôi):

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

Với cơ sở tuyệt đối, có thể an toàn khi gọi .NET API GetFullPath:

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

Điều này cung cấp cho bạn đường dẫn đủ điều kiện và với đường dẫn ..đã bị xóa:

C:\WINDOWS\system32\fred\frag

Nó cũng không phức tạp, cá nhân tôi không thích các giải pháp phụ thuộc vào các tập lệnh bên ngoài cho việc này, vấn đề đơn giản được giải quyết khá khéo léo bởi Join-Pathpwd( GetFullPathchỉ là làm cho nó đẹp). Nếu bạn chỉ muốn giữ lại phần tương đối , bạn chỉ cần thêm .Substring((pwd).Path.Trim('\').Length + 1)và thì đấy!

fred\frag

CẬP NHẬT

Cảm ơn @Dangph đã chỉ ra C:\trường hợp cạnh.


Bước cuối cùng không hoạt động nếu pwd là "C: \". Trong trường hợp đó, tôi nhận được "red \ frag".
dan-gph

@Dangph - Tôi không chắc tôi hiểu ý bạn muốn nói gì, ở trên dường như đang hoạt động tốt? Bạn đang sử dụng phiên bản PowerShell nào? Tôi đang sử dụng phiên bản 3.0.
John Leidegren

1
Ý tôi là bước cuối cùng: cd c:\; "C:\fred\frag".Substring((pwd).Path.Length + 1). Nó không phải là một việc lớn; chỉ là một cái gì đó cần biết.
dan-gph

Ah, bắt tốt, chúng tôi có thể khắc phục điều đó bằng cách thêm một lệnh gọi. Cố gắng cd c:\; "C:\fred\frag".Substring((pwd).Path.Trim('\').Length + 1). Nó đã trở nên khá lâu mặc dù vậy.
John Leidegren

2
Hoặc, chỉ cần sử dụng: $ ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath (". \ Nonexist \ foo.txt") Cũng hoạt động với các đường dẫn không tồn tại. "x0n" xứng đáng nhận được tín dụng cho btw này. Như anh ấy lưu ý, nó phân giải thành PSPath, không phải đường dẫn hệ thống phân giải, nhưng nếu bạn đang sử dụng các đường dẫn trong PowerShell, ai quan tâm? stackoverflow.com/questions/3038337/…
Joe the Coder,

106

Bạn có thể mở rộng .. \ frag đến đường dẫn đầy đủ của nó bằng đường dẫn giải quyết:

PS > resolve-path ..\frag 

Cố gắng bình thường hóa đường dẫn bằng phương thức kết hợp ():

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

Điều gì nếu con đường của bạn là C:\Windowsvs C:\Windows\ con đường tương tự nhưng hai kết quả khác nhau
Joe Phillips

2
Các tham số cho [io.path]::Combineđược đảo ngược. Tốt hơn, hãy sử dụng Join-Pathlệnh PowerShell gốc : Join-Path (Resolve-Path ..\frag).Path 'fred\frog'Cũng lưu ý rằng, ít nhất là kể từ PowerShell v3, Resolve-Pathhiện hỗ trợ -Relativechuyển đổi để phân giải thành một đường dẫn liên quan đến thư mục hiện tại. Như đã đề cập, Resolve-Pathchỉ hoạt động với các đường dẫn hiện có, không giống như [IO.Path]::GetFullPath().
mklement0

25

Bạn cũng có thể sử dụng Path.GetFullPath , mặc dù (như với câu trả lời của Dan R) điều này sẽ cung cấp cho bạn toàn bộ đường dẫn. Cách sử dụng sẽ như sau:

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

hoặc thú vị hơn

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

cả hai đều mang lại kết quả sau (giả sử thư mục hiện tại của bạn là D: \):

D:\fred\frag

Lưu ý rằng phương pháp này không cố gắng xác định liệu fred hoặc frag có thực sự tồn tại hay không.


Điều đó đang đến gần, nhưng khi tôi thử nó, tôi nhận được "H: \ fred \ frag" mặc dù thư mục hiện tại của tôi là "C: \ xước", điều này là sai. (Nó không nên làm điều đó theo MSDN.) Tuy nhiên, nó đã cho tôi một ý tưởng. Tôi sẽ thêm nó như một câu trả lời.
dan-gph

8
Vấn đề của bạn là bạn cần đặt thư mục hiện tại trong .NET. [System.IO.Directory]::SetCurrentDirectory(((Get-Location -PSProvider FileSystem).ProviderPath))
JasonMArcher

2
Chỉ cần nói rõ rằng:, [IO.Path]::GetFullPath()không giống như bản gốc của PowerShell Resolve-Path, cũng hoạt động với các đường dẫn không tồn tại. Nhược điểm của nó là trước tiên cần phải đồng bộ thư mục làm việc của .NET với PS ', như @JasonMArcher đã chỉ ra.
mklement0

Join-Pathgây ra một ngoại lệ nếu đang tham chiếu đến một ổ đĩa không tồn tại.
Tahir Hassan

20

Câu trả lời được chấp nhận là một trợ giúp tuyệt vời tuy nhiên nó cũng không 'bình thường hóa' một đường dẫn tuyệt đối một cách chính xác. Tìm bên dưới công việc phái sinh của tôi chuẩn hóa cả đường dẫn tuyệt đối và tương đối.

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

Với tất cả các giải pháp khác nhau, giải pháp này hoạt động cho tất cả các loại đường dẫn khác nhau. Như một ví dụ [IO.Path]::GetFullPath()không xác định chính xác thư mục cho một tên tệp thuần túy.
Jari Turkia

10

Bất kỳ chức năng thao tác đường dẫn nào không phải PowerShell (chẳng hạn như trong System.IO.Path) sẽ không đáng tin cậy từ PowerShell vì mô hình trình cung cấp của PowerShell cho phép đường dẫn hiện tại của PowerShell khác với những gì Windows nghĩ là thư mục làm việc của quy trình.

Ngoài ra, như bạn có thể đã khám phá ra, lệnh ghép ngắn Resolve-Path và Convert-Path của PowerShell rất hữu ích để chuyển đổi các đường dẫn tương đối (những đường có chứa '..) thành các đường dẫn tuyệt đối đủ điều kiện lái xe nhưng chúng không thành công nếu đường dẫn được tham chiếu không tồn tại.

Lệnh ghép ngắn rất đơn giản sau đây sẽ hoạt động đối với các đường dẫn không tồn tại. Nó sẽ chuyển đổi 'fred \ ếch \ .. \ frag' thành 'd: \ fred \ frag' ngay cả khi không tìm thấy tệp hoặc thư mục 'fred' hoặc 'frag' (và ổ PowerShell hiện tại là 'd:') .

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

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

2
Điều này không hoạt động đối với các đường dẫn không tồn tại mà ký tự ổ đĩa không tồn tại, ví dụ như tôi không có ổ đĩa Q:. Get-AbsolutePath q:\foo\bar\..\bazkhông thành công mặc dù nó là một đường dẫn hợp lệ. Vâng, tùy thuộc vào việc xác định đường dẫn hợp lệ của bạn. :-) FWIW, ngay cả cài sẵn cũng Test-Path <path> -IsValidkhông thành công trên các đường dẫn bắt nguồn từ ổ đĩa không tồn tại.
Keith Hill,

2
@KeithHill Nói cách khác, PowerShell coi một đường dẫn trên một gốc không tồn tại là không hợp lệ. Tôi nghĩ điều đó khá hợp lý vì PowerShell sử dụng root để quyết định loại trình cung cấp nào sẽ sử dụng khi làm việc với nó. Ví dụ: HKLM:\SOFTWARElà một đường dẫn hợp lệ trong PowerShell, tham chiếu đến SOFTWAREkhóa trong tổ đăng ký Máy cục bộ. Nhưng để tìm hiểu xem nó có hợp lệ hay không, nó cần phải tìm ra các quy tắc cho đường dẫn đăng ký là gì.
jpmc

3

Thư viện này tốt: NDepend.Helpers.FileDirectoryPath .

CHỈNH SỬA: Đây là những gì tôi đã nghĩ ra:

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

Gọi nó như thế này:

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

Lưu ý rằng đoạn mã này yêu cầu đường dẫn đến DLL. Có một thủ thuật bạn có thể sử dụng để tìm thư mục chứa tập lệnh hiện đang thực thi, nhưng trong trường hợp của tôi, tôi có một biến môi trường mà tôi có thể sử dụng, vì vậy tôi chỉ sử dụng nó.


Tôi không biết tại sao lại nhận được một phiếu tán thành. Thư viện đó thực sự tốt để thực hiện các thao tác đường dẫn. Đó là những gì tôi đã sử dụng trong dự án của mình.
dan-gph

Điểm trừ 2. Vẫn còn phân vân. Tôi hy vọng rằng mọi người nhận ra rằng thật dễ dàng để sử dụng các tổ hợp .Net từ PowerShell.
dan-gph

Đây có vẻ không phải là giải pháp tốt nhất, nhưng nó hoàn toàn hợp lệ.
JasonMArcher

@Jason, tôi không nhớ chi tiết, nhưng vào thời điểm đó thì đó là giải pháp tốt nhất vì nó là giải pháp duy nhất giải quyết được vấn đề cụ thể của tôi. Tuy nhiên, có thể kể từ đó một giải pháp khác tốt hơn đã ra đời.
dan-gph

2
có DLL của bên thứ 3 là một nhược điểm lớn đối với giải pháp này
Louis Kottmann

1

Điều này cung cấp đường dẫn đầy đủ:

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

Điều này cung cấp cho đường dẫn liên quan đến thư mục hiện tại:

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

Vì một số lý do, chúng chỉ hoạt động nếu fraglà một tệp, không phải a directory.


1
gci là một bí danh của get-childitem. Phần tử con của một thư mục là nội dung của nó. Thay thế gci bằng gi và nó sẽ hoạt động cho cả hai.
zdan

2
Get-Item hoạt động tốt. Nhưng một lần nữa, phương pháp này yêu cầu các thư mục phải tồn tại.
Peter Lillevold

1

Tạo một chức năng. Chức năng này sẽ bình thường hóa một đường dẫn không tồn tại trên hệ thống của bạn cũng như không thêm ký tự ổ đĩa.

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

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

Ví dụ:

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

Xin gửi lời cảm ơn tới Oliver Schadlich vì đã giúp đỡ trong RegEx.


Lưu ý điều này không làm việc cho những con đường như somepaththing\.\filename.txtlà nó giữ rằng dấu chấm duy nhất
Đánh dấu Schultheiss

1

Nếu đường dẫn bao gồm một định tính (ký tự ổ đĩa) thì câu trả lời của x0n cho Powershell: giải quyết đường dẫn có thể không tồn tại? sẽ bình thường hóa đường dẫn. Nếu đường dẫn không bao gồm bộ đủ điều kiện, nó sẽ vẫn được chuẩn hóa nhưng sẽ trả về đường dẫn đủ điều kiện liên quan đến thư mục hiện tại, có thể không phải là những gì bạn muốn.

$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

Nếu bạn cần loại bỏ phần .., bạn có thể sử dụng đối tượng System.IO.DirectoryInfo. Sử dụng 'fred \ ếch .. \ frag' trong hàm tạo. Thuộc tính FullName sẽ cung cấp cho bạn tên thư mục chuẩn hóa.

Hạn chế duy nhất là nó sẽ cung cấp cho bạn toàn bộ đường dẫn (ví dụ: c: \ test \ fred \ frag).


0

Các phần thích hợp của các nhận xét ở đây kết hợp để chúng thống nhất các đường dẫn tương đối và tuyệt đối:

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

Một số mẫu:

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

đầu ra:

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

Chà, một cách sẽ là:

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

Chờ đã, có lẽ tôi hiểu sai câu hỏi. Trong ví dụ của bạn, frag có phải là thư mục con của ếch không?


"frag có phải là thư mục con của ếch không?" Không. Có nghĩa là đi lên một cấp độ. frag là một thư mục con (hoặc một tệp) trong fred.
dan-gph
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.