PowerShell에서 경로를 정규화하는 방법은 무엇입니까?


94

두 가지 경로가 있습니다.

fred\frog

..\frag

다음과 같이 PowerShell에서 함께 조인 할 수 있습니다.

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

그것은 나에게 이것을 준다 :

fred\frog\..\frag

그러나 나는 그것을 원하지 않는다. 다음과 같이 이중 점이없는 정규화 된 경로를 원합니다.

fred\frag

어떻게 얻을 수 있습니까?


1
Frag는 frog의 하위 폴더입니까? 그렇지 않은 경우 경로를 결합하면 fred \ frog \ frag가됩니다. 그렇다면 그것은 매우 다른 질문입니다.
EBGreen

답변:


81

당신의 조합을 사용할 수 있습니다 pwd, Join-Path그리고 [System.IO.Path]::GetFullPath완전한 확장 된 경로를 얻을 수 있습니다.

cd( Set-Location)는 프로세스의 현재 작업 디렉터리를 변경하지 않기 때문에 PowerShell 컨텍스트를 이해하지 못하는 .NET API에 상대 파일 이름을 전달하기 만하면 초기 작업을 기반으로하는 경로로 확인하는 것과 같은 의도하지 않은 부작용이 발생할 수 있습니다. 디렉터리 (현재 위치 아님).

당신이하는 일은 먼저 당신의 경로를 한정하는 것입니다.

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)하고 짜잔!

fred\frag

최신 정보

C:\엣지 케이스 를 지적 해 주신 @Dangph에게 감사드립니다 .


마지막 단계는 pwd가 "C : \"이면 작동하지 않습니다. 이 경우 "red \ frag"가 표시됩니다.
dan-gph 2013 년

@Dangph-당신이 의미하는 바를 이해하지 못합니다. 위의 내용이 제대로 작동하는 것 같습니다. 어떤 버전의 PowerShell을 사용하고 있습니까? 버전 3.0을 사용하고 있습니다.
John Leidegren 2013 년

1
마지막 단계 : cd c:\; "C:\fred\frag".Substring((pwd).Path.Length + 1). 큰 문제는 아닙니다. 알아 두어야 할 것입니다.
dan-gph 2013 년

