我想从存储在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}
到该命令列表的末尾只显示一系列空行.
要回答这个直接的问题有一个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 没有提供这样的等价物,但可以模拟行为:
.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
文件中.
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-Any
和Test-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
写入输出流以发信号通知该块.
不幸的是,PowerShell中没有相应的功能.我写了一篇关于此的博客文章,建议使用通用的Test-Any函数/过滤器.
function Test-Any() { begin { $any = $false } process { $any = $true } end { $any } }
博客文章:那条管道中有什么东西吗?
@ 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
你可以使用原来的LINQ Any
:
[Linq.Enumerable]::Any($list)
我的方法现在是:
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") } }