A melhor maneira de verificar se um caminho existe ou não no PowerShell

123

Só não gosto da sintaxe de:

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

e

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

especialmente, há muitos parênteses e não é muito legível ao verificar "não existe" para um uso tão comum. Qual é a melhor maneira de fazer isso?

Atualização: Minha solução atual é usar aliases para existe not-existconforme explicado aqui .

Problema relacionado no repositório do PowerShell: https://github.com/PowerShell/PowerShell/issues/1970

orad
fonte
2
Você poderia usartry{ Test-Path -EA Stop $path; #stuff to do if found } catch { # stuff to do if not found }
Eris
1
Problema relacionado: github.com/PowerShell/PowerShell/issues/1970
orad

Respostas:

130

Se você deseja apenas uma alternativa à sintaxe do cmdlet, especificamente para arquivos, use o File.Exists()método .NET:

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

Se, por outro lado, você quiser um alias negado de uso geral para Test-Path, aqui está como você deve fazer isso:

# 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

notexistsagora se comportará exatamente como Test-Path, mas sempre retornará o resultado oposto:

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 você já mostrou, o oposto é bastante fácil, apenas um alias existspara Test-Path:

PS C:\> New-Alias exists Test-Path
PS C:\> exists -Path "C:\Windows"
True
Mathias R. Jessen
fonte
1
Se $pathfor "especial", como em um provedor Powershell (pense em HKLM: \ SOFTWARE \ ...), isso irá falhar terrivelmente.
Eris
4
A pergunta do @Eris pede especificamente para verificar se um arquivo existe ou não
Mathias R. Jessen
1
Definitivamente, criar um novo cmdlet dinamicamente é legal. Quase tão insustentável quanto um pseudônimo, mas ainda assim muito legal :)
Eris
Agradável! Acho que o PS deve adicionar suporte nativo para isso.
orad
4
@orad, duvido seriamente que você os faça fazer isso. "Muitos parênteses" é um raciocínio muito subjetivo e não merece realmente se desviar do design / especificação da linguagem. FWIW, também concordo com a construção if / else proposta por @briantist como uma alternativa melhor se você realmente odeia tanto parênteses:if(Test-Path $path){}else{ # do your thing }
Mathias R. Jessen
38

A solução de alias que você postou é inteligente, mas eu argumentaria contra seu uso em scripts, pela mesma razão que não gosto de usar apelidos em scripts; isso tende a prejudicar a legibilidade.

Se isso é algo que você deseja adicionar ao seu perfil para poder digitar comandos rápidos ou usá-lo como um shell, então posso ver que faz sentido.

Você pode considerar tubulação em vez disso:

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

Como alternativa, para a abordagem negativa, se apropriado para o seu código, você pode torná-la uma verificação positiva e usar elsepara a negativa:

if (Test-Path $path) {
    throw "File already exists."
} else {
   # The thing you really wanted to do.
}
briantista
fonte
1
Eu gosto do encanamento aqui, mas suas verificações propostas para negativos estão incorretas sem parênteses, ou sempre avaliará False. Você precisa fazer como if (-not ($path | Test-Path)) { ... }.
orad
1
@orad você está correto! Na verdade, isso é um negativo de tubulação nesse caso. Fui iludido por uma falsa sensação de segurança por ele não lançar uma exceção, quando na verdade estava falhando. Chamar da maneira original gera uma exceção, tornando mais fácil detectar o problema.
briantist
10

Adicione os seguintes aliases. Acho que devem estar disponíveis no PowerShell por padrão:

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

Com isso, as declarações condicionais mudarão para:

if (exist $path) { ... }

e

if (not-exist $path)) { ... }
if (!exist $path)) { ... }
orad
fonte
4
Se quiser que a equipe do PowerShell adicione um alias "existente", você deve enviar uma solicitação de recurso pelo Microsoft Connect
Mathias R. Jessen
1
Embora eu mesmo tenha respondido, aceito a resposta de @mathias-r-jessen porque ele lida melhor com os parâmetros.
orad
2

Outra opção é usar o IO.FileInfoque fornece tantas informações sobre o arquivo que torna a vida mais fácil apenas com 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

Mais detalhes no meu blog.

VertigoRay
fonte
1

Para verificar se existe um caminho para um diretório, use este:

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

Para verificar se existe um caminho para um arquivo, use o que @Mathias sugeriu:

[System.IO.File]::Exists($pathToAFile)
Shaheen g
fonte
0

Esta é a minha maneira de novato com poderes de fazer isso

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