Array.Find on powershell array

2020-07-05 07:46发布

问题:

How can I use the Array.Find method in powershell?

For example:

$a = 1,2,3,4,5
[Array]::Find($a, { args[0] -eq 3 })

gives

Cannot find an overload for "Find" and the argument count: "2".
At line:3 char:1
+ [Array]::Find($a, { $args[0] -eq 3 })
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [], MethodException
    + FullyQualifiedErrorId : MethodCountCouldNotFindBest

The array class has the methods I expect, as given by:

PS> [Array] | Get-Member -Static

   TypeName: System.Array

Name            MemberType Definition                                                                                       
----            ---------- ----------
Find            Method     static T Find[T](T[] array, System.Predicate[T] match)

Should the array be casted to something else to match the T[] type? I know there are other ways to achieve the find functionality, but I was wondering why this doesn't work.

回答1:

You need to cast the ScriptBlock as a Predicate[T]. Consider the following example:

[Array]::Find(@(1,2,3), [Predicate[int]]{ $args[0] -eq 1 })
# Result: 1

The reason that you received the error, is because there was no matching method overload, in the case where you're passing in a PowerShell ScriptBlock. As you noted in your Get-Member output, there is no Find() method overload that accepts a ScriptBlock as its second parameter.

[Array]::Find(@(1,2,3), { $args[0] -eq 1 })

Cannot find an overload for "Find" and the argument count: "2". At line:1 char:17 + [Array]::Find(@(1,2,3), { $_ -eq 1 }) + ~~~~~ + CategoryInfo : NotSpecified: (:) [], MethodException + FullyQualifiedErrorId : MethodCountCouldNotFindBest



回答2:

There is no need to use Array.Find, a regular where clause would work fine:

$a = @(1,2,3,4,5)
$a | where { $_ -eq 3 }

Or this (as suggested by @mjolinor):

$a -eq 3

Or this (returns $true or $false):

$a -contains 3

Where clause supports any type of objects, not just basic types, like this:

$a | where { $_.SomeProperty -eq 3 }


回答3:

Another option would be using an ArrayList, which provides a Contains method:

PS C:\> [Collections.ArrayList]$a = 'a', 'b', 'c'
PS C:\> $a.Contains('b')
True
PS C:\> $a.Contains('d')
False

Or, as @Neolisk mentioned in the comments, you could use PowerShell's -contains operator:

PS C:\> $a = 'a', 'b', 'c'
PS C:\> $a -contains 'b'
True
PS C:\> $a -contains 'd'
False


回答4:

Trevor Sullivan's answer is the right one, not only for Find() static method, but for FindIndex() as well.

When you've got several NIC cards with both ipv4 & ipv6 active on your servers and want to check the ipv4 IP/netmask pairs, something like this is good :

$NetworkAdapters = Get-WmiObject Win32_NetworkAdapterConfiguration -Filter 'IPEnabled = True' | Select-Object -Property Description, IPAddress, IPSubnet, DefaultIPGateway, DNSServerSearchOrder, DNSDomain
$NetworkAdapters | % {
  "Adapter {0} :" -f $_.Description
  # array'ing to avoid failure against single homed netcards
  $idx = [System.Array]::FindIndex(@($_.IPAddress), [Predicate[string]]{ $args[0] -match "\d+.\d+.\d+.\d+" })
  "  IP {0} has netmask {1}" -f @($_.IPAddress[$idx]), @($_.IPSubnet)[$idx]
}

My point is it works like a charm on 2012 WinPE, and fails on a production Win7 wks. Anyone got an idea ?



回答5:

This was run across ~6 million items in a system.array using both methods

$s=get-date
$([array]::FindALL($OPTArray,[Predicate[string]]{ $args[0] -match '^004400702(_\d{5})?' })).count
$(New-TimeSpan -Start $s -End $(get-date)).TotalSeconds

20 items
33.2223219 seconds

$s=get-date
$($OPTArray | where { $_ -match '^004400702(_\d{5})?'}).count 
$(New-TimeSpan -Start $s -End $(get-date)).TotalSeconds

20 items
102.1832173 seconds


标签: powershell