powershellsubstringstring-matching

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

Solution

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


    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, notably when the string is contained in 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) + '*')