【问题标题】:How do I check if all the returned values are true?如何检查所有返回值是否为真?
【发布时间】:2016-07-11 18:13:26
【问题描述】:

有没有一种简单的方法可以确保所有返回的值都是 True?在下面的示例中,我有一组代表文件的对象。在继续之前,我想确保所有源文件都存在。我将所有路径传递给 Test-Path,该函数为每个文件返回 True/False。

    > $filesToUpdate = @(
      [PsCustomObject] @{ Source = (Join-Path $basePath "Abc.module"); }
      [PsCustomObject] @{ Source = (Join-Path $basePath "Def.module"); }
    )

    > $filesToUpdate.Source | Test-Path

    True
    True

    > # How can I check if all the returned values are true?

如何检查所有返回值是否为真?

【问题讨论】:

    标签: powershell


    【解决方案1】:

    管道使用的更广泛视角补充FoxDeploy's helpful answer

    tl;博士:

    • (Test-Path $filesToUpdate.Source) -contains $false:

      • 速度快,但不适合非常大的集合,因为输入集合必须作为一个整体适合内存(在本问题的上下文中它会这样做)。

      • Test-Path 接受一个数组作为输入,并输出一个反映每个输入项存在的并行数组; -contains 测试 LHS 数组中 RHS 标量的成员资格。

      • 此解决方案在概念上最简单、最快(与未优化的管道使用相比)。
    • $false -ne ($filesToUpdate.Source | Test-Path | ? { -not $_ } | Select-Object -First 1)

      • 如果输入集合太大而无法放入整个内存中,则必须采用这种方法/内存使用必须保持不变。

        • 注意:这里,管道输入$filesToUpdate.Source 定义为内存中的集合,但所讨论的场景适用于命令,它们会逐个生成大量输出对象 /em> 并将它们发送到管道中。
      • ? { -not $_ }(缩写为:Where-Object { -not $_ })过滤由Test-Path 输出的布尔值以仅包含$false 值(如果有)。因此,只有存在至少一个 $false 值时,过滤器才会产生输出。

      • Select-Object -First 1 通过在收到第一个对象(第一个 $false 值)(如果有)后退出管道来优化处理 [PSv3+],这意味着输出要么是单个 $false 值,要么没有输出完全没有。

        • 此优化的性能影响完全取决于输入数据,范围从显着(大型输入集合中的第一个项映射到$false)到无(没有项映射到@ 987654336@)。
      • $false -ne ... 然后测试管道的输出 not 是否为 $false,这意味着 Test-Path所有 输入路径返回了 $true

        • 请注意,必须使用与$false 的显式比较,因为-not (...) / ! (...)不会按预期工作,因为否定产生没有输出也会产生$true

    一般来说,管道是 PowerShell 不可或缺的一个强大概念,即使它们引入了处理开销,但它们的概念优雅值得使用,除非它们存在性能问题。强>

    • 如果性能至关重要,则可以绕过管道,但这可能很麻烦。

    • 相反,PowerShell 灵活的 运算符 有时会提供概念上既优雅快速的解决方案,但这里就是这种情况可能会以内存消耗为代价。
      在这种情况下使用管道的唯一原因是处理大型输入集合。

    为了让您了解相对性能,以下是比较解决方案的测试时间,根据输入集合的大小和平均运行次数进行参数化。

    在底部找到测试脚本。

    请注意,输入数据的构造是将第一个(也是唯一一个)不存在的路径放在输入集合的中间

    此选择会极大地影响Select-Object -First 1 解决方案的性能: 如果你在开头放置一条不存在的路径,它会表现最好,如果你把它放在最后或根本不包含一个,则不会有任何表现再次(相反)。


    我的机器(2012 年末 iMac)中的样本数字,以秒为单位:

    > .\Test.ps1 -count 10 -repeat 10  # 10 input items, timing averaged over 10 runs
    
    Command                                           10-run average
    -------                                           --------------
    -contains, no pipeline                            .00124
    -contains, pipeline                               .00170
    pipeline, where-object, select -first 1           .00276
    pipeline, where-object                            .00314
    pipeline, where-object, Test-Path in script block .00460
    
    > .\Test.ps1 -count 100 -repeat 10
    
    Command                                           10-run average
    -------                                           --------------
    -contains, no pipeline                            .01190
    pipeline, where-object, select -first 1           .01328
    -contains, pipeline                               .01836
    pipeline, where-object                            .02365
    pipeline, where-object, Test-Path in script block .03725
    
    > .\Test.ps1 -count 1000 -repeat 10
    
    Command                                           10-run average
    -------                                           --------------
    pipeline, where-object, select -first 1           .11154
    -contains, no pipeline                            .11764
    -contains, pipeline                               .16508
    pipeline, where-object                            .22246
    pipeline, where-object, Test-Path in script block .37015
    
    > .\Test.ps1 -count 10000 -repeat 10
    
    Command                                           10-run average
    -------                                           --------------
    pipeline, where-object, select -first 1           1.09919
    -contains, no pipeline                            1.15089
    -contains, pipeline                               1.75926
    pipeline, where-object                            2.21868
    pipeline, where-object, Test-Path in script block 3.65946
    

    Test.ps1

    param(
      [int] $count = 50
      ,
      [int] $repeat = 10
    )
    
    # Create sample input array.
    $paths = @('c:') * $count 
    $paths[$paths.Count / 2] = 'nosuch'
    
    
    $timingPropName = "$repeat-run average"
    
    @(
      [pscustomobject] @{ Command = "-contains, no pipeline"; $timingPropName = 
        (1..$($repeat) | % { (Measure-Command { (Test-Path $paths) -contains $false }).TotalSeconds } | 
          Measure-Object -average | % Average) }
      [pscustomobject] @{ Command = "-contains, pipeline"; $timingPropName = 
        (1..$($repeat) | % { (Measure-Command { ($paths | Test-Path) -contains $false }).TotalSeconds } | 
          Measure-Object -average | % Average) }
      [pscustomobject] @{ Command = "pipeline, where-object, select -first 1"; $timingPropName = 
        ( 1..$($repeat) | % { (Measure-Command { $paths | Test-Path | ? { $_ -eq $false } | Select-Object -First 1 }).TotalSeconds } | 
          Measure-Object -average | % Average) }
      [pscustomobject] @{ Command = "pipeline, where-object"; $timingPropName = 
        (1..$($repeat) | % { (Measure-Command { $paths | Test-Path | ? { $_ -eq $false } }).TotalSeconds } | 
          Measure-Object -average | % Average) }
      [pscustomobject] @{ Command = "pipeline, where-object, Test-Path in script block"; $timingPropName = 
        (1..$($repeat) | % { (Measure-Command { $paths | ? { !(Test-Path $_) } }).TotalSeconds } | 
          Measure-Object -average | % Average) }
    ) | 
      Sort-Object $timingPropName | 
        Format-Table Command, @{ n=$timingPropName; e={ '{0:.00000}' -f $_.$timingPropName } }
    

    【讨论】:

      【解决方案2】:
      if (($filesToUpdate.Source | Test-Path) -contains $false){
      #We know that one of the attempts was a failure
      }
      else{
      #proceed
      }
      

      【讨论】:

      • 完美!你也可以使用-notcontains,这取决于你想如何组织你的条件(例如,如果你没有什么可以执行,除非一切都为真)。
      • 我是一个代码悲观主义者,我也先假设失败,然后转向我的成功条件。有时我什至忘记写一个成功条件。
      • 干得好;你可以稍微简化一下,这也加快了速度:if ((Test-Path $filesToUpdate.Source) -contains $false)
      • 你不需要这里的管道。 (Test-Path $filesToUpdate.Source) -contains $false。 Path 接受数组作为输入。
      【解决方案3】:

      PowerShell 没有直接具有任何类似 LINQ 的等效项(尽管有一些第三方模块可以为您提供这些功能)。

      一般来说,您可以简单地过滤项目。如果里面有任何$false 值,则以下表达式将返回一个真值:

      $filesToUpdate.Source | Test-Path | ? { $_ -ne $true }
      

      因此,为了在所有路径都存在时获得“真”,您可以将其反转:

      !($filesToUpdate.Source | Test-Path | ? { $_ -ne $true })
      

      或者,在您的具体情况下,您也可以直接将您的列表与Test-Path结合起来:

      $filesToUpdate.Source | ? { !(Test-Path $_) }
      

      这将为您提供所有不存在的文件;当有任何文件不存在(即不全部存在时)时,它最终是一个真实的值(您可以在条件中使用)。

      【讨论】:

      • 不幸的是,PowerShell 的隐式到布尔转换是 quite tricky。如果输入恰好包含 one $false 值,则所有命令都会返回错误结果。例如,!($true, $true, $true) | ? { $_ -ne $true }!($true, $true, $false) | ? { $_ -ne $true } both 返回 $false,即使第二个输入集合明确包含 $false 值。 ! (& {})(没有输出的命令)、! $false! @($false) 在 PowerShell 中都返回 $true - 只有 2+ 元素集合总是“真实”。
      【解决方案4】:

      这是另一种方式,使用“与”函数对数组的所有元素执行“-and”。

      # foldright or reduce
      function foldr ($sb, $accum, $list) {
        if ($list.count -eq 0) { $accum }
        else { & $sb $list[0] (foldr $sb $accum $list[1..$list.length]) }
      }
      
      function and ($list) {
        foldr { $args[0] -and $args[1] } $true $list
      }
      
      $filestoupdate = [pscustomobject]@{source = $true,$true}
      and $filestoupdate.source
      
      
      True
      

      【讨论】:

        猜你喜欢
        • 2023-03-13
        • 2013-09-14
        • 1970-01-01
        • 2016-02-10
        • 2014-03-23
        • 2021-10-15
        • 2016-05-03
        • 2017-09-17
        • 2021-12-10
        相关资源
        最近更新 更多