To answer the immediate question with a PowerShell v3+ solution:
(Get-ChildItem -Force -Directory -Recurse -Depth 2 -Include '_svn', '.svn').Parent.FullName
-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-access enumeration (implicitly accessing a collection’s elements’ properties).
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.
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.
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:
Natively, PowerShell offers no such equivalent, but the behavior can be emulated:
Using the PowerShell v4+ intrinsic.Where() method:
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
... 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.
For example:
# 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.
-
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: Easy to understand, but cumbersome to implement]
Enclose the pipeline in a dummy loop and usebreakto 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) -
[Use a PowerShell v3+ self-contained utility function that is nontrivial to implement]
See the implementation of functionTest-Anybelow.
It can be added to scripts or, for use in interactive sessions, to your$PROFILEfile.
-
PowerShell v3+: Optimized utility function Test-Any
The function is nontrivial, because as of PowerShell (Core) v7.2.x, there is no direct way to exit a pipeline prematurely, so a workaround based on .NET reflection and a private type is currently necessary.
If you agree that there should be such a feature, take part in the conversation in GitHub issue #3821.
#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))defaults to true if$Filterwasn’t specified, and otherwise evaluates the filter (script block) with the object at hand.- The use of
ForEach-Objectto evaluate the filter script block ensures that$_binds to the current pipeline object in all scenarios, as demonstrated in PetSerAl’s helpful answer here.
- The use of
-
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.
Examples:
-
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
Background information
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.
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 in GitHub issue #3821.
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 } }
-
The
processblock 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
processblock is still entered, with$_set to$null, so callingTest-Anyoutside of a pipeline unhelpfully returns$true. To distinguish between between$null | Test-AnyandTest-Any, check$MyInvocation.ExpectingInput, which is$trueonly in a pipeline: Thanks, PetSerAl
function Test-Any() { process { $MyInvocation.ExpectingInput; break } end { $false } }
- Small caveat: By design, if there’s no pipeline at all, the
-
$true, written to the output stream, signals that at least one object was found. -
breakthen terminates the pipeline and thus prevents superfluous processing of additional objects. HOWEVER, IT ALSO EXITS ANY ENCLOSING LOOP –breakis NOT designed to exit a PIPELINEThanks, PetSerAl
.- If there were a command to exit the pipeline, this is where it would go.
- Note that
returnwould simply move on to the next input object.
-
Since the
processblock unconditionally executesbreak, theendblock is only reached if theprocessblock was never entered, which implies an empty pipeline, so$falseis written to the output stream to signal that.