amaru96
amaru96

Reputation: 191

Check if a string exists in an array even as a substring in PowerShell

I'm trying to work out if a string exists in an array, even if it's a substring of a value in the array.

I've tried a few methods and just can't get it to work, not sure where I'm going wrong.

I have the below code, you can see that $val2 exists within $val1, but I always get a FALSE when I run it.

$val1 = "folder1\folder2\folder3"
$val2 = "folder1\folder2"
$val3 = "folder9"

$val_array = @()

$val_array += $val1
$val_array += $val3

$null -ne ($val_array | ? { $val2 -match $_ })  # Returns $true

I also tried:

foreach ($item in $val_array) {
if ($item -match $val2) {
Write-Host "yes"
}
}

Upvotes: 1

Views: 2237

Answers (3)

mklement0
mklement0

Reputation: 440556

iRon's helpful answer offers the best solution to your problem, using wildcard matching via the -like operator.

Note:

  • The need to escape select characters in a search pattern in order for the pattern to be taken verbatim in principle also applies to the wildcard-based -like operator, not just to the regex-based -match operator, but since wildcard expressions have far fewer metacharacters than regexes - namely just *, ?, and [ - the need for such escaping doesn't often arise in practice; whereas regexes require \ as the escape characters, wildcards use `, and programmatic escaping can be achieved with [WildcardPattern]::Escape()

  • Unfortunately, as of PowerShell 7.2, there is no dedicated operator for verbatim substring matching:

    • A workaround for this limitation is to call the [string] .NET type's .Contains() method (on a single input string only), however, this performs case-sensitive matching, whereas PowerShell operators are case-insensitive by default, but offer case-sensitive variants simply by prefixing the operator name with c (e.g., -clike, -cmatch).

    • In Windows PowerShell, .Contains() is invariably case-sensitive, but in PowerShell (Core) 7+ an additional overload is available that offers case-insensitive matching:

       'Foo'.Contains('fo') # -> $false, due to case difference
      
       # PowerShell (Core) 7+ *only*:
       'Foo'.Contains('fo', 'InvariantCultureIgnoreCase') # -> $true
      
    • Caveat: Despite the name similarity, PowerShell's -contains operator does not perform substring matching; instead, it tests whether a collection contains a given element (in full).


As for what you tried:

  • Your primary problem is that you've accidentally swapped the -match operator's operands: the search pattern - which is invariably interpreted as a regex (regular expression) - must be on the RHS.

  • As iRon points out, in order for your search pattern to be taken verbatim (literally), you need to escape regex metacharacters with \, and the robust, programmatic way to do this is with [regex]::Escape().

Therefore, the immediate fix would have been (? is a built-in alias of the Where-Object cmdlet):

# OK, but SLOW.
$val_array | ? { $_ -match [regex]::Escape($val2) }

However, this solution is inefficient (it involves the pipeline and a cmdlet).

Fortunately, PowerShell's comparison operators can be applied to arrays (collections) directly, in which case they act as filters, i.e. they return the sub-array of matching elements - see the docs.

iRon's answer uses this technique with -like, but it equally works with -match, so that your expression can be simplified to the following, much more efficient form:

# MUCH FASTER.
$val_array -match [regex]::Escape($val2)

Upvotes: 2

iRon
iRon

Reputation: 23862

The -Match operator does a regular expression comparison. Where the backslash character (\) has a special meaning (it escapes the following character).

Instead you might use the -Like operator:

$val_array -Like "*$val2*"

Yields:

folder1\folder2\folder3

Upvotes: 3

Vivere
Vivere

Reputation: 2300

Try the string method Contains:

$null -ne ($val_array | ? { $_.Contains($val2) })

Upvotes: 0

Related Questions