MKP
MKP

Reputation: 31

search a substring with special characters in a string

I am searching a substring with special characters in a string. How do I search for the substring in the string.

$path = 'c:\test'
$mountpoint = 'c:\test\temp\20190987-120\'

I want to search for the $path in $mountpoint

I have tried using -match,-contains,-in etc..

PS C:\>$path = 'c:\test'
PS C:\>$mountpoint = 'c:\test\temp\20190987-120\'
PS C:\>$path -contains $mountpoint
False

Upvotes: 0

Views: 6396

Answers (2)

mklement0
mklement0

Reputation: 439357

AdminOfThing's answer is helpful, but I found myself wanting things to be framed differently.

  • You're looking for a way to perform a literal substring search that is anchored at the start, which is only indirectly supported in PowerShell - see the next section for solutions.

  • Operators -contains and -in are unrelated to substring matching (despite the similarity in name between -contains and the String.Contains() .NET method).

    • They test a single value's membership (being contained as a whole) in a collection, by way of element-by-element equality comparisons (implied -eq). See the docs and the bottom section of this answer for details.

    • If you want to combine the two tasks - looking for a substring in all elements of a collection - you can take advantage of the fact that PowerShell's -match and -like operators - discussed below - can operate on collection-valued LHSs as well, in which case they act as filters; while this is not exactly the same as testing for membership, it can effectively be used for that; this answer shows how to use -match that way.


Solutions:

Using the .NET framework:

The .NET String.IndexOf() method performs literal substring searching and returns the 0-based index of the character where the substring starts in the input string (and -1 if the substring cannot be found at all):

# -> $true
0 -eq 'foo\bar'.IndexOf('foo\')

Note that, unlike PowerShell's operators, the above is case-sensitive by default, but you can change to case-insensitive behavior with additional arguments:

# -> $true
0 -eq 'foo\bar'.IndexOf('FOO\', [StringComparison]::InvariantCultureIgnoreCase)

Note that PowerShell uses the invariant rather than the current culture in many (but not all) contexts, such as with operators -eq, -contains, -in and the switch statement.

If there were no need to anchor your substring search, i.e., if you only want to know whether the substring is contained somewhere in the input string, you can use String.Contains():

 # Substring is present, but not at the start
 # Note: matching is case-SENSITIVE.
 # -> $true
 'foo\bar'.Contains('oo\')   

Caveat: In Windows PowerShell, .Contains() is invariably case-sensitive. In PowerShell (Core) 7+, an additional overload is available that offers case-insensitivity (e.g., 'FOO\BAR'.Contains('oo\', 'InvariantCultureIgnoreCase'))


Using the -match operator:

While -match does implicitly perform substring matching, it does so based on a regex (regular expression) rather than a literal string.

-match performs case-insensitive matching by default; use the -cmatch variant for case-sensitivity.

This means that you can conveniently use ^, the start-of-input anchor, to ensure that the search expression only matches at the start of the input string.

Conversely, in order for your search string to be treated as a literal string in your regex, you must \-escape any regex metacharacters in it (characters that have special meaning) in a regex.

Since \ is therefore itself a metacharacter, it must be escaped too, namely as \\.

In string literals you can do the escaping manually:

# Manual escaping: \ is doubled.
# Note the ^ to anchor matching at the start.
# -> $true
'foo\bar' -match '^foo\\'

Programmatically, when the string as a variable, you must use the [regex]::Escape() method:

# Programmatic escaping via [regex]::Escape()
# Note the ^ to anchor matching at the start.
# -> $true
$s = 'foo\'; 'foo\bar' -match ('^' + [regex]::Escape($s))

Using the -like operator:

Unlike -match, -like performs full-string matching and does so based on wildcard expressions (a.k.a globs in the Unix world); while distantly related to regexes, they use simpler, incompatible syntax (and are far less powerful).

-like performs case-insensitive matching by default; use the -clike variant for case-sensitivity.

Wildcards have only 3 basic constructs and therefore only 4 metacharacters: ? (to match a single char.), * (to match any number of chars., including none), and [ (the start of a character set or range matching a single char., e.g., [a-z] or [45]), and the escape character, `

In the simplest case, you can just append * to your search string to see if it matches at the start of the input string:

# OK, because 'foo\' contains none of: ? * [ `
# -> $true
'foo\bar' -like 'foo\*'

# With a variable, using an expandable string:
# -> $true
$s = 'foo\'; 'foo\bar' -like "$s*"

As with -match, however, programmatic escaping may be necessary, which requires a call to [WildcardPattern]::Escape():

# -> $true
$s = 'foo['; 'foo[bar' -like ([WildcardPattern]::Escape($s) + '*')

Upvotes: 2

AdminOfThings
AdminOfThings

Reputation: 25021

You can use -Match in this particular case.

$mountpoint -match [regex]::escape($path)

The issue here is with the \ character. It is a special character in a regex pattern and needs to be escaped. Since the -Match operator does regex matching, the special characters need to be considered. I chose to use the Escape() method for this scenario. You can escape characters individually with the \ character like c:\\test. LotPings comments reiterate this idea.

With Regex matching, you have some control over how much matching you want to do. You can include anchors and other special characters to tailor your match. Regex101 is one of many online options to test and learn about regex.

If you notice in the example below, the match returns True. This is because the string c:\test exists in c:\testing, which may give you unwanted results. You need to carefully consider those situations.

"c:\testing" -match [regex]::Escape("c:\test")
True

-Contains and -in are containment operators. Their purpose is to check if a single object value exists within a collection of object values. For example, these are best used when you want to compare a single string like 'c:\test' to a collection like 'c:\test','c:\folder','c:\folder\test'. They take the value you are testing and basically perform an -eq comparison (not literally but more efficiently) against each item in the collection. However, you can compare collections, but that entire test collection must exist as a element in the reference collection. With -Contains you want your reference collection to be on the LHS of the operator. With -in, you want your reference collection to be on the RHS of the operator.

Examples Using -Contains and -In

$collection = 'c:\test','c:\folder','c:\folder\test'
$path = 'c:\test'

$collection -contains $path
True

$path -in $collection
True

"c:\test\" -in $collection
False

Notice the False return in the last example because the trailing \ character makes this different from any element in the collection.

Please see About_Comparison_Operators for information about -Match and see Regex.Escape Method for more details on the Escape() method.

Upvotes: 1

Related Questions