PowerShellでパスを正規化する方法は?


94

私には2つのパスがあります。

fred\frog

そして

..\frag

PowerShellで次のように結合できます。

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

それは私にこれを与えます:

fred\frog\..\frag

しかし、私はそれを望んでいません。次のように、二重ドットのない正規化されたパスが必要です。

fred\frag

どうすれば入手できますか?


1
fragはカエルのサブフォルダーですか?そうでない場合、パスを組み合わせると、fred \ frog \ fragになります。もしそうなら、それは非常に異なる質問です。
EBGreen、2009年

回答:


81

あなたは、の組み合わせを使用することができpwdJoin-Pathかつ[System.IO.Path]::GetFullPath完全修飾拡張パスを取得します。

以来cdSet-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-PathpwdGetFullPathちょうどかなりそれを作るためです)。相対部分のみを保持したい場合は、追加.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年

ああ、いいキャッチ、trim呼び出しを追加することで修正できます。お試しくださいcd c:\; "C:\fred\frag".Substring((pwd).Path.Trim('\').Length + 1)。少し長くなってきています。
John Leidegren 2013年

2
または、次のように使用します。$ ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath( "。\ nonexist \ foo.txt")存在しないパスでも機能します。「x0n」は、このbtwのクレジットに値します。彼が指摘するように、それはファイルシステムパスではなくPSPathsに解決されますが、PowerShellでパスを使用している場合、誰が気にしますか?stackoverflow.com/questions/3038337/...
ジョー・コーダ

105

resolve-pathを使用して、.. \ fragを完全なパスに展開できます。

PS > resolve-path ..\frag 

Combine()メソッドを使用してパスを正規化してみてください:

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

あなたのパスがある場合はどうC:\WindowsC:\Windows\ 同じパスが、2つの異なる結果
ジョー・フィリップス

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 2013年

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 2009年

8
あなたの問題は、.NETで現在のディレクトリを設定する必要があることです。 [System.IO.Directory]::SetCurrentDirectory(((Get-Location -PSProvider FileSystem).ProviderPath))
JasonMArcher、2011

2
明確に述べると[IO.Path]::GetFullPath()、PowerShellのネイティブとは異なりResolve-Path、存在しないパスでも機能します。その欠点は、@ JasonMArcherが指摘するように、最初に.NETの作業フォルダーをPSと同期する必要があることです。
mklement0 2013年

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がプロセスの作業ディレクトリと見なしているパスと異なるためです。

また、既にお気づきかもしれませんが、PowerShellのResolve-PathおよびConvert-Pathコマンドレットは、相対パス(「..」を含むもの)をドライブ修飾された絶対パスに変換するのに役立ちますが、参照されるパスが存在しない場合は失敗します。

次の非常に単純なコマンドレットは、存在しないパスに対して機能します。「fred」または「frag」のファイルまたはフォルダーが見つからない場合(および現在のPowerShellドライブが「d:」の場合)でも、「fred \ frog \ .. \ frag」を「d:\ fred \ frag」に変換します。 。

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存在しないドライブをルートとするパスで失敗します。
キースヒル

2
@KeithHill言い換えると、PowerShellは存在しないルート上のパスは無効であると見なします。PowerShellはルートを使用して、どの種類のプロバイダーを使用するかを決定するので、それはかなり合理的だと思います。たとえば、ローカルマシンのレジストリハイブのキーHKLM:\SOFTWAREを参照するPowerShellの有効なパスですSOFTWARE。しかし、それが有効かどうかを判断するには、レジストリパスのルールが何であるかを把握する必要があります。
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 2009年

マイナス2。まだ困惑しています。PowerShellから.Netアセンブリを使用するのが簡単であることを人々が理解していることを願っています。
dan-gph、

これは最善の解決策のようには見えませんが、完全に有効です。
JasonMArcher、2011

@ジェイソン、私は詳細を覚えていませんが、それが私の特定の問題を解決した唯一のソリューションだったので、当時はそれが最良の解決策でした。しかし、それ以来、別のより良い解決策が実現した可能性があります。
dan-gph 2011

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 2009年

2
Get-Itemはうまく機能しました。ただし、このアプローチでも、フォルダが存在する必要があります。
Peter Lillevold、2012年

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、単一のドットを保持するようなパスでは機能しないことに注意してください
Mark Schultheiss '17

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はカエルのサブフォルダーですか?


「fragはカエルのサブフォルダですか?」いいえ。「..」は1レベル上がることを意味します。fragは、fredのサブフォルダー(またはファイル)です。
dan-gph 2009年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.