Una mejor manera de verificar si existe una ruta o no en PowerShell

122

Simplemente no me gusta la sintaxis de:

if (Test-Path $path) { ... }

y

if (-not (Test-Path $path)) { ... }
if (!(Test-Path $path)) { ... }

especialmente hay demasiados paréntesis y no es muy legible cuando se busca "no existe" para un uso tan común. ¿Cuál es una mejor manera de hacer esto?

Actualización: Mi solución actual es usar alias para existy not-existcomo se explica aquí .

Problema relacionado en el repositorio de PowerShell: https://github.com/PowerShell/PowerShell/issues/1970

orad
fuente
2
Podrías usartry{ Test-Path -EA Stop $path; #stuff to do if found } catch { # stuff to do if not found }
Eris
1
Problema
orad

Respuestas:

130

Si solo desea una alternativa a la sintaxis del cmdlet, específicamente para archivos, use el File.Exists()método .NET:

if(![System.IO.File]::Exists($path)){
    # file with path $path doesn't exist
}

Si, por otro lado, desea un alias negado de propósito general para Test-Path, así es como debe hacerlo:

# Gather command meta data from the original Cmdlet (in this case, Test-Path)
$TestPathCmd = Get-Command Test-Path
$TestPathCmdMetaData = New-Object System.Management.Automation.CommandMetadata $TestPathCmd

# Use the static ProxyCommand.GetParamBlock method to copy 
# Test-Path's param block and CmdletBinding attribute
$Binding = [System.Management.Automation.ProxyCommand]::GetCmdletBindingAttribute($TestPathCmdMetaData)
$Params  = [System.Management.Automation.ProxyCommand]::GetParamBlock($TestPathCmdMetaData)

# Create wrapper for the command that proxies the parameters to Test-Path 
# using @PSBoundParameters, and negates any output with -not
$WrappedCommand = { 
    try { -not (Test-Path @PSBoundParameters) } catch { throw $_ }
}

# define your new function using the details above
$Function:notexists = '{0}param({1}) {2}' -f $Binding,$Params,$WrappedCommand

notexistsahora se comportará exactamente igual Test-Path, pero siempre devolverá el resultado opuesto:

PS C:\> Test-Path -Path "C:\Windows"
True
PS C:\> notexists -Path "C:\Windows"
False
PS C:\> notexists "C:\Windows" # positional parameter binding exactly like Test-Path
False

Como ya ha demostrado, lo contrario es bastante fácil, solo alias existspara Test-Path:

PS C:\> New-Alias exists Test-Path
PS C:\> exists -Path "C:\Windows"
True
Mathias R. Jessen
fuente
1
Si $pathes "especial", como en un proveedor de Powershell (piense en HKLM: \ SOFTWARE \ ...) entonces esto fallará miserablemente.
Eris
4
La pregunta de @Eris pide específicamente que se verifique si un archivo existe o no
Mathias R. Jessen
1
Definitivamente, y crear un nuevo cmdlet sobre la marcha es genial. Casi tan inalcanzable como un alias, pero aún así muy ordenado :)
Eris
¡Agradable! Creo que PS debería agregar soporte nativo para esto.
orad
4
@orad Dudo seriamente que consigas que hagan eso. "Demasiados paréntesis" es un razonamiento muy subjetivo y no merece realmente desviarse del diseño / especificación del lenguaje. FWIW, también estoy de acuerdo con la construcción if / else propuesto por @briantist como una mejor alternativa si realmente odias tanto los paréntesis:if(Test-Path $path){}else{ # do your thing }
Mathias R. Jessen
38

La solución de alias que publicaste es inteligente, pero yo argumentaría en contra de su uso en scripts, por la misma razón que no me gusta usar ningún alias en scripts; tiende a dañar la legibilidad.

Si esto es algo que desea agregar a su perfil para que pueda escribir comandos rápidos o usarlo como un shell, entonces puedo ver que tiene sentido.

En su lugar, podría considerar la instalación de tuberías:

if ($path | Test-Path) { ... }
if (-not ($path | Test-Path)) { ... }
if (!($path | Test-Path)) { ... }

Alternativamente, para el enfoque negativo, si es apropiado para su código, puede convertirlo en una verificación positiva y luego usarlo elsepara el negativo:

if (Test-Path $path) {
    throw "File already exists."
} else {
   # The thing you really wanted to do.
}
briantist
fuente
1
Me gusta la tubería aquí, pero sus comprobaciones propuestas para los negativos son incorrectas sin paréntesis, o siempre se evaluará False. Necesitas hacerlo como if (-not ($path | Test-Path)) { ... }.
orad
1
@orad ¡tienes razón! En realidad, eso es un negativo de la tubería en ese caso. Me adormeció una falsa sensación de seguridad al no lanzar una excepción, cuando en realidad estaba fallando. Llamarlo de la forma original arroja una excepción, lo que facilita la detección del problema.
briantist
10

Agregue los siguientes alias. Creo que estos deberían estar disponibles en PowerShell de forma predeterminada:

function not-exist { -not (Test-Path $args) }
Set-Alias !exist not-exist -Option "Constant, AllScope"
Set-Alias exist Test-Path -Option "Constant, AllScope"

Con eso, las declaraciones condicionales cambiarán a:

if (exist $path) { ... }

y

if (not-exist $path)) { ... }
if (!exist $path)) { ... }
orad
fuente
4
Si desea que el equipo de PowerShell agregue un alias de "existe", debe enviar una solicitud de función a través de Microsoft Connect
Mathias R. Jessen
1
Aunque lo respondí yo mismo, acepto la respuesta de @ mathias-r-jessen porque maneja mejor los parámetros.
orad
2

