What is the Linq.First equivalent in PowerShell?
Asked Answered
M

5

62

The snippet below detects from a list of files which of them is a Directory on Ftp

as C# it will be like below

var files = new List<string>(){"App_Data", "bin", "Content"};
var line = "drwxr-xr-x 1 ftp ftp              0 Mar 18 22:41 App_Data"
var dir = files.First(x => line.EndsWith(x));

How I can transalte the last line in PowerShell ?

Mcinnis answered 19/3, 2011 at 4:45 Comment(0)
G
95

Something like this...

$files = @("App_Data", "bin", "Content")
$line = "drwxr-xr-x 1 ftp ftp              0 Mar 18 22:41 App_Data"
$dir = $files | Where { $line.EndsWith($_) } | Select -First 1

These versions of the last line would all accomplish the same:

$dir = @($files | Where { $line.EndsWith($_) })[0]

$dir = $files | Where { $line.EndsWith($_) } | Select -index 0

$dir = $files | Where { $line.EndsWith($_) } | Select -First 1

It was pointed out that the above is not exactly equivalent in behavior to Linq.First because Linq.First throws exceptions in two cases:

  • Throws ArgumentNullException when source or predicate is null.
  • Throws InvalidOperationException when source sequence is empty or no element satisfies the condition in predicate.

If you wanted that behavior exactly, you'd need some extra guard code.

Gibbs answered 19/3, 2011 at 5:6 Comment(12)
Edited to fix because the first version I posted did not implement the equivalent functionality of First().Gibbs
The last example is probably the most "canonical" except that it is more conventional to specify the Select alias instead of Select-Object - just as you did for Where-Object.Colpitis
I've updated the sample to show the more canonical/conventional code sample in the main example. (Thanks Keith!)Gibbs
I ran into an issue "Missing an argument for parameter 'First'. Specify a parameter of type 'System.Int32' and try again." and had to use -First 1 like @James answer has hidden away at the end. Powershell's Select -First would be more directly equivalent to .Take() but still works great.Quasimodo
For anyone wondering, where {...} | Select -First does "short circuit". That is, if you were searching through a million files in the above example, and a match were found after say 10 files, then the result would come back straight away. PowerShell wouldn't search through the remaining files.Foregone
I also like the fact that this is idempotent. No matter how many times I | Select -First 1 during an operation, I always get the same result. Noticed this today while testing.Pickaxe
@Foregone Any source about the short-circuit behavior? That is very useful because I'm making web request in the where clause.Stendhal
@FranklinYu, I don't remember now. I probably found out that fact experimentally. Try something like this 1..1000000 | ? {$_ -eq 10} | select -First 1. Try changing the 10 to bigger and bigger numbers and see if the execution time goes up.Foregone
@FranklinYu, also, you can look into how to write pipeline processing code. Such code involves a loop that processes the input items one at a time, and I think that all that Select-Object would need to do it break out of its loop when its "First" counter was satisfied . You could maybe track down the source code for Select-Object. I think it may be available.Foregone
Am I correct with the assumption, that the examples above are all equivalents of the FirstOrDefault, but not of the First method? The First method should rise an exception if no match found in the collection.Gestate
@pholper, as you point out it's not 100% equivalent to First, in that it will not throw if there is no result—the examples in the answer above will return $null in that case. For that reason It is also is not 100% equivalent to FirstOrDefault either, because FirstOrDefault returns a default value of the source type. If you want to be 100% equivalent to either of those you'd need some extra code to do a throw or assign an appropriate default.Gibbs
@Gestate thanks for pointing it out though, I updated the answer with that info.Gibbs
E
8

as Robert Groves said, Select-Object -First Occurence do the tricks, you can also use -Last Occurence.

by the way, like any other static .Net method you can use linq in powershell.

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

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

[Linq.Enumerable]::Where($list, [Func[int,bool]]{ param($item) $item -gt 1 })
Era answered 16/11, 2016 at 10:59 Comment(1)
Couldn't get $delegate = [Func[Object, bool]]{ param($section) $section.Path -eq $sectionPath } new line $newSection = [Linq.Enumerable]::Single($exclusionSections, $delegate) to work. Always returned Cannot find an overload for "Single" and the argument count: "2" If I removed the delegate argument it just returned the first element in the list.Heterosexual
T
2

There is a native way to do this using the Powershell Array's Where Function by passing in a WhereOperatorSelectionMode like this:

(1..9).Where({ $_ -gt 3}, "First") # 4

You can also use the mode straight from the enum as well:

$mode = [System.Management.Automation.WhereOperatorSelectionMode]::First
(1..9).Where({ $_ -gt 3}, $mode) # 4

Using any of the values from the WhereOperatorSelectionMode enum

Name Val Description
Default 0 Return all items
First 1 Return the first item
Last 2 Return the last item
SkipUntil 3 Skip items until condition is true
Until 4 Return all items until condition is true
Split 5 Return an array of two elements

See Also: Checking Out The Where and ForEach Operators in PowerShell V4

Tarsometatarsus answered 15/1, 2023 at 13:30 Comment(1)
Was looking everywhere for an example of how to use the WhereOperatorSelectionModeDinesh
R
1

Doug Finke produced a great video ( only 7 mins ) about converting C# to Powershell http://dougfinke.com/video/CSharpToPowerShell.html

Roberts example is very good indeed, though comma delimiting will implicitly be treated as an array

the shortest way of doing it would be to put it all into a single pipeline :

$dir = "App_Data", "bin", "Content" | % { if("drwxr-xr-x 1 ftp ftp              0 Mar 18 22:41 App_Data".EndsWith($_)) { $_ } } | select -first 1
Recreant answered 19/3, 2011 at 9:24 Comment(2)
This doesnt' work. Your Foreach stage is outputting the result of the EndsWith() call, which is a boolean.Colpitis
Thanks Keith :) It should have been $dir = "App_Data", "bin", "Content" | % { if ("drwxr-xr-x 1 ftp ftp 0 Mar 18 22:41 App_Data".EndsWith($_)){ $_ } } | select -first 1Recreant
C
0

This is a really simple implementation for First:

function First($collection)
{
    foreach ($item in $collection)
    {
        return $item
    }
    return $null
}

Instead of returning $null, you could throw an InvalidOperationException exception.

Crownwork answered 23/9, 2014 at 12:54 Comment(1)
This doesn't take input from pipeline, e.g. you cannot do 1..10 | FirstBowlds

© 2022 - 2024 — McMap. All rights reserved.