¿Cómo normalizar una ruta en PowerShell?

94

Tengo dos caminos:

fred\frog

y

..\frag

Puedo unirlos en PowerShell de esta manera:

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

Eso me da esto:

fred\frog\..\frag

Pero yo no quiero eso. Quiero una ruta normalizada sin los puntos dobles, como este:

fred\frag

¿Cómo puedo conseguirlo?

dan-gph
fuente
1
¿Frag es una subcarpeta de frog? Si no, la combinación de la ruta te daría fred \ frog \ frag. Si es así, entonces es una cuestión muy diferente.
EBGreen

Respuestas:

81

Se puede utilizar una combinación de pwd, Join-Pathy [System.IO.Path]::GetFullPathpara obtener una ruta ampliada completo.

Dado que cd( Set-Location) no cambia el directorio de trabajo actual del proceso, simplemente pasar un nombre de archivo relativo a una API .NET que no comprende el contexto de PowerShell puede tener efectos secundarios no deseados, como resolver una ruta basada en el trabajo inicial. directorio (no su ubicación actual).

Lo que haces es primero calificar tu camino:

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

Esto produce (dada mi ubicación actual):

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

Con una base absoluta, es seguro llamar a la API .NET GetFullPath:

[System.IO.Path]::GetFullPath((Join-Path (Join-Path (pwd) fred\frog) '..\frag'))

Lo que le brinda la ruta completamente calificada y con lo ..eliminado:

C:\WINDOWS\system32\fred\frag