Otra opción es usar, IO.FileInfoque le brinda tanta información de archivo que hace la vida más fácil simplemente usando este tipo:

PS > mkdir C:\Temp
PS > dir C:\Temp\
PS > [IO.FileInfo] $foo = 'C:\Temp\foo.txt'
PS > $foo.Exists
False
PS > New-TemporaryFile | Move-Item -Destination C:\Temp\foo.txt
PS > $foo.Refresh()
PS > $foo.Exists
True
PS > $foo | Select-Object *


Mode              : -a----
VersionInfo       : File:             C:\Temp\foo.txt
                    InternalName:
                    OriginalFilename:
                    FileVersion:
                    FileDescription:
                    Product:
                    ProductVersion:
                    Debug:            False
                    Patched:          False
                    PreRelease:       False
                    PrivateBuild:     False
                    SpecialBuild:     False
                    Language:

BaseName          : foo
Target            : {}
LinkType          :
Length            : 0
DirectoryName     : C:\Temp
Directory         : C:\Temp
IsReadOnly        : False
FullName          : C:\Temp\foo.txt
Extension         : .txt
Name              : foo.txt
Exists            : True
CreationTime      : 2/27/2019 8:57:33 AM
CreationTimeUtc   : 2/27/2019 1:57:33 PM
LastAccessTime    : 2/27/2019 8:57:33 AM
LastAccessTimeUtc : 2/27/2019 1:57:33 PM
LastWriteTime     : 2/27/2019 8:57:33 AM
LastWriteTimeUtc  : 2/27/2019 1:57:33 PM
Attributes        : Archive

Más detalles en mi blog.

VertigoRay
fuente
1

Para verificar si existe una ruta a un directorio, use este:

$pathToDirectory = "c:\program files\blahblah\"
if (![System.IO.Directory]::Exists($pathToDirectory))
{
 mkdir $path1
}

Para verificar si existe una ruta a un archivo, use lo que @Mathias sugirió:

[System.IO.File]::Exists($pathToAFile)
shaheen g
fuente
0

Esta es mi forma novato de powershell de hacer esto

if ((Test-Path ".\Desktop\checkfile.txt") -ne "True") {
    Write-Host "Damn it"
} else {
    Write-Host "Yay"
}
David Bohbot
fuente