Как нормализовать путь в PowerShell?


у меня есть два пути:

fred\frog

и

..\frag

Я могу объединить их вместе в PowerShell следующим образом:

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

это дает мне следующее:

fred\frog\..\frag

но я не хочу этого. Я хочу нормализованный путь без двойных точек, как это:

fred\frag

как я могу получить это?

12   74  

12 ответов:

вы можете использовать комбинацию pwd,Join-Path и [System.IO.Path]::GetFullPath чтобы получить полный расширенный путь.

С cd (Set-Location) не изменяет текущий рабочий каталог процесса, просто передавая относительное имя файла в API .NET, который не понимает контекст PowerShell, может иметь непреднамеренные побочные эффекты, такие как разрешение на путь, основанный на исходном рабочем каталоге (а не на вашем текущем местоположении).

вы сначала квалифицировать ваш путь:

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

это дает (учитывая мое текущее местоположение):

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

С абсолютным основанием, ее с уверенностью можно назвать .ЧИСТЫЙ 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

обновление

спасибо @Dangph за указание на C:\ края корпуса.

Вы можете расширить ..\frag к своему полному пути с resolve-path:

PS > resolve-path ..\frag 

попробуйте нормализовать путь с помощью метода combine ():

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

вы также можете использовать путь.GetFullPath, хотя (как и с ответом Dan R) это даст вам весь путь. Использование будет следующим:

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

или более интересно

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

оба из которых дают следующее (предполагая, что ваш текущий каталог на D:\):

D:\fred\frag

обратите внимание, что этот метод не пытается определить, существуют ли Фред или фраг на самом деле.

принятый ответ был большим подспорьем, однако он не правильно "нормализует" абсолютный путь тоже. Найдите ниже мою производную работу, которая нормализует как абсолютные, так и относительные пути.

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

любые функции управления путями, отличные от PowerShell (например, в System.IO.Path), не будут надежными из PowerShell, потому что модель поставщика PowerShell позволяет текущему пути PowerShell отличаться от того, что Windows считает рабочим каталогом процесса.

следующий очень простой командлет должен работать для несуществующих путей. Он преобразует ' fred\frog\..\frag ' to '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($_)
        }
    }
}

эта библиотека хороша: вопросом, что происходит.Прислуга.FileDirectoryPath.

EDIT: вот что я придумал:

[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. Есть трюк, который вы можете использовать, чтобы найти папку, содержащую текущий выполняемый скрипт, но в моем случае у меня была переменная среды, которую я мог использовать, поэтому я просто использовал ее.

создать функцию. Эта функция нормализует путь, который не существует в вашей системе, а также не добавляет буквы дисков.

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'

благодарность выходит Оливер Schadlich за помощью в регулярное выражение.

это дает полный путь:

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

это дает путь относительно текущего каталога:

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

Почему они работают только если frag - это файл, а не directory.

Если путь содержит квалификатор (букву диска), то ответ x0n на Powershell: разрешить путь, который может не существовать? нормализует путь. Если путь не содержит квалификатора, он все равно будет нормализован, но вернет полный путь относительно текущего каталога, который может быть не тем, что вы хотите.

$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

Ну, один из способов будет:

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

подождите, может быть, я неправильно понял вопрос. В вашем примере, является ли frag подпапкой frog?

Если вам нужно избавиться от .. часть, вы можете использовать объект System.IO.DirectoryInfo. Используйте ' fred\frog..\frag ' в конструкторе. Свойство FullName даст вам нормализованное имя каталога.

единственным недостатком является то, что он даст вам весь путь (например c:\test\fred\frag).

целесообразного часть комментариев здесь объединены таким образом, что они объединяют относительные и абсолютные пути:

[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