PowerShell 相当于 LINQ Any()? [英] PowerShell equivalent of LINQ Any()?
问题描述
我想从存储在 subversion 中的脚本位置找到所有顶级目录.
I would like to find all directories at the top level from the location of the script that are stored in subversion.
在 C# 中会是这样的
In C# it would be something like this
Directory.GetDirectories(".")
.Where(d=>Directories.GetDirectories(d)
.Any(x => x == "_svn" || ".svn"));
我在 PowerShell 中找到等效的Any()"有点困难,我不想经历调用扩展方法的尴尬.
I'm having a bit of difficulty finding the equivalent of "Any()" in PowerShell, and I don't want to go through the awkwardness of calling the extension method.
到目前为止,我得到了这个:
So far I've got this:
Get-ChildItem | ? {$_.PsIsContainer} | Get-ChildItem -force | ? {$_.PsIsContainer -and $_.Name -eq "_svn" -or $_.Name -eq ".svn"
这为我找到了 svn
目录本身,而不是它们的父目录——这正是我想要的.如果你能告诉我为什么添加
This finds me the svn
directories themselves, but not their parent directories - which is what I want. Bonus points if you can tell me why adding
| Select-Object {$_.Directory}
在该命令列表的末尾仅显示一系列空行.
to the end of that command list simply displays a sequence of blank lines.
推荐答案
使用 PowerShell v3+ 解决方案回答即时问题:
To answer the immediate question with a PowerShell v3+ solution:
(Get-ChildItem -Force -Directory -Recurse -Depth 2 -Include '_svn', '.svn').Parent.FullName
-Directory
限制对目录的匹配,-Recurse -Depth 2
最多递归三个级别(子、孙和曾孙),Include
允许指定多个(文件名组件)过滤器,并且 .Parent.FullName
返回 parent 目录的完整路径.使用成员枚举(隐式访问集合的元素属性).
-Directory
limits the matches to directories, -Recurse -Depth 2
recurses up to three levels (children, grandchildren, and great-grandchildren), Include
allows specifying multiple (filename-component) filters, and .Parent.FullName
returns the full path of the parent dirs. of the matching dirs., using member enumeration (implicitly accessing a collection's elements' properties).
至于奖励问题:select-object {$_.Directory}
不起作用,因为 [
实例没有 Get-ChildItem
返回的 System.IO.DirectoryInfo].Directory
属性,只有一个 .Parent代码>属性;
Select-Object -ExpandProperty Parent
应该已经使用了.
As for the bonus question: select-object {$_.Directory}
does not work,
because the [System.IO.DirectoryInfo]
instances returned by Get-ChildItem
have no .Directory
property, only a .Parent
property; Select-Object -ExpandProperty Parent
should have been used.
除了只返回感兴趣的属性 value 之外,-ExpandProperty
还强制执行该属性的存在.相比之下,Select-Object {$_.Directory}
返回一个自定义对象,其属性字面名为 $_.Directory
,其值为$null
,假设输入对象没有 .Directory
属性;这些 $null
值在控制台中打印为空行.
In addition to only returning the property value of interest, -ExpandProperty
also enforces the existence of the property. By contrast, Select-Object {$_.Directory}
returns a custom object with a property literally named $_.Directory
, whose value is $null
, given that the input objects have no .Directory
property; these $null
values print as empty lines in the console.
至于关于 PowerShell 等价于 LINQ 的 .Any()
方法,表示[带有布尔结果]给定的可枚举(集合)是否有任何元素/任何满足给定条件的元素:
As for the more general question about a PowerShell equivalent to LINQ's .Any()
method, which indicates [with a Boolean result] whether a given enumerable (collection) has any elements at all / any elements satisfying a given condition:
PowerShell 本身没有提供这样的等价物,但可以模拟的行为:
Natively, PowerShell offers no such equivalent, but the behavior can be emulated:
警告:这需要首先在内存中收集整个输入集合,这对于大型集合和/或长时间运行的输入命令可能会出现问题.
Caveat: This requires collecting the entire input collection in memory first, which can be problematic with large collections and/or long-running input commands.
(...).Where({ $_ ... }, 'First').Count -gt 0
...
表示感兴趣的命令,$_ ...
表示感兴趣的条件,应用于每个输入对象,其中PowerShell的自动$_
变量指的是手头的输入对象;参数 'First'
确保该方法在找到第一个匹配项后返回.
...
represents the command of interest, and $_ ...
the condition of interest, applied to each input object, where PowerShell's automatic $_
variable refers to the input object at hand; argument 'First'
ensures that the method returns once the first match has been found.
例如:
# See if there's at least one value > 1
PS> (1, 2, 3).Where({ $_ -gt 1 }, 'First').Count -gt 0
True
<小时>
使用管道:测试一个命令是否至少产生了一个输出对象[匹配条件]:
基于管道的解决方案的优势是它可以对命令的输出一个接一个地进行操作,因为它正在生成,而无需首先在内存中收集整个输出.
Using the pipeline: Testing whether a command produced at least one output object [matching a condition]:
The advantage of a pipeline-based solution is that it can act on a command's output one by one, as it is being produced, without needing to collect the entire output in memory first.
如果您不介意枚举所有对象 - 即使您只关心是否有至少一个- 使用 Paolo Tedesco 的有用扩展JaredPar 的有用答案.这种方法的缺点是您总是必须等待(可能长时间运行)命令完成生成所有 输出对象,即使 - 逻辑上 - 确定是否存在 任何输出对象都可以在收到第一个对象后立即生成.
If you don't mind that all objects are enumerated - even if you only care if there is at least one - use Paolo Tedesco's helpful extension to JaredPar's helpful answer. The down-side of this approach is that you always have to wait for a (potentially long-running) command to finish producing all output objects, even though - logically - the determination whether there are any output objects can be made as soon as the first object is received.
如果您想在遇到一个[匹配]对象后立即退出管道,您有两种选择:
If you want to exit the pipeline as soon as one [matching] object has been encountered, you have two options:
[Ad-hoc:易于理解,但实施起来很麻烦]将管道包含在 虚拟循环 中,并使用
break
中断管道 和该循环 (...
表示要测试其输出的命令,并且$_ ...
与条件匹配):
[Ad-hoc: Easy to understand, but cumbersome to implement] Enclose the pipeline in a dummy loop and use
break
to break out of the pipeline and that loop (...
represents the command whose output to test, and$_ ...
match the condition):
# 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
文件中.
[Use a PowerShell v3+ self-contained utility function that is nontrivial to implement]
See the implementation of function Test-Any
below.
It can be added to scripts or, for use in interactive sessions, to your $PROFILE
file.
该功能很重要,因为从 Windows PowerShell v5.1、PowerShell Core v6 开始,没有直接方式提前退出管道,因此基于变通方法在 .NET 反射上,目前需要一个私有类型.
The function is nontrivial, because as of Windows PowerShell v5.1, PowerShell Core v6, there is no direct way to exit a pipeline prematurely, so a workaround based on .NET reflection and a private type is currently necessary.
如果您同意应该有这样的功能,请参与 在 GitHub 上的对话一>.
If you agree that there should be such a feature, take part in the conversation on 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, 否则用手头的对象评估过滤器(脚本块).if (-not $Filter -or (Foreach-Object $Filter -InputObject $InputObject))
defaults to true if$Filter
wasn't specified, and otherwise evaluates the filter (script block) with the object at hand.- The use of
ForEach-Object
to evaluate the filter script block ensures that$_
binds to the current pipeline object in all scenarios, as demonstrated in PetSerAl's helpful answer here.
(Add-Type ...
语句使用使用 C# 代码创建的临时类型,该类型使用反射抛出与Select-Object -First<相同的异常/code> (PowerShell v3+) 在内部使用来停止管道,即
[System.Management.Automation.StopUpstreamCommandsException]
,从 PowerShell v5 开始,它仍然是一个 private 类型.这里的背景:http://powershell.com/cs/blogs/tobias/archive/2010/01/01/cancelling-a-pipeline.aspx非常感谢 PetSerAl 在评论中贡献此代码.The
(Add-Type ...
statement uses an ad-hoc type created with C# code that uses reflection to throw the same exception thatSelect-Object -First
(PowerShell v3+) uses internally to stop the pipeline, namely[System.Management.Automation.StopUpstreamCommandsException]
, which as of PowerShell v5 is still a private type. Background here: http://powershell.com/cs/blogs/tobias/archive/2010/01/01/cancelling-a-pipeline.aspx A big thank-you to PetSerAl for contributing this code in the comments.示例:
PS>@() |测试任何假
PS>Get-EventLog 应用程序 |Test-Any # 应该立即返回 *真的
PS>1, 2, 3 |Test-Any { $_ -gt 1 } # 查看是否有任何对象 >1真的
JaredPar 的有用回答和Paolo Tedesco 的有用扩展 在一个方面存在不足:一旦匹配成功,他们就不会退出管道找到了,这可能是一个重要的优化.
JaredPar's helpful answer and Paolo Tedesco's helpful extension fall short in one respect: they don't exit the pipeline once a match has been found, which can be an important optimization.
遗憾的是,即使从 PowerShell v5 开始,也没有直接的方法可以提前退出管道.如果您同意应该有这样的功能,请参与 GitHub 上的对话.
Sadly, even as of PowerShell v5, there is no direct way to exit a pipeline prematurely. If you agree that there should be such a feature, take part in the conversation on GitHub.
A naïve optimization of JaredPar's answer actually shortens the code:
# 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-Any
和Test-Any
,检查$MyInvocation.ExpectingInput
,这是$true
只在管道中:谢谢,PetSerAlfunction Test-Any() { process { $MyInvocation.ExpectingInput;break } end { $false } }
- The
process
block is only entered if there's at least one element in the pipeline.- Small caveat: By design, if there's no pipeline at all, the
process
block is still entered, with$_
set to$null
, so callingTest-Any
outside of a pipeline unhelpfully returns$true
. To distinguish between between$null | Test-Any
andTest-Any
, check$MyInvocation.ExpectingInput
, which is$true
only in a pipeline: Thanks, PetSerAlfunction Test-Any() { process { $MyInvocation.ExpectingInput; break } end { $false } }
- 如果有退出管道的命令,这就是它要去的地方.
- 请注意,
return
只会移动到下一个 输入对象. - If there were a command to exit the pipeline, this is where it would go.
- Note that
return
would simply move on to the next input object.
break
然后终止管道,从而防止额外对象的多余处理.但是,它也会退出任何封闭循环 -break
并非旨在退出管道谢谢,PetSerAl.break
then terminates the pipeline and thus prevents superfluous processing of additional objects. HOWEVER, IT ALSO EXITS ANY ENCLOSING LOOP -break
is NOT designed to exit a PIPELINEThanks, PetSerAl .由于
process
块无条件地执行break
,end
块只有在process
> 块从未进入,这意味着一个空的管道,所以$false
被写入输出流以发出信号.Since the
process
block unconditionally executesbreak
, theend
block is only reached if theprocess
block was never entered, which implies an empty pipeline, so$false
is written to the output stream to signal that.这篇关于PowerShell 相当于 LINQ Any()?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!
- Small caveat: By design, if there's no pipeline at all, the
- 小警告:根据设计,如果根本没有管道,则仍会进入
- The use of