Лучший способ проверить, существует ли путь или нет в PowerShell

124

Мне просто не нравится синтаксис:

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

и

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

особенно здесь слишком много скобок, и они не очень удобочитаемы при проверке «не существует» для такого обычного использования. Как лучше это сделать?

Обновление: мое текущее решение - использовать псевдонимы для existи, not-existкак описано здесь .

Связанная проблема в репозитории PowerShell: https://github.com/PowerShell/PowerShell/issues/1970

Orad
источник
2
Вы могли бы использоватьtry{ Test-Path -EA Stop $path; #stuff to do if found } catch { # stuff to do if not found }
Эрис
1
Связанная проблема: github.com/PowerShell/PowerShell/issues/1970
orad

Ответы:

131

Если вам просто нужна альтернатива синтаксису командлета, особенно для файлов, используйте File.Exists()метод .NET:

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

Если, с другой стороны, вам нужен отрицательный псевдоним общего назначения для Test-Path, вот как вы должны это сделать:

# 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

notexistsтеперь будет вести себя точно так же Test-Path, но всегда будет возвращать противоположный результат:

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

Как вы уже показали себя, наоборот довольно легко, просто псевдоним existsдля Test-Path:

PS C:\> New-Alias exists Test-Path
PS C:\> exists -Path "C:\Windows"
True
Матиас Р. Джессен
источник
1
Если $pathон «особенный», например, на Powershell Provider (подумайте, HKLM: \ SOFTWARE \ ...), то это с треском провалится.
Эрис
4
Вопрос @Eris, в частности, просит проверить, существует ли файл или нет
Матиас Р. Джессен
1
Определенно, и создание нового командлета «на лету» - это удобно. Почти так же трудно поддерживать, как псевдоним, но все же действительно изящно :)
Эрис
Ницца! Я думаю, PS должен добавить для этого нативную поддержку.
orad 08
4
@orad Я серьезно сомневаюсь, что ты заставишь их это сделать. «Слишком много круглых скобок» - это очень субъективное рассуждение, которое на самом деле не заслуживает отклонения от дизайна / спецификации языка. FWIW, я также согласен с конструкцией if / else, предложенной @briantist в качестве лучшей альтернативы, если вы действительно так ненавидите круглые скобки:if(Test-Path $path){}else{ # do your thing }
Матиас Р. Джессен
38

Опубликованное вами решение для псевдонимов является умным, но я бы возражал против его использования в сценариях по той же причине, по которой я не люблю использовать псевдонимы в сценариях; это имеет тенденцию ухудшать читаемость.

Если это то, что вы хотите добавить в свой профиль, чтобы вы могли набирать быстрые команды или использовать его как оболочку, тогда я мог бы увидеть в этом смысл.

Вместо этого вы можете подумать о трубопроводе:

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

В качестве альтернативы, для отрицательного подхода, если это подходит для вашего кода, вы можете сделать положительную проверку, а затем использовать elseдля отрицательной:

if (Test-Path $path) {
    throw "File already exists."
} else {
   # The thing you really wanted to do.
}
бриантист
источник
1
Мне здесь нравится трубопровод, но предлагаемые вами проверки на негативы без скобок неверны или всегда будут соответствовать False. Вам нужно сделать это как if (-not ($path | Test-Path)) { ... }.
orad 08
1
@orad ты прав! На самом деле в этом случае это недостаток трубопровода. Меня убаюкивало ложное чувство безопасности из-за того, что он не генерировал исключения, хотя на самом деле он не работал. Вызов его оригинальным способом вызывает исключение, что упрощает выявление проблемы.
бриантист
10

Добавьте следующие псевдонимы. Я думаю, что по умолчанию они должны быть доступны в PowerShell:

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

При этом условные операторы изменятся на:

if (exist $path) { ... }

и

if (not-exist $path)) { ... }
if (!exist $path)) { ... }
Orad
источник
4
Если вы хотите, чтобы команда PowerShell добавила псевдоним «Существующий», вам следует отправить запрос функции через Microsoft Connect
Матиас Р. Джессен
1
Несмотря на то, ответил я это сам, я принимаю @ Матиас-R-ЕССЕН в ответ , потому что она обрабатывает параметры лучше.
orad
2

Другой вариант - использовать, IO.FileInfoкоторый дает вам столько информации о файлах, что упрощает жизнь, просто используя этот тип:

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

Подробнее в моем блоге.

VertigoRay
источник
1

Чтобы проверить, существует ли путь к каталогу, используйте этот:

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

Чтобы проверить, существует ли путь к файлу, используйте то, что предложил @Mathias :

[System.IO.File]::Exists($pathToAFile)
Shaheen G
источник
1

Это мой способ новичка в PowerShell сделать это

if ((Test-Path ".\Desktop\checkfile.txt") -ne "True") {
    Write-Host "Damn it"
} else {
    Write-Host "Yay"
}
Дэвид Бохбот
источник