当前位置:  开发笔记 > 编程语言 > 正文

PowerShell相当于LINQ Any()?

如何解决《PowerShell相当于LINQAny()?》经验,为你挑选了5个好方法。

我想从存储在subversion中的脚本位置找到顶层的所有目录.

在C#中它会是这样的

Directory.GetDirectories(".")
  .Where(d=>Directories.GetDirectories(d)
     .Any(x => x == "_svn" || ".svn"));

我在PowerShell中找到相当于"Any()"的东西有点困难,我不想经历调用扩展方法的尴尬.

到目前为止,我有这个:

 Get-ChildItem | ? {$_.PsIsContainer} | Get-ChildItem -force | ? {$_.PsIsContainer -and $_.Name -eq "_svn" -or $_.Name -eq ".svn"

这找到了我svn自己的目录,但不是他们的父目录 - 这就是我想要的.如果您能告诉我添加的原因,可以获得奖励积分

 | Select-Object {$_.Directory}

到该命令列表的末尾只显示一系列空行.



1> mklement0..:

回答这个直接的问题有一个PowerShell v3的+解决方案:

(Get-ChildItem -Force -Directory -Recurse -Depth 2 -Include '_svn', '.svn').Parent.FullName

-Directory将匹配限制为目录,-Recurse -Depth 2最多可以递送三个级别(子级,孙级和曾孙级),Include允许指定多个(文件名组件)过滤器,并.Parent.FullName返回目录的完整路径.匹配dirs.,使用成员枚举(隐式访问集合的元素属性).

至于奖金问题:select-object {$_.Directory}不起作用,因为\[System.IO.DirectoryInfo\]返回的实例Get-ChildItem没有.Directory财产,只有.Parent财产; Select-Object -ExpandProperty Parent本应该用的.

除了仅返回感兴趣的属性值之外,-ExpandProperty还强制执行属性的存在.相比之下,Select-Object {$_.Directory}返回一个自定义对象,该对象具有字面命名的属性$_.Directory,其值为$null,假设输入对象没有.Directory属性; 这些$null值在控制台中作为空行打印.


关于PowerShell等效于LINQ方法的更普遍的问题,该方法指示[使用布尔结果]给定的可枚举(集合)是否具有满足给定条件的所有/任何元素的任何元素:.Any()

本地,PowerShell 没有提供这样的等价物,但可以模拟行为:


使用PowerShell v4 + .Where()集合方法:

警告:这需要首先在内存中收集整个输入集合,这对于大型集合和/或长时间运行的输入命令可能是有问题的.

(...).Where({ $_ ... }, 'First').Count -gt 0

...表示感兴趣的命令,以及$_ ...应用于每个输入对象的感兴趣的条件,其中PowerShell的自动$_变量指的是手头的输入对象; 参数'First'确保在找到第一个匹配项后返回该方法.

例如:

# See if there's at least one value > 1
PS> (1, 2, 3).Where({ $_ -gt 1 }, 'First').Count -gt 0
True

使用管道:测试命令是否产生至少一个输出对象[匹配条件]:

基于流水线的解决方案优点是,它可以在生成时逐个作用于命令的输出,而无需首先在内存中收集整个输出.

如果您不介意枚举所有对象 - 即使您只关心至少有一个 - 请使用Paolo Tedesco对JaredPar的有用答案的有用扩展.这种方法的缺点是你总是必须等待(可能是长时间运行的)命令来完成所有输出对象的生成,即使 - 逻辑上 - 确定是否有任何输出对象可以立即生成收到第一个对象.

如果要在遇到一个 [匹配]对象时立即退出管道,则有两个选项:

[ Ad-hoc:易于理解,但实施起来很麻烦 ]将管道封闭在虚拟循环中用于break打破管道和该循环(...表示要测试其输出的命令,并$_ ...匹配条件):

 # Exit on first input object.
 [bool] $haveAny = do { ... | % { $true; break } } while ($false)

 # Exit on first input object that matches a condition.
 [bool] $haveAny = do { ... | % { if ($_ ...) { $true ; break } } } while ($false)

[ 使用PowerShell v3 +自包含的实用程序函数,实现起来非常重要 ]请参阅下面函数Test-Any实现.它可以添加到脚本中,也可以添加到交互式会话中,也可以添加到$PROFILE文件中.


PowerShell v3 +:优化的实用功能Test-Any

该功能非常重要,因为从Windows PowerShell v5.1,PowerShell Core v6开始,没有直接退出管道的方法,因此目前需要基于.NET反射和私有类型的解决方法.

如果您同意应该有这样的功能,请参加GitHub上的对话.

#requires -version 3
Function Test-Any {

    [CmdletBinding()]
    param(
        [ScriptBlock] $Filter,
        [Parameter(ValueFromPipeline = $true)] $InputObject
    )

    process {
      if (-not $Filter -or (Foreach-Object $Filter -InputObject $InputObject)) {
          $true # Signal that at least 1 [matching] object was found
          # Now that we have our result, stop the upstream commands in the
          # pipeline so that they don't create more, no-longer-needed input.
          (Add-Type -Passthru -TypeDefinition '
            using System.Management.Automation;
            namespace net.same2u.PowerShell {
              public static class CustomPipelineStopper {
                public static void Stop(Cmdlet cmdlet) {
                  throw (System.Exception) System.Activator.CreateInstance(typeof(Cmdlet).Assembly.GetType("System.Management.Automation.StopUpstreamCommandsException"), cmdlet);
                }
              }
            }')::Stop($PSCmdlet)
      }
    }
    end { $false }
}

if (-not $Filter -or (Foreach-Object $Filter -InputObject $InputObject))如果$Filter未指定,则默认为true ,否则使用手头的对象计算过滤器(脚本块).

使用的ForEach-Object评价过滤器脚本块确保$_绑定到当前管道对象的所有场景,如证明PetSerAl的有用的答案在这里.

(Add-Type ...语句使用由C#代码创建的ad-hoc类型,该代码使用反射来抛出Select-Object -First(PowerShell v3 +)内部用于停止管道的相同异常,即[System.Management.Automation.StopUpstreamCommandsException]从PowerShell v5开始仍然是私有类型.这里的背景:http : //powershell.com/cs/blogs/tobias/archive/2010/01/01/cancelling-a-pipeline.aspx非常感谢PetSerAl在评论中提供此代码.

例子:

PS> @() | Test-Any false

PS> Get-EventLog Application | Test-Any # should return *right away* true

PS> 1, 2, 3 | Test-Any { $_ -gt 1 } # see if any object is > 1 true


背景资料

JaredPar的有用答案和Paolo Tedesco的有用扩展在一个方面不足:一旦找到匹配,他们就不会退出管道,这可能是一个重要的优化.

遗憾的是,即使是PowerShell v5,也没有直接退出管道的方法.如果您同意应该有这样的功能,请参加GitHub上的对话.

一个天真的优化JaredPar的答案实际上缩短了代码:

# IMPORTANT: ONLY EVER USE THIS INSIDE A PURPOSE-BUILT DUMMY LOOP (see below)
function Test-Any() { process { $true; break } end { $false } }

process只有在管道中至少有一个元素时才输入该块.

小警告:根据设计,如果根本没有管道,process则仍然输入块,$_设置为$null,所以Test-Any 管道调用无益返回$true.要区分$null | Test-AnyTest-Any,检查$MyInvocation.ExpectingInput,这$true只是在管道中:谢谢,PetSerAl function Test-Any() { process { $MyInvocation.ExpectingInput; break } end { $false } }

$true写入输出流,表示至少找到一个对象.

break然后终止管道,从而防止额外对象的多余处理.但是,它还break可以退出任何封闭循环 - 不是为了退出PIPELINE 感谢,PetSerAl .

如果退出管道命令,这是它会去.

请注意,return只需转到下一个输入对象.

由于process块无条件地执行break,所以end只有process在从未输入块时才会到达块,这意味着空管道,因此$false写入输出流以发信号通知该块.



2> JaredPar..:

不幸的是,PowerShell中没有相应的功能.我写了一篇关于此的博客文章,建议使用通用的Test-Any函数/过滤器.

function Test-Any() {
    begin {
        $any = $false
    }
    process {
        $any = $true
    }
    end {
        $any
    }
}

博客文章:那条管道中有什么东西吗?



3> Paolo Tedesc..:

@ JaredPar答案的变体,将测试合并到Test-Any过滤器中:

function Test-Any {
    [CmdletBinding()]
    param($EvaluateCondition,
        [Parameter(ValueFromPipeline = $true)] $ObjectToTest)
    begin {
        $any = $false
    }
    process {
        if (-not $any -and (& $EvaluateCondition $ObjectToTest)) {
            $any = $true
        }
    }
    end {
        $any
    }
}

现在我可以编写"任何"测试

> 1..4 | Test-Any { $_ -gt 3 }
True

> 1..4 | Test-Any { $_ -gt 5 }
False


@msorens:如果$ any已经为真,我想避免评估条件.
`&$ EvaluateCondition $ ObjectToTest`不会将对象绑定到`$ _`.您的示例之所以有效,只是因为您从父作用域引用了`$ _`,这将是在同一模块或全局状态下定义和执行的大小写函数中的`Test-Any`作用域.但是如果函数在一个模块中定义并从全局状态的其他模块中使用,那么你将在`$ _`中有错误的值.你应该使用这样的东西:`ForEach-Object $ EvaluateCondition -InputObject $ ObjectToTest`.
@ mklement0查看我的[answer](http://stackoverflow.com/a/33240383)链接问题.不需要`Test-Any`的范围将是`$ EvaluateCondition`脚本块的父级.

4> 小智..:

你可以使用原来的LINQ Any:

[Linq.Enumerable]::Any($list)



5> Joey..:

我的方法现在是:

gci -r -force `
    | ? { $_.PSIsContainer -and $_.Name -match "^[._]svn$" } `
    | select Parent -Unique

之所以

select-object {$_.Directory}

不返回任何有用的东西是DirectoryInfo对象上没有这样的属性.至少不在我的PowerShell中.


要详细说明您自己的答案:PowerShell可以将大多数非空集合视为$true,因此您可以简单地执行以下操作:

$svnDirs = gci `
    | ? {$_.PsIsContainer} `
    | ? {
        gci $_.Name -Force `
            | ? {$_.PSIsContainer -and ($_.Name -eq "_svn" -or $_.Name -eq ".svn") }
        }


杰伊的意思是| 该行末尾的字符自动将代码继续到下一行(就像没有闭括号的开括号),这样您就不需要转义返回字符.
推荐阅读
女女的家_747
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有