Lepszy sposób sprawdzenia, czy ścieżka istnieje, czy nie w PowerShell

124

Po prostu nie podoba mi się składnia:

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

i

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

zwłaszcza, że ​​jest zbyt wiele nawiasów i niezbyt czytelne podczas sprawdzania „nie istnieje” dla tak powszechnego zastosowania. Jaki jest lepszy sposób na zrobienie tego?

Aktualizacja: moim obecnym rozwiązaniem jest użycie aliasów dla existi not-existzgodnie z wyjaśnieniem tutaj .

Powiązany problem w repozytorium PowerShell: https://github.com/PowerShell/PowerShell/issues/1970

orad
źródło
2
Możesz użyćtry{ Test-Path -EA Stop $path; #stuff to do if found } catch { # stuff to do if not found }
Eris

Odpowiedzi:

131

Jeśli potrzebujesz tylko alternatywy dla składni poleceń cmdlet, szczególnie dla plików, użyj File.Exists()metody .NET:

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

Jeśli z drugiej strony chcesz mieć zanegowany alias ogólnego przeznaczenia Test-Path, oto jak powinieneś to zrobić:

# 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

notexistsbędzie się teraz zachowywać dokładnie tak Test-Path, jak , ale zawsze zwraca odwrotny wynik:

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

Jak już pokazano siebie, przeciwnie jest bardzo proste, wystarczy alias existsdo Test-Path:

PS C:\> New-Alias exists Test-Path
PS C:\> exists -Path "C:\Windows"
True
Mathias R. Jessen
źródło
1
Jeśli $pathjest „specjalny”, jak w przypadku dostawcy Powershell (pomyśl o HKLM: \ SOFTWARE \ ...), to zakończy się niepowodzeniem.
Eris
4
Pytanie @Eris konkretnie prosi o sprawdzenie, czy plik istnieje, czy nie
Mathias R. Jessen
1
Zdecydowanie i tworzenie nowego polecenia cmdlet w locie jest przyjemne. Prawie tak nie do utrzymania jak alias, ale wciąż naprawdę fajny :)
Eris
Miły! Myślę, że PS powinien dodać do tego natywne wsparcie.
orad
4
@orad Poważnie wątpię, czy ich do tego zmusisz. „Zbyt wiele nawiasów” to bardzo subiektywne rozumowanie i tak naprawdę nie zasługuje na odstępstwo od projektu / specyfikacji języka. FWIW, zgadzam się również z konstrukcją if / else zaproponowaną przez @briantist jako lepszą alternatywą, jeśli naprawdę nienawidzisz nawiasów:if(Test-Path $path){}else{ # do your thing }
Mathias R. Jessen
38

Rozwiązanie aliasu, które opublikowałeś, jest sprytne, ale sprzeciwiłbym się jego używaniu w skryptach, z tego samego powodu, dla którego nie lubię używać żadnych aliasów w skryptach; zwykle szkodzi czytelności.

Jeśli jest to coś, co chcesz dodać do swojego profilu, aby móc wypisywać szybkie polecenia lub używać go jako powłoki, to widzę, że ma to sens.

Zamiast tego możesz rozważyć orurowanie:

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

Alternatywnie, w przypadku podejścia negatywnego, jeśli jest to odpowiednie dla twojego kodu, możesz sprawić, że będzie to pozytywna kontrola, a następnie użyj elsedla negatywu:

if (Test-Path $path) {
    throw "File already exists."
} else {
   # The thing you really wanted to do.
}
briantist
źródło
1
Podoba mi się orurowanie tutaj, ale proponowane przez Ciebie sprawdzenia negatywów są niepoprawne bez nawiasów lub zawsze będą oceniane do False. Musisz to zrobić jak if (-not ($path | Test-Path)) { ... }.
orad
1
@orad masz rację! Właściwie to jest minusem rurociągów w tym przypadku. Zostałem uśpiony w fałszywym poczuciu bezpieczeństwa przez to, że nie rzucało wyjątku, kiedy faktycznie zawodziło. Nazwanie tego w oryginalny sposób zgłasza wyjątek, ułatwiając wykrycie problemu.
briantist
10

Dodaj następujące aliasy. Myślę, że powinny one być domyślnie dostępne w PowerShell:

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

W ten sposób instrukcje warunkowe zmienią się na:

if (exist $path) { ... }

i

if (not-exist $path)) { ... }
if (!exist $path)) { ... }
orad
źródło
4
Jeśli chcesz, aby zespół PowerShell dodał alias „istnieje”, prześlij żądanie funkcji za pośrednictwem Microsoft Connect
Mathias R. Jessen
1
Mimo że odebrał sobie, przyjmuję @ Mathias-R-Jessen za odpowiedź , ponieważ obsługuje parametry lepsze.
orad
2

Inną opcją jest użycie, IO.FileInfoktóre daje tak dużo informacji o pliku, że ułatwia życie po prostu używając tego typu:

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

Więcej szczegółów na moim blogu.

VertigoRay
źródło
1

Aby sprawdzić, czy istnieje ścieżka do katalogu, użyj tego:

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

Aby sprawdzić, czy ścieżka do pliku istnieje, skorzystaj z sugestii @Mathias :

[System.IO.File]::Exists($pathToAFile)
shaheen g
źródło
0

To jest mój nowicjusz w PowerShell

if ((Test-Path ".\Desktop\checkfile.txt") -ne "True") {
    Write-Host "Damn it"
} else {
    Write-Host "Yay"
}
David Bohbot
źródło