Як нормалізувати шлях у PowerShell?


94

У мене є два шляхи:

fred\frog

і

..\frag

Я можу приєднати їх до PowerShell так:

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

Це дає мені таке:

fred\frog\..\frag

Але я цього не хочу. Я хочу нормалізований шлях без подвійних крапок, наприклад:

fred\frag

Як я можу це отримати?


1
Frag - це підпапка жаби? Якщо ні, тоді комбінування шляху дасть вам fred \ frog \ frag. Якщо це тоді, то це зовсім інше питання.
EBGreen

Відповіді:


81

Ви можете використовувати комбінацію 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, а не шляхи flilesystem, але якщо ви використовуєте шляхи в 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\ одного шляху, але два різні результати
Джо Філліпс,

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

Зверніть увагу, що цим методом не намагаються визначити, чи насправді існують фред чи фраг.


Це наближається, але коли я спробую, я отримую "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викликає виняток, якщо посилаються на диск, який не існує.
Тахір Хасан,

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

10

Будь-які функції обробки шляхів, не пов'язані з PowerShell (наприклад, ті, що в System.IO.Path), не будуть надійними від PowerShell, оскільки модель постачальника PowerShell дозволяє поточному шляху PowerShell відрізнятися від того, чим Windows вважає робочий каталог процесу.

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

Наступний дуже простий командлет повинен працювати для неіснуючих шляхів. Він перетворить '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 використовує root, щоб вирішити, якого постачальника використовувати під час роботи з ним. Наприклад, HKLM:\SOFTWAREце дійсний шлях у PowerShell, посилаючись на SOFTWAREключ у вулику реєстру Local Machine. Але щоб з’ясувати, чи він дійсний, йому потрібно з’ясувати, якими є правила для шляхів реєстру.
jpmc26

3

Ця бібліотека хороша: 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. Існує хитрість, за допомогою якої можна знайти папку, що містить виконуваний в даний час скрипт, але в моєму випадку у мене була змінна середовища, яку я міг використовувати, тому я її просто використав.


Не знаю, чому це отримало голос проти. Ця бібліотека дійсно хороша для маніпуляцій із шляхами. Це те, що я в підсумку використав у своєму проекті.
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

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 - це підпапка жаби?" Ні. Засоби .. піднімаються на один рівень. frag - це підпапка (або файл) у fred.
dan-gph
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.