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

96

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

fred\frog

а также

..\frag

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

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

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

fred\frog\..\frag

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

fred\frag

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

дан-гф
источник
1
Frag - это вложенная папка frog? Если нет, то объединение путей даст вам fred \ frog \ frag. Если да, то это совсем другой вопрос.
EBGreen

Ответы:

83

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

fred\frag

ОБНОВИТЬ

Спасибо @Dangph за указание на крайний C:\случай.

Джон Лейдегрен
источник
Последний шаг не работает, если pwd равен «C: \». В этом случае я получаю "красный \ фраг".
dan-gph
@Dangph - Не уверен, что я понимаю, что вы имеете в виду, похоже, что все работает нормально? Какую версию PowerShell вы используете? Пользуюсь версией 3.0.
Джон Лейдегрен
1
Я имею в виду последний шаг: cd c:\; "C:\fred\frag".Substring((pwd).Path.Length + 1). Это не имеет большого значения; просто то, о чем нужно знать.
dan-gph
Ах, хороший улов, мы могли бы исправить это, добавив вызов обрезки. Попробуй cd c:\; "C:\fred\frag".Substring((pwd).Path.Trim('\').Length + 1). Хотя это становится довольно долгим.
Джон Лейдегрен
2
Или просто используйте: $ ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath (". \ Nonexist \ foo.txt") Также работает с несуществующими путями. "x0n" заслуживает похвалы за это, кстати. Как он отмечает, он разрешается в PSPaths, а не в пути к файловой системе, но кого это волнует, если вы используете пути в PowerShell? stackoverflow.com/questions/3038337/…
Кодировщик Джо
106

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

PS > resolve-path ..\frag 

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

[io.path]::Combine("fred\frog",(resolve-path ..\frag).path)
Шэй Леви
источник
Что делать , если ваш путь C:\Windowsпротив C:\Windows\ того же пути , но два разных результатов
Joe Phillips
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
25

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

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

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

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

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

D:\fred\frag

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

Чарли
источник
Это уже близко, но когда я пытаюсь это сделать, я получаю «H: \ fred \ frag», хотя мой текущий каталог - «C: \ scratch», что неверно. (Согласно MSDN этого не должно быть.) Однако это подсказало мне идею. Я добавлю это как ответ.
dan-gph
8
Ваша проблема в том, что вам нужно установить текущий каталог в .NET. [System.IO.Directory]::SetCurrentDirectory(((Get-Location -PSProvider FileSystem).ProviderPath))
JasonMArcher
2
Просто чтобы указать это явно: в [IO.Path]::GetFullPath()отличие от собственного PowerShell Resolve-Path, он также работает с несуществующими путями. Его недостатком является необходимость сначала синхронизировать рабочую папку .NET с PS, как указывает @JasonMArcher.
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(), некорректно определяется каталог для простого имени файла.
Яри
10

Любые функции манипулирования путями, отличные от 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($_)
        }
    }
}
Джейсон Стэнгроум
источник
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
Минус 2. По-прежнему озадачился. Я надеюсь, что люди осознают, что использовать сборки .Net из PowerShell просто.
dan-gph
Это не кажется лучшим решением, но вполне справедливо.
JasonMArcher
@ Джейсон, я не помню подробностей, но в то время это было лучшее решение, потому что оно было единственным, которое решило мою конкретную проблему. Однако возможно, что с тех пор появилось другое, лучшее решение.
dan-gph
2
наличие сторонней DLL является большим недостатком этого решения
Луис Коттманн
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 работал хорошо. Но опять же, этот подход требует, чтобы папки существовали.
Питер Лиллевольд
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.

М. Хуберс
источник
Обратите внимание, что это не работает для таких путей, как то, somepaththing\.\filename.txtчто он сохраняет эту единственную точку
Марк Шультайс
1

Если путь включает квалификатор (букву диска), то ответ 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
WileCau
источник
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
TNT
источник
-1

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

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

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

EBGreen
источник
"Frag - это вложенная папка frog?" Нет .. означает подняться на один уровень. frag - это подпапка (или файл) в fred.
dan-gph