在PowerShell中匹配字符串

时间:2018-07-18 15:06:05

标签: regex powershell string-matching

该问题的要点是:

因此,if ($C -match $b.Name)似乎将字符串的部分匹配视为匹配?有没有更好的方法来强制字符串的 complete [match]?

我有一个目录,其中填充有大量的.7z文件。我需要不断清理该目录。还有一个脚本,该脚本早于我在这里的工作,目前正在工作,但是它由3000行组成,并不断生成不正确的匹配项,并且不记录移动或删除的内容。之所以如此之大,部分原因在于它具有大量的路径,这些路径需要将这些文件移至其中的硬编码位置。有时,这些路径会发生变化,并且很难进行更新。

因此,我开始制作一个较小的脚本,该脚本在CSV文件中引用了所有这些路径。除了这些路径之外,CSV文件中还记录了已知的文件名。

我正在尝试将文件名与CSV文件中记录的名称进行匹配。通常可以,但是有时候我得到不正确的匹配。

比方说,我有两个类似的文件开始,分别是Apple和Apple_Pie。 Apple将与Apple匹配并移至正确的目录,但是Apple_Pie将首先与Apple匹配并移至错误的目录。在清除$C变量之前,它将使Apple_Pie与正确的目录匹配,但是到那时,Apple_Pie不再存在于原始目录中,需要将其从中移出。

所以看来if ($C -match $b.Name)认为字符串的部分匹配是匹配的?有没有更好的方法来强制完成一个字符串?

我认为我对-match应该如何工作的期望有些偏离。

我在这里使用的regex东西是剥离由另一个自动化过程添加到文件名的日期时间的每个文件名。我用它来隔离要匹配的文件名。

$Wild = "C:\Some\Folder\With\Files\"

$CSV = "C:\Another\Folder\Paths.csv"

$Content = gci $wild

$Reg1 = [regex] '_[0-9]{4}-[0-9]{2}-[0-9]{2}[A-Z]{1}[0-9]{2}_[0-9]{2}_[0-9]{2}'

$Reg2 = [regex] '[0-9]{4}-[0-9]{2}-[0-9]{2}[A-Z]{1}[0-9]{2}_[0-9]{2}_[0-9]{2}'

$Paths = import-csv -path $CSV -header Name, Path

foreach ($a in $content) {
    $c = $a.BaseName

    if ($c -match $reg1) {
        $c = $c -replace $regyear
    }
    elseif ($c -match $reg2) {
        $c = $c -replace $reg2
    }

    foreach ($b in $Paths) {

        if ($c -match $b.Name) {
            Do something
        }
    }
}

2 个答案:

答案 0 :(得分:2)

tl; dr

  • -match的确使RHS(右侧)上的正则表达式默认匹配 substrings
    • 'foo' -match 'o' # true
  • 不过,您可以使用^ 固定正则表达式以匹配输入字符串的 start ,和/或$匹配正则表达式。 结束
    • 'foo' -match '^foo$' # true - full match
    • 'foot' -match '^foo$' # false

继续阅读有关其他字符串匹配运算符的详细信息。


前言:

  • PowerShell 字符串-comparison operators 默认为不区分大小写 ,并且使用{{3 }} ,而不是当前的文化。

    • 您可以使用前缀c 来选择区分大小写
      例如-cmatch而不是-match
  • 所有比较运算符都可以 取反,且前缀为not ;例如,-notmatch取反-match

  • 单个字符串作为LHS,比较运算符返回$True$False,但是具有 array 用作过滤器 的字符串;也就是说,它们返回比较结果为true的元素的 subarray


invariant culture对问题的评论提供了最好的解释(经过轻松编辑和强调):

  

[...] 默认情况下,如果可以在字符串的任何地方中找到[RHS]模式(正则表达式),则-match将返回$True如果要在字符串中的某些位置查找字符串,请使用^来指示字符串的开头,并使用$来指示字符串的结尾。要匹配整个字符串,请同时使用两者。

应用于部分代码:

$Reg2 = '^[0-9]{4}-[0-9]{2}-[0-9]{2}[A-Z]{1}[0-9]{2}_[0-9]{2}_[0-9]{2}$'

# ...

$c -match $Reg2

请注意开头的 ^ 和末尾的 $ ,以确保输入整个字符串必须匹配。