Tampoco es complicado, personalmente, desdeño las soluciones que dependen de scripts externos para esto, es un problema simple resuelto con bastante acierto por Join-Pathy pwd( GetFullPathes solo para hacerlo bonito). Si solo desea conservar solo la parte relativa , simplemente agregue .Substring((pwd).Path.Trim('\').Length + 1)y ¡listo!

fred\frag

ACTUALIZAR

Gracias a @Dangph por señalar el C:\caso de borde.

John Leidegren
fuente
El último paso no funciona si pwd es "C: \". En ese caso, obtengo "red \ frag".
dan-gph
@Dangph - No estoy seguro de entender lo que quieres decir, ¿lo anterior parece funcionar bien? ¿Qué versión de PowerShell estás usando? Estoy usando la versión 3.0.
John Leidegren
1
Me refiero a la última etapa: cd c:\; "C:\fred\frag".Substring((pwd).Path.Length + 1). No es gran cosa; solo algo a tener en cuenta.
dan-gph
Ah, buen truco, podríamos solucionarlo agregando una llamada de recorte. Prueba cd c:\; "C:\fred\frag".Substring((pwd).Path.Trim('\').Length + 1). Aunque se está haciendo un poco largo.
John Leidegren
2
O simplemente use: $ ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath (". \ Nonexist \ foo.txt") También funciona con rutas inexistentes. "x0n" merece el crédito por esto por cierto. Como señala, se resuelve en PSPaths, no en rutas de flilesystem, pero si está usando las rutas en PowerShell, ¿a quién le importa? stackoverflow.com/questions/3038337/…
Joe the Coder
106

Puede expandir .. \ frag a su ruta completa con resolve-path:

PS > resolve-path ..\frag 

Intente normalizar la ruta usando el método combine ():

[io.path]::Combine("fred\frog",(resolve-path ..\frag).path)
Shay Levy
fuente
¿Qué pasa si su ruta es C:\Windowscontra C:\Windows\ la misma ruta pero dos resultados diferentes
Joe Phillips
2
Los parámetros de [io.path]::Combineestán invertidos. Mejor aún, use el Join-Pathcomando nativo de PowerShell: Join-Path (Resolve-Path ..\frag).Path 'fred\frog'tenga en cuenta también que, al menos a partir de PowerShell v3, Resolve-Pathahora es compatible con el -Relativeconmutador para resolver una ruta relativa a la carpeta actual. Como se mencionó, Resolve-Pathsolo funciona con rutas existentes, a diferencia de [IO.Path]::GetFullPath().
mklement0
25

También puede usar Path.GetFullPath , aunque (como con la respuesta de Dan R) esto le dará la ruta completa. El uso sería el siguiente:

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

o más interesante

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

ambos producen lo siguiente (asumiendo que su directorio actual es D: \):

D:\fred\frag

Tenga en cuenta que este método no intenta determinar si fred o frag realmente existen.

Charlie
fuente
Eso se está acercando, pero cuando lo intento obtengo "H: \ fred \ frag" a pesar de que mi directorio actual es "C: \ scratch", lo cual es incorrecto. (No debería hacer eso de acuerdo con el MSDN.) Sin embargo, me dio una idea. Lo agregaré como respuesta.
dan-gph
8
Su problema es que necesita configurar el directorio actual en .NET. [System.IO.Directory]::SetCurrentDirectory(((Get-Location -PSProvider FileSystem).ProviderPath))
JasonMArcher
2
Solo para decirlo explícitamente:, a [IO.Path]::GetFullPath()diferencia del nativo de PowerShell Resolve-Path, también funciona con rutas inexistentes. Su desventaja es la necesidad de sincronizar primero la carpeta de trabajo de .NET con la de PS, como señala @JasonMArcher.
mklement0
Join-Pathprovoca una excepción si se refieren a una unidad que no existe.
Tahir Hassan
20

La respuesta aceptada fue de gran ayuda, sin embargo, tampoco 'normaliza' adecuadamente una ruta absoluta. Encuentre a continuación mi trabajo derivado que normaliza rutas absolutas y relativas.

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;
}
Sean Hanna
fuente
Dadas todas las diferentes soluciones, esta funciona para todos los diferentes tipos de rutas. Como ejemplo [IO.Path]::GetFullPath(), no determina correctamente el directorio para un nombre de archivo simple.
Jari Turkia
10

Cualquier función de manipulación de ruta que no sea de PowerShell (como las de System.IO.Path) no será confiable desde PowerShell porque el modelo de proveedor de PowerShell permite que la ruta actual de PowerShell difiera de lo que Windows cree que es el directorio de trabajo del proceso.

Además, como ya habrá descubierto, los cmdlets Resolve-Path y Convert-Path de PowerShell son útiles para convertir rutas relativas (las que contienen '..' s) en rutas absolutas calificadas por la unidad, pero fallan si la ruta a la que se hace referencia no existe.

El siguiente cmdlet muy simple debería funcionar para rutas inexistentes. Convertirá 'fred \ frog \ .. \ frag' a 'd: \ fred \ frag' incluso si no se puede encontrar un archivo o carpeta 'fred' o 'frag' (y la unidad PowerShell actual es 'd:') .

function Get-AbsolutePath {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [string[]]
        $Path
    )

    process {
        $Path | ForEach-Object {
            $PSCmdlet.SessionState.Path.GetUnresolvedProviderPathFromPSPath($_)
        }
    }
}
Jason Stangroome
fuente
2
Esto no funciona para rutas inexistentes donde la letra de la unidad no existe, por ejemplo, no tengo unidad Q :. Get-AbsolutePath q:\foo\bar\..\bazfalla a pesar de que es una ruta válida. Bueno, dependiendo de su definición de una ruta válida. :-) FWIW, incluso el integrado Test-Path <path> -IsValidfalla en rutas enraizadas en unidades que no existen.
Keith Hill
2
@KeithHill En otras palabras, PowerShell considera que una ruta en una raíz inexistente no es válida. Creo que eso es bastante razonable ya que PowerShell usa la raíz para decidir qué tipo de proveedor usar cuando se trabaja con él. Por ejemplo, HKLM:\SOFTWAREes una ruta válida en PowerShell, que hace referencia a la SOFTWAREclave en el subárbol de registro de la máquina local. Pero para averiguar si es válido, necesita averiguar cuáles son las reglas para las rutas de registro.
jpmc26
3

Esta biblioteca es buena: NDepend.Helpers.FileDirectoryPath .

EDITAR: Esto es lo que se me ocurrió:

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

Llámalo así:

> NormalizePath "fred\frog\..\frag"
fred\frag

Tenga en cuenta que este fragmento requiere la ruta a la DLL. Hay un truco que puede usar para encontrar la carpeta que contiene el script que se está ejecutando actualmente, pero en mi caso tenía una variable de entorno que podía usar, así que simplemente la usé.

dan-gph
fuente
No sé por qué eso recibió un voto negativo. Esa biblioteca es realmente buena para realizar manipulaciones de ruta. Es lo que terminé usando en mi proyecto.
dan-gph
Menos 2. Aún desconcertado. Espero que la gente se dé cuenta de que es fácil de usar los ensamblados .Net de PowerShell.
dan-gph
Esta no parece la mejor solución, pero es perfectamente válida.
JasonMArcher
@Jason, no recuerdo los detalles, pero en ese momento fue la mejor solución porque fue la única que resolvió mi problema particular. Sin embargo, es posible que desde entonces haya surgido otra solución mejor.
dan-gph
2
tener una DLL de terceros es un gran inconveniente para esta solución
Louis Kottmann
1

Esto da la ruta completa:

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

Esto da la ruta relativa al directorio actual:

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

Por alguna razón, solo funcionan si frages un archivo, no un directory.

dan-gph
fuente
1
gci es un alias de get-childitem. Los hijos de un directorio son su contenido. Reemplace gci con gi y debería funcionar para ambos.
zdan
2
Get-Item funcionó muy bien. Pero nuevamente, este enfoque requiere que existan las carpetas.
Peter Lillevold
1

Crea una función. Esta función normalizará una ruta que no existe en su sistema y no agregará letras de unidades.

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'

Gracias a Oliver Schadlich por su ayuda en la RegEx.

M. Hubers
fuente
Tenga en cuenta que esto no funciona para rutas como, somepaththing\.\filename.txtya que mantiene ese único punto
Mark Schultheiss
1

Si la ruta incluye un calificador (letra de unidad), entonces la respuesta de x0n a Powershell: ¿resolver la ruta que podría no existir? normalizará el camino. Si la ruta no incluye el calificador, aún se normalizará, pero devolverá la ruta completamente calificada en relación con el directorio actual, que puede no ser lo que desea.

$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
fuente
0

Si necesita deshacerse de la parte .., puede usar un objeto System.IO.DirectoryInfo. Use 'fred \ frog .. \ frag' en el constructor. La propiedad FullName le dará el nombre del directorio normalizado.

El único inconveniente es que le dará la ruta completa (por ejemplo, c: \ test \ fred \ frag).

Dan R
fuente
0

Las partes convenientes de los comentarios aquí se combinan de manera que unifican caminos relativos y absolutos:

[System.IO.Directory]::SetCurrentDirectory($pwd)
[IO.Path]::GetFullPath($dapath)

Algunas muestras:

$fps = '.', 'file.txt', '.\file.txt', '..\file.txt', 'c:\somewhere\file.txt'
$fps | % { [IO.Path]::GetFullPath($_) }

salida:

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
fuente
-1

Bueno, una forma sería:

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

Espera, quizás no entiendo bien la pregunta. En su ejemplo, ¿frag es una subcarpeta de frog?

EBGreen
fuente
"¿frag es una subcarpeta de rana?" No. El ... significa subir un nivel. frag es una subcarpeta (o un archivo) en fred.
dan-gph