Reputation: 31
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
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.
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
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