아, 잘 잡았습니다. 트림 호출을 추가하여 수정할 수 있습니다. 시도해보십시오 cd c:\; "C:\fred\frag".Substring((pwd).Path.Trim('\').Length + 1). 그래도 조금 길어지고 있습니다.
John Leidegren 2013 년

2
또는 다음을 사용하십시오. $ ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath ( ". \ nonexist \ foo.txt") 존재하지 않는 경로에서도 작동합니다. "x0n"은이 btw에 대한 크레딧을받을 자격이 있습니다. 그가 언급했듯이 그것은 flilesystem 경로가 아닌 PSPath로 확인되지만 PowerShell에서 경로를 사용하는 경우 누가 신경 쓰나요? stackoverflow.com/questions/3038337/…
Joe the Coder 2011

106

resolve-path를 사용하여 .. \ frag를 전체 경로로 확장 할 수 있습니다.

PS > resolve-path ..\frag 

Combine () 메서드를 사용하여 경로를 정규화하십시오.

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

어떤 경로 인 경우 C:\WindowsC:\Windows\ 동일한 경로하지만 두 개의 서로 다른 결과
조 필립스

2
의 매개 변수 [io.path]::Combine가 반전됩니다. 더 좋은 방법은 기본 Join-PathPowerShell 명령을 사용하는 것입니다 . 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가 실제로 존재하는지 여부를 판별하지 않습니다.


점점 가까워지고 있지만 현재 디렉토리가 "C : \ scratch"인 경우에도 "H : \ fred \ frag"가 표시됩니다. 이는 잘못된 것입니다. (MSDN에 따르면 그렇게해서는 안됩니다.) 그러나 그것은 나에게 아이디어를주었습니다. 대답으로 추가하겠습니다.
dan-gph

8
문제는 .NET에서 현재 디렉토리를 설정해야한다는 것입니다. [System.IO.Directory]::SetCurrentDirectory(((Get-Location -PSProvider FileSystem).ProviderPath))
JasonMArcher 2011-08-16

2
명시 적으로 설명하기 위해 : [IO.Path]::GetFullPath()PowerShell의 기본과 달리 Resolve-Path존재하지 않는 경로에서도 작동합니다. 단점은 @JasonMArcher가 지적했듯이 .NET의 작업 폴더를 PS의 작업 폴더와 먼저 동기화해야한다는 것입니다.
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()일반 파일 이름에 대한 디렉토리를 올바르게 결정하지 않습니다.
야리 Turkia

10

PowerShell의 공급자 모델을 사용하면 PowerShell의 현재 경로가 Windows에서 프로세스의 작업 디렉터리라고 생각하는 것과 다를 수 있기 때문에 PowerShell이 ​​아닌 경로 조작 기능 (예 : System.IO.Path의 기능)은 PowerShell에서 신뢰할 수 없습니다.

또한 이미 발견 한 것처럼 PowerShell의 Resolve-Path 및 Convert-Path cmdlet은 상대 경로 ( '..'s 포함)를 드라이브 한정 절대 경로로 변환하는 데 유용하지만 참조 된 경로가 존재하지 않으면 실패합니다.

다음의 매우 간단한 cmdlet은 존재하지 않는 경로에 대해 작동합니다. 'fred'또는 'frag'파일 또는 폴더를 찾을 수없는 경우에도 'fred \ frog \ .. \ frag'를 'd : \ 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 : 드라이브가없는 경우)에서는 작동하지 않습니다. Get-AbsolutePath q:\foo\bar\..\baz유효한 경로인데도 실패합니다. 글쎄, 유효한 경로에 대한 정의에 따라. :-) FWIW, 심지어는 Test-Path <path> -IsValid존재하지 않는 드라이브에 루트가있는 경로 에서 내장 오류가 발생합니다.
Keith Hill

2
@KeithHill 즉, PowerShell은 존재하지 않는 루트의 경로를 유효하지 않은 것으로 간주합니다. PowerShell은 루트를 사용하여 작업 할 때 사용할 공급자의 종류를 결정하므로 상당히 합리적이라고 생각합니다. 예를 들어 는 로컬 컴퓨터 레지스트리 하이브 HKLM:\SOFTWARESOFTWARE키를 참조하는 PowerShell의 유효한 경로입니다 . 그러나 그것이 유효한지 알아 내려면 레지스트리 경로에 대한 규칙이 무엇인지 파악해야합니다.
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. 여전히 의아해합니다. 사람들이 PowerShell에서 .Net 어셈블리를 사용하기 쉽다는 것을 깨닫기를 바랍니다.
dan-gph

이것은 최선의 해결책은 아니지만 완벽하게 유효합니다.
JasonMArcher 2011-08-10

@Jason, 나는 세부 사항을 기억하지 못하지만 당시에는 그것이 내 특정 문제를 해결 한 유일한 솔루션 이었기 때문에 최선의 해결책이었습니다. 그러나 그 이후로 또 다른 더 나은 해결책이 등장했을 가능성이 있습니다.
dan-gph 2011-08-16

2
제 3 자 DLL있는 것은이 솔루션에 큰 단점입니다
루이 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'

RegEx에 도움을 주신 Oliver Schadlich에게 감사드립니다.


같은 경로가 작동하지 않습니다이 참고 somepaththing\.\filename.txt가 그 하나의 점 유지로
마크 Schultheiss

1

경로에 한정자 (드라이브 문자)가 포함 된 경우 Powershell에 대한 x0n의 대답 : 존재하지 않을 수있는 경로를 해결 하시겠습니까? 경로를 정상화합니다. 경로에 한정자가 포함되지 않은 경우에도 정규화되지만 현재 디렉터리에 상대적인 정규화 된 경로를 반환합니다. 이는 원하는 것이 아닐 수 있습니다.

$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는 frog의 하위 폴더입니까?" 아니요. ..은 한 단계 위로 올라간다는 뜻입니다. frag는 fred의 하위 폴더 (또는 파일)입니다.
dan-gph
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.