还要注意,由于[regex]可以直接接受 strings ,因此我没有必要使用-match强制转换。

在相关说明中,您可以使用断言 \b 来修改子字符串匹配,以便匹配仅在单词边界(其中单词定义为任何非空的字母,数字和下划线);例如'a10' -match 'a1'是正确的,但'a10' -match 'a1\b'不是正确的,因为输入字符串中的1不在单词的末尾。

请注意,使用带有单个字符串的-match作为LHS(而不是数组)会在自动$Matches变量中记录最近匹配的详细信息,这是一个哈希表,其0条目包含整个匹配项(匹配输入字符串的一部分);如果在正则表达式中使用了捕获组(包含在(...)中的子表达式)-条目1包含捕获的第一个捕获组,2捕获的第二个捕获组,依此类推; 命名捕获组(例如,
(?<foo>...))按名称(例如foo)获取条目。

此外,您可以将 EBGreen's 与{{1}一起使用,而不是冗长的if / elseif构造来顺序匹配多个正则表达式。 }选项:

代替:

-regex

您可以写得更干净:

if ($c -match $reg1) {
  $c = $c -replace $regyear 
}
elseif ($c -match $reg2) {
  $c = $c -replace $reg2 
}

switch -regex ($c) { $reg1 { $c = $c -replace $regyear; break } $reg2 { $c = $c -replace $reg2; break } default { <# handles the case where nothing above matched #> } } 确保不再执行任何匹配。

  • break的默认匹配项(或带有选项switch)的默认匹配项类似于-exact运算符(请参见下文)。

  • 您还可以使用
    使它执行通配符表达式匹配,例如-eq运算符(见下文)。 -like选项。

  • -wildcard选项使匹配的任何一种模式都区分大小写。

  • 如果输入是 array ,则对每个元素执行匹配;请注意,-casesensitive然后停止处理其他元素,而break立即进入下一个元素。


PowerShell中其他字符串匹配方法

continue 允许您根据switch statement匹配字符串。

简单地说,-like匹配任何字符,包括 none *完全匹配 1 字符,而?与指定的 set range 字符中的任意一个字符匹配。

[...]不同, -match始终与 entire 字符串匹配,但是请注意,通配符表达式的语法与正则表达式根本不同,并且相距甚远功能不那么强大-您不能互换使用-like-like

因此,要获得 substring 匹配,请将-match放在表达式的两端;例如:

*

'ingot' -like '*go*' # true 比较整个字符串,从字面上看(区分大小写除外)。

请注意, PowerShell没有文字上的子字符串匹配运算符,但是您可以(有点笨拙)用-eqwildcard expressions模拟一个:

-match

'Cost: 7$.' -match [regex]::Escape('7$') # true 转义其参数,以便在将其内容解释为 regex [regex]::Escape()的RHS总是如此)时,将其内容按字面意义处理。

这有点效率低下,因为没有充分的理由使用正则表达式开始。

直接使用[regex]::Escape()方法 是一种选择,但也不是一件容易的事。以下与先前的命令等效:

-match

请注意,需要使用 'Cost: 7$.'.IndexOf('7$', [StringComparison]::InvariantCultureIgnoreCase) -ne -1 # true 来匹配PowerShell的默认行为,并且需要与InvariantCultureIgnoreCase进行比较,因为会返回子字符串开始处的字符索引

另一方面,通过.NET [string] type's .IndexOf()枚举的其他成员,此方法可让您更好地控制匹配的执行方式。
如果您正在寻找基于 current 区域性的区分大小写 子字符串匹配的方法,则可以仅依靠-1的默认行为;例如,
.IndexOf()
'I am here.'.IndexOf('am') -ne -1 # true


最后,请注意, [System.StringComparison] 管道中执行字符串匹配,并且它同时支持正则表达式(默认情况下)和文字子字符串匹配(使用'I am here.'.IndexOf('AM') -ne -1 # false, because matching is case-sensitive)开关。

与比较运算符不同,-SimpleMatch为包含原始行以及有关匹配项的元数据的每条匹配输入行输出类型为Select-String cmdlet的匹配信息对象。

答案 1 :(得分:1)

我认为您的主要问题是您使用的是“匹配”。

它检查右字符串是否是左字符串的...部分,而不是它是否符合您的期望。

$a = "Test"
$b = "Test_me"

$a -match $b
False

$b -match $a
True

我将-match替换为-like