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

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

fred\frog

и

..\frag

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

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

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

fred\frog\..\frag

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

fred\frag

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

Ответ 1

Вы можете использовать комбинацию 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

UPDATE

Благодаря @Dangph для указания случая края C:\.

Ответ 2

Вы можете развернуть.. \frag до его полного пути с помощью пути решения:

PS > resolve-path ..\frag 

Попробуйте нормализовать путь, используя метод comb():

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

Ответ 3

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

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

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

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

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

D:\fred\frag

Обратите внимание, что этот метод не пытается определить, действительно ли существуют fred или frag.

Ответ 4

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

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

Ответ 5

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

Кроме того, как вы, возможно, уже обнаружили, командлеты PowerShell Resolve-Path и Convert-Path полезны для преобразования относительных путей (те, которые содержат ".." ), к абсолютным путям, определяемым приводом, но они терпят неудачу, если ссылающийся путь не существует.

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

Ответ 6

Эта библиотека хороша: NDepend.Helpers.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. Существует трюк, который вы можете использовать для поиска папки, содержащей текущий выполняемый script, но в моем случае у меня была переменная среды, которую я мог использовать, поэтому я просто использовал это.

Ответ 7

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

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.

Ответ 8

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

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

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

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

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

Ответ 9

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

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

Подождите, может быть, я неправильно понял вопрос. В вашем примере есть фрагмент подпапки лягушки?

Ответ 10

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

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