【问题标题】:A better way to check if a path exists or not in PowerShell [closed]在PowerShell中检查路径是否存在的更好方法[关闭]
【发布时间】:2015-10-31 12:56:06
【问题描述】:

在 PowerShell 中是否有更简洁且不易出错的方法来检查路径是否不存在?

对于这样一个常见的用例来说,这客观上太冗长了:

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

它需要太多的括号并且在检查“不存在”时不是很可读。这也容易出错,因为这样的语句:

if (-not $non_existent_path | Test-Path) { $true } else { $false }

实际上会返回False,而用户可能期望True

有什么更好的方法来做到这一点?

更新 1:我目前的解决方案是为 existnot-exist 使用别名,如 here 所述。

更新 2: 也可以解决此问题的建议语法是允许以下语法:

if !(expr) { statements* }
if -not (expr) { statements* }

这是 PowerShell 存储库中的相关问题(请投票 ????):https://github.com/PowerShell/PowerShell/issues/1970

【问题讨论】:

标签: powershell


【解决方案1】:

如果您只是想要替代 cmdlet 语法,特别是对于文件,请使用 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

【讨论】:

  • 如果$path 是“特殊的”,例如在 Powershell Provider 上(想想 HKLM:\SOFTWARE\...),那么这将失败。
  • @Eris 问题专门要求检查 file 是否存在
  • 毫无疑问,动态创建一个新的 cmdlet 非常简洁。几乎和别名一样难以维护,但仍然非常整洁:)
  • 不错!我认为 PS 应该为此添加原生支持。
  • @orad 我严重怀疑你会让他们这样做。 “括号太多”是一种非常主观的推理,并不值得偏离语言设计/规范。 FWIW,如果您真的非常讨厌括号,我也同意 @briantist 提出的 if/else 构造作为更好的选择:if(Test-Path $path){}else{ # do your thing }
【解决方案2】:

您发布的别名解决方案很聪明,但我反对在脚本中使用它,出于同样的原因,我不喜欢在脚本中使用任何别名;它往往会损害可读性。

如果这是您想要添加到个人资料中的内容,以便您可以输入快速命令或将其用作外壳,那么我认为这是有道理的。

您可以考虑改为管道:

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

【讨论】:

  • 我喜欢这里的管道,但是你建议的负数检查没有括号是不正确的,否则它总是会评估为False。你需要像 if (-not ($path | Test-Path)) { ... } 那样做。
  • @orad 你是对的!实际上,在这种情况下,这是管道的负面影响。当它实际上失败时,它没有抛出异常,这让我陷入了一种错误的安全感。以原始方式调用它会引发异常,从而更容易发现问题。
【解决方案3】:

添加以下别名。我认为这些应该默认在 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) { ... }

【讨论】:

  • 如果您希望 PowerShell 团队添加“现有”别名,您应该通过 Microsoft Connect 提交功能请求
  • 虽然我自己回答了,但我接受@mathias-r-jessen 的answer,因为它可以更好地处理参数。
【解决方案4】:

这是我的 powershell 新手的做法

if ((Test-Path ".\Desktop\checkfile.txt") -eq $true) {
    Write-Host "Yay"
} 
else {
    Write-Host "Damn it"
}

【讨论】:

    【解决方案5】:

    另一种选择是使用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
    

    More details on my blog.

    【讨论】:

      【解决方案6】:

      要检查目录是否存在路径,请使用此路径:

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

      要检查文件路径是否存在,请使用@Mathias 建议的内容:

      [System.IO.File]::Exists($pathToAFile)
      

      【讨论】:

        【解决方案7】:
        if (Test-Path C:\DockerVol\SelfCertSSL) {
            write-host "Folder already exists."
        } else {
           New-Item -Path "C:\DockerVol\" -Name "SelfCertSSL" -ItemType "directory"
        }
        

        【讨论】:

        • 请添加更多解释。
        • 请不要只发布代码作为答案,还要解释您的代码的作用以及它如何解决问题的问题。带有解释的答案通常更有帮助,质量更高,更有可能吸引投票。
        【解决方案8】:

        看了@Mathias R. Jessen 的出色回答后,我想到您不需要创建两个新函数。相反,您可以围绕原生 Test-Path 函数创建一个包装器,其名称与添加 -Not 开关的名称相同:

        $TestPathCmd = Get-Command Test-Path
        $TestPathCmdMetaData = New-Object System.Management.Automation.CommandMetadata $TestPathCmd
        $Binding = [System.Management.Automation.ProxyCommand]::GetCmdletBindingAttribute($TestPathCmdMetaData)
        $Params  = [System.Management.Automation.ProxyCommand]::GetParamBlock($TestPathCmdMetaData)
        
        $Params += ', [Switch]${Not}'
        $WrappedCommand = {
            $PSBoundParameters.Remove('Not') | Out-Null
            [Bool]($Not.ToBool() -bxor (Microsoft.PowerShell.Management\Test-Path @PSBoundParameters))
        }
        
        ${Function:Test-Path} = '{0} Param({1}) {2}' -f $Binding,$Params,$WrappedCommand
        

        例如:

        Test-Path -Path 'C:\Temp'      # True
        Test-Path -Path 'C:\Temp' -Not # False
        Test-Path -Path 'C:\Txmp'      # False
        Test-Path -Path 'C:\Txmp' -Not # True
        

        这有几个优点:

        1. 熟悉的语法:当您不使用自定义开关时,语法与本机命令相同,当您使用时,所发生的事情非常直观,这意味着用户的认知负担更小,共享时的兼容性更高。
        2. 因为包装器在后台调用本机函数,所以它可以在本机函数所做的任何地方工作,例如:
          Test-Path -Path 'HKLM:\SOFTWARE'      # True
          Test-Path -Path 'HKLM:\SOFTWARE' -Not # False
          Test-Path -Path 'HKLM:\SXFTWARE'      # False
          Test-Path -Path 'HKLM:\SXFTWARE' -Not # True
          

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2019-04-03
          • 1970-01-01
          • 2011-05-30
          • 1970-01-01
          • 1970-01-01
          • 2020-05-08
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多