在 PowerShell 中匹配字符串

Och*_*use 5 regex powershell string-matching

这个问题要点是:

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

我有一个目录,里面装满了大量的 .7z 文件。我需要不断清理这个目录。还有另一个脚本,它早于我在这里的工作,目前正在工作,但它由 3000 行组成,并且不断生成不正确的匹配,并且不记录移动或删除的内容。使它如此庞大的部分原因是它有大量的路径,这些文件需要移动到硬编码的位置。有时,这些路径会发生变化,更新起来很麻烦。

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

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

假设我有两个以类似方式启动的文件,Apple 和 Apple_Pie。Apple 会匹配 Apple 并移动到正确的目录,但 Apple_Pie 将首先匹配 Apple 并移动到错误的目录。在$C清除变量之前,它将 Apple_Pie 匹配到正确的目录,但此时 Apple_Pie 不再存在于需要从中移动的原始目录中。

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

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

我在这里使用的正则表达式是去除由另一个自动化进程添加到文件名中的日期时间的每个文件名。我用它来隔离我想要匹配的文件名。

$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
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

mkl*_*nt0 11

太长了;博士

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


前言:

  • 默认情况下, PowerShell字符串比较运算符区分大小写与使用不变区域性的字符串运算符不同,正则表达式运算符似乎使用当前区域性,尽管这种差异在正则表达式操作中很少有影响)。

    • 您可以使用prefix选择区分大小写匹配; 例如,而不是.c
      -cmatch-match
  • 所有比较运算符都可以用前缀not反;例如,-notmatch否定-match.

  • 对于单个字符串作为 LHS,比较运算符返回$Trueor $False,但对于字符串数组,它们充当过滤器;也就是说,它们返回比较为真的元素的子数组。


EBGreen对这个问题的评论提供了最好的解释(经过轻微编辑并添加了重点):

[...]默认情况下,如果可以在字符串中的任何位置找到 [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
Run Code Online (Sandbox Code Playgroud)

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

另请注意,我省略了[regex]强制转换,因为它没有必要,因为-match可以直接接受字符串

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

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

此外,您可以使用带有选项的语句,而不是按顺序匹配多个正则表达式的冗长/if构造:elseifswitch-regex

代替:

if ($c -match $reg1) {
  $c = $c -replace $regyear 
}
elseif ($c -match $reg2) {
  $c = $c -replace $reg2 
}
Run Code Online (Sandbox Code Playgroud)

你可以写得更干净:

switch -regex ($c) {
  $reg1 { $c = $c -replace $regyear; break }
  $reg2 { $c = $c -replace $reg2;    break }
  default { <# handles the case where nothing above matched #> }
}
Run Code Online (Sandbox Code Playgroud)

break确保不再执行进一步的匹配。

  • switch的默认匹配(或与 option 一起-exact)的工作方式类似于-eq运算符(见下文)。

  • 您还可以使用该选项使其执行通配符表达式匹配 - 就像-like运算符(见下文)
    -wildcard

  • -casesensitive选项使任何匹配模式的匹配区分大小写。

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


PowerShell 中字符串匹配的其他方法

-like允许您根据通配符表达式匹配字符串。

简而言之,*匹配任何一串字符(包括none )?精确匹配1 个字符以及[...]匹配指定字符集或字符范围中的任何一个字符。

-match,-like始终匹配整个字符串,但请注意,通配符表达式的语法与正则表达式根本不同,并且功能远不那么强大 -不能互换使用-like-match

因此,要获得子字符串匹配,请在*表达式的两端放置一个 o ;例如:

'ingot' -like '*go*'  # true
Run Code Online (Sandbox Code Playgroud)

-eq按字面意思比较整个字符串(大小写变化除外)。

请注意,PowerShell 没有文字子字符串匹配运算符,但您可以(有点笨拙)使用-matchand模拟一个[regex]::Escape()

 'Cost: 7$.' -match [regex]::Escape('7$') # true
Run Code Online (Sandbox Code Playgroud)

[regex]::Escape()转义其参数,以便在将其内容解释为正则表达式字面处理(其 RHS始终如此)。-match

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

直接使用.NET[string]类型的.IndexOf()方法一种选择,但也很重要;以下命令与前面的命令等效:

 'Cost: 7$.'.IndexOf('7$', [StringComparison]::InvariantCultureIgnoreCase) -ne -1 # true
Run Code Online (Sandbox Code Playgroud)

请注意,需要使用InvariantCultureIgnoreCase来匹配 PowerShell 的默认行为,并且需要比较-1,因为返回了子字符串开始位置的字符索引。

另一方面,此方法使您可以通过枚举的其他成员更好地控制如何执行匹配[System.StringComparison]
如果您正在寻找基于当前区域性区分大小写的子字符串匹配,那么您可以简单地依赖;的默认行为。例如,与.IndexOf()
'I am here.'.IndexOf('am') -ne -1 # true
'I am here.'.IndexOf('AM') -ne -1 # false, because matching is case-sensitive


最后,请注意,Select-Stringcmdlet在管道中执行字符串匹配,并且它支持正则表达式(默认情况下)和文字子字符串匹配(使用-SimpleMatch)开关。

与比较运算符不同,为每个匹配输入行Select-Object输出一个类型的匹配信息对象[Microsoft.PowerShell.Commands.MatchInfo],其中包含原始行以及有关匹配的元数据。


小智 4

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

它检查右侧字符串是否是...左侧字符串的一部分,而不是检查它是否是您期望的实际匹配项。

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

$a -match $b
False

$b -match $a
True
Run Code Online (Sandbox Code Playgroud)

我会替换-match-like.

  • 如果您建议使用 `-like` 而不是 `-match`,请明确指出 (a) 您将无法将 _regex_ 与 `-like` 一起使用,并且 (b) 通配符表达式“-like”(唯一)支持的功能远不如正则表达式强大。另外,我想澄清一下,“-match”并不将 RHS 视为_string_,而是将其视为_regex_ - 只是为了明确“-match”不执行_literal_子字符串匹配。 (2认同)