Nick
Nick

Reputation: 1208

Powershell Use Wildcards when matching arrays

I've been banging my head against a wall for hours now and am looking for some help. To simplify my issue, I have two arrays, one which contains wildcards, and the other which makes use of those wildcards:

$WildCardArray = @("RED-*.htm", "*.yellow", "BLUE!.txt", "*.green", "*.purple")
$SpelledOutArray = @("RED-123.htm", "456.yellow", "BLUE!.txt",  "789.green", "purple.102", "orange.abc")

I cannot get PowerShell to recognize that these match.

My end goal is to have an output that tells me that purple.102 and orange.abc are not in $WildCardArray.

Seems super simple! Some of the things I've tried:

$WildCardArray = @("RED-*.htm", "*.yellow", "BLUE!.txt", "*.green", "*.purple")
$SpelledOutArray = @("RED-123.htm", "456.yellow", "BLUE!.txt",  "789.green", "purple.102", "orange.abc")
foreach($Item in $SpelledOutArray)
{
$item | where {$wildcardarray -contains $item}
}

I get BLUE!.txt as a result because it's my control with no wildcards. If I change that to -notcontains, I get all of the results returned except BLUE. I've tried contains, match, equals, like and all of their opposites, compare-object, and nothing works. I get no errors, I just do not get the expected results

I tried replacing "*" with [a-zA-Z] and other combinations, but it replaces it literally, and not as a wildcard. I'm not sure what I'm doing wrong.... PSVersion 5.1 Win 10

Does anybody know the logic behind WHY like/match/contains doesn't work, and what I can do make it work? It doesn't have to be pretty, it just needs to work

Upvotes: 3

Views: 9215

Answers (3)

Joey
Joey

Reputation: 354456

Your wildcard array is effectively a list of patterns to look for. You can turn this into a single regex and match against that:

$WildCardArray = @("RED-*.htm", "*.yellow", "BLUE!.txt", "*.green", "*.purple")
$SpelledOutArray = @("RED-123.htm", "456.yellow", "BLUE!.txt",  "789.green", "purple.102", "orange.abc")

# Turn wildcards into regexes
# First escape all characters that might cause trouble in regexes (leaving out those we care about)
$escaped = $WildcardArray -replace '[ #$()+.[\\^{]','\$&' # list taken from Regex.Escape
# replace wildcards with their regex equivalents
$regexes = $escaped -replace '\*','.*' -replace '\?','.'
# combine them into one regex
$singleRegex = ($regexes | %{ '^' + $_ + '$' }) -join '|'

# match against that regex
$SpelledOutArray -notmatch $singleRegex

This has the potential to be faster than checking everything in a loop, although I didn't test. Also, extraordinarily-long regexes may cause trouble as well.

Upvotes: 4

Esperento57
Esperento57

Reputation: 17462

$WildCardArray = @("RED-*.htm", "*.yellow", "BLUE!.txt", "*.green", "*.purple")
$SpelledOutArray = @("RED-123.htm", "456.yellow", "BLUE!.txt",  "789.green", "purple.102", "orange.abc")

$WildCardArray | %{$str=$_; $SpelledOutArray | ? {$_ -like $str}  }

other solution, not short

$WildCardArray | 
   %{$current=$_; $SpelledOutArray | %{ [pscustomobject]@{wildcard=$current; value=$_ }}} | 
        where {$_.value -like $_.wildcard } 

Upvotes: 1

TessellatingHeckler
TessellatingHeckler

Reputation: 28983

banging my head against a wall for hours [..] Seems super simple!

That's probably a hint that it's not super simple. You're trying to cross match two lists: red to red, yellow, blue.... then Blue to red, yellow, blue... then Green to red, yellow, blue.... 30 comparisons but you only have 5 loops happening.

You need more.

$WildCardArray = @("RED-*.htm", "*.yellow", "BLUE!.txt", "*.green", "*.purple")
$SpelledOutArray = @("RED-123.htm", "456.yellow", "BLUE!.txt",  "789.green", "purple.102", "orange.abc")

# Loop once over the spelled out items
foreach($Item in $SpelledOutArray)
{
    # for each one, loop over the entire WildCard array and check for matches
    $WildCardMatches = foreach ($WildCard in $WildCardArray)
    { 
        if ($item -like $WildCard) {
            $Item
        }
    }

    # Now see if there were any wildcard matches for this SpelledOut Item or not
    if (-not $WildCardMatches)
    {
        $Item 
    }
}

and the inner loop over WildCardArray can become a filter, but you have to be filtering the array, not the individual item as your code does.

$WildCardArray = @("RED-*.htm", "*.yellow", "BLUE!.txt", "*.green", "*.purple")
$SpelledOutArray = @("RED-123.htm", "456.yellow", "BLUE!.txt",  "789.green", "purple.102", "orange.abc")

foreach($Item in $SpelledOutArray)
{
   $WildCardMatches = $wildcardarray | Where { $item -like $_ }

   if (-not $WildCardMatches)
   {
       $Item 
   }
}

And I guess you could mash that down into an unclear double-where filter if you had to.

$WildCardArray = @("RED-*.htm", "*.yellow", "BLUE!.txt", "*.green", "*.purple")
$SpelledOutArray = @("RED-123.htm", "456.yellow", "BLUE!.txt",  "789.green", "purple.102", "orange.abc")

$SpelledOutArray |Where {$item=$_; -not ($WildCardArray |Where {$item -like $_}) }

Upvotes: 9

Related Questions