我正在搜索字符串中包含特殊字符的子字符串。如何在字符串中搜索子字符串。
$path = 'c:\test'
$mountpoint = 'c:\test\temp\20190987-120\'
我想在$path
中搜索$mountpoint
我尝试过使用-match
,-contains
,-in
等。
PS C:\>$path = 'c:\test'
PS C:\>$mountpoint = 'c:\test\temp\20190987-120\'
PS C:\>$path -contains $mountpoint
False
AdminOfThing's answer很有帮助,但我发现自己希望以不同的方式构建框架。
-contains
和-in
与子串匹配无关(尽管-contains
和String.Contains()
.NET方法之间的名称相似)。
它们通过逐个元素的相等比较(隐含的-eq
)测试集合中单个值的成员资格(作为一个整体包含)。有关详细信息,请参阅docs和this answer的底部。
如果你想要结合两个任务 - 在集合的所有元素中寻找子字符串 - 你可以利用PowerShell的-match
和-like
操作符(下面讨论)也可以对集合值LHS进行操作,在这种情况下他们充当过滤器;虽然这与成员资格测试不完全相同,但它可以有效地用于此; this answer展示了如何使用-match
。使用.NET框架:
.NET String.IndexOf()
方法执行文字子字符串搜索,并返回子字符串在输入字符串中开始的字符的基于0
的索引(如果根本找不到子字符串,则返回-1
):
PS> 0 -eq 'foo\bar'.IndexOf('foo\')
True
请注意,与PowerShell的运算符不同,默认情况下,上述内容区分大小写,但您可以使用其他参数更改为不区分大小写的行为:
PS> 0 -eq 'foo\bar'.IndexOf('FOO\', [System.StringComparison]::InvariantCultureIgnoreCase)
True
请注意,PowerShell在许多(但不是全部)上下文中使用不变量而不是当前文化,例如运算符-eq
,-contains
,-in
和switch
语句。
如果不需要锚定子字符串搜索,即,如果您只想知道子字符串是否包含在输入字符串中的某个位置,则可以使用String.Contains()
:
PS> 'foo\bar'.Contains('oo\') #'# substring is present, but not at the start
True
关于区分大小写的上述观点也适用于此。
使用-match
运算符:
虽然-match
隐式执行子串匹配,但它基于正则表达式(regular expression)而不是文字字符串。
-match
默认执行不区分大小写的匹配;使用-cmatch
变体区分大小写。
这意味着您可以方便地使用^
(输入开始锚点)来确保搜索表达式仅匹配输入字符串的开头。
相反,为了将您的搜索字符串视为正则表达式中的文字字符串,您必须在正则表达式中\
-escape其中的任何正则字符元字符(具有特殊含义的字符)。
因为\
本身就是一个元字符,所以它也必须逃脱,即\\
。
在字符串文字中,您可以手动执行转义:
# Manual escaping: \ is doubled.
# Note the ^ to anchor matching at the start.
PS> 'foo\bar' -match '^foo\\'
True
以编程方式,当字符串作为变量时,必须使用[regex]::Escape()
方法:
# Programmatic escaping via [regex]::Escape()
# Note the ^ to anchor matching at the start.
PS> $s = 'foo\'; 'foo\bar' -match ('^' + [regex]::Escape($s))
True
使用-like
运算符:
与-match
不同,-like
执行全字符串匹配,并基于wildcard expressions(在Unix世界中的a.k.a globs)执行此操作;虽然与正则表达式有很大关系,但它们使用更简单,不兼容的语法(并且功能强大得多)。
-like
默认执行不区分大小写的匹配;使用-clike
变体区分大小写。
通配符只有3个基本结构,因此只有3个元字符:?
(匹配单个字符。),*
(匹配任意数量的字符,包括无字符)和[
(字符集的开头或匹配单个字符的范围) char。,例如,[a-z]
或[45]
)。
在最简单的情况下,您可以将*
附加到搜索字符串,以查看它是否与输入字符串的开头匹配:
# OK, because 'foo\' contains none of: ? * [
PS> 'foo\bar' -like 'foo\*'
True
# With a variable, using an expandable string:
PS> $s = 'foo\'; 'foo\bar' -like "$s*"
True
然而,与-match
一样,程序化转义可能是必要的,这需要调用[WildcardPattern]::Escape()
:
PS> $s = 'foo['; 'foo[bar' -like ([WildcardPattern]::Escape($s) + '*')
True
在这种特殊情况下,您可以使用-Match
。
$mountpoint -match [regex]::escape($path)
这里的问题是\
字符。它是正则表达式模式中的特殊字符,需要进行转义。由于-Match
运算符进行正则表达式匹配,因此需要考虑特殊字符。我选择在这种情况下使用Escape()
方法。你可以使用像\
这样的c:\\test
字符单独转义字符。 LotPings评论重申了这一想法。
使用正则表达式匹配,您可以控制您想要进行多少匹配。您可以包含锚点和其他特殊字符来定制您的匹配。 Regex101是测试和学习正则表达式的众多在线选项之一。
如果您在下面的示例中注意到,匹配返回True。这是因为字符串c:\test
存在于c:\testing
中,这可能会给你带来不必要的结果。您需要仔细考虑这些情况。
"c:\testing" -match [regex]::Escape("c:\test")
True
-Contains
和-in
是遏制运营商。它们的目的是检查对象值集合中是否存在单个对象值。例如,当您想要将像'c:\test'
这样的单个字符串与'c:\test','c:\folder','c:\folder\test'
这样的集合进行比较时,最好使用它们。它们采用您正在测试的值,并且基本上对集合中的每个项目执行-eq
比较(不是字面上但更有效)。但是,您可以比较集合,但整个测试集合必须作为参考集合中的元素存在。使用-Contains
,您希望您的参考集合在运营商的LHS上。使用-in
,您希望您的参考集合在运营商的RHS上。
示例使用-Contains和-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
请注意最后一个示例中的False返回,因为尾随的\
字符使其与集合中的任何元素不同。
有关About_Comparison_Operators的信息,请参阅-Match
,有关Regex.Escape Method方法的更多详细信息,请参阅Escape()
。