【问题标题】:Regex character exclusion正则表达式字符排除
【发布时间】:2021-09-16 12:32:13
【问题描述】:

我正在尝试验证包含一个或多个标记的字符串,其格式为 [Item~SomeNameHere],该标记的颠覆为 [Item~Incerement***],其中 *** 是我稍后将进一步验证的多种可能性。关键是增量标记必须是字符串中的最终标记。 所以[Item~Increment] 有效,_[Item~modifiedDate][Item~Increment(#)] 有效但_[Item~Increment(#)][Item~modifiedDate] 无效。目标是一个非常灵活的面包屑功能,然后这些标记将被替换为最终文件或文件夹名称的其他数据。

为此,我从这个正则表达式 \[Item~Increment.*\] 开始,它确实在字符串中找到了标记。因此,我对其进行了修改,以使用\[Item~Increment.*\]$ 查找字符串末尾的标记,这可以正常工作,直到像[Item~Increment(#)][Item~modifiedDate] 这样的示例,其中.* 匹配(#)][Item~modifiedDate 并在需要False 的地方生成True。不知何故,我需要. 任何字符,不包括[],零次或多次。但是\[Item~Increment[.-\[\]]*\]$ 并没有完成工作,我现在已经超出了我的 RegEx 深度。我也试过用\[Item~Increment[^\[\]]*\]$ 否定它,它也失败了,总是错误的。

编辑:澄清一下,增量标记只能出现一次并且只能在最后出现。但是,在字符串的前面可能有其他形式为[Item~???][???~???] 的标记,也可能有文字字符。所以-[Some~String]_[Item~Date]_[Item~Increment] 是有效的。 根据@wiktor-stribiżew 的回答,我对内容进行了一些改进,并转录为 PowerShell 命名法,现在我有了这个......

$breadcrumbs = @('none', '[Item~Increment]', '[Item~Increment(#)]', '[Item~modifiedDate][Item~Increment(#)]', '[Item~Increment][Item~modifiedDate]', '[Item~Increment][Item~Increment]')
$pattern = '(?:\[Item~[^][]*])*\[Item~Increment[^][]*]$'

CLS
foreach ($breadcrumb in $breadcrumbs) {
    Write-Host "$([regex]::matches($breadcrumb, $pattern).Count) $breadcrumb"
}

生产...

0 none
1 [Item~Increment]
1 [Item~Increment(#)]
1 [Item~modifiedDate][Item~Increment(#)]
0 [Item~Increment][Item~modifiedDate]
1 [Item~Increment][Item~Increment]

理论上第一个应该失败,因为没有增量令牌理论上第一个应该通过,因为没有不正确的令牌,最后一个应该失败,因为有两个增量令牌,并且倒数第二个应该失败,因为有一个增量标记不在字符串的末尾。

但是这个 RegEx 在最终测试中失败了,我认为是因为 [Item~Increment][Item~Increment] 被匹配为

[Item~Increment
][Item~Increment
]

其中][Item~Increment 是变量内容。

这是我错误地转录到 PowerShell 吗?或者,RegEx 是否需要更多的东西来确保 [] 不会出现在令牌内,因此这个例子会产生两个计数或失败。我不介意需要另一个计数测试,因为这是为用户生成的有用错误。但就目前而言,我得到的计数是 1,这是无效的。

EDIT2:Doj 的回答也很有趣,而且也更短。对于最后一个示例,使用\[Item~Increment[^\[\]]*\] 我得到的计数为 2,但未处理订单。将其修改为像这样\[Item~Increment[^\[\]]*\]$ 并处理订单,但不再是倍数。呃。

EDIT3:根据 Doj 的回答结合这两种模式让我明白了这一点

foreach ($breadcrumb in $breadcrumbs) {
    $incrementCount = ([regex]::matches($breadcrumb, '\[Item~Increment[^\[\]]*\]')).Count
    if ($incrementCount -eq 0) {
        Write-Host "$breadcrumb good"
    } elseif ($incrementCount -gt 1) {
        Write-Host 'Duplicate [Item~Increment] tokens'
    } else {
        if (([regex]::matches($breadcrumb, '\[Item~Increment[^\[\]]*\]$')).Count -ne 0) {
            Write-Host "$breadcrumb good"
        } else {
             Write-Host '[Item~Increment] token not at the end of the string'
        }
    }
}

产生...

none good
[Item~Increment] good
[Item~Increment(#)] good
[Item~modifiedDate][Item~Increment(#)] good
[Item~Increment] token not at the end of the string
Duplicate [Item~Increment] tokens

我要去参加比赛了!

EDIT4:因为学习两种做事的方法总是比一种更好,所以我修改了 Wiktor 的 PS 方法,就像这样......

$breadcrumbs = @('none', '[Item~Increment]', '[Item~Increment%]', '[Item~modifiedDate][Item~Increment%]', '[Item~Increment][Item~modifiedDate]', '[Item~Increment][Item~Increment%]', '[Item~Increment]_')

CLS
foreach ($breadcrumb in $breadcrumbs) {
if ($breadcrumb -match '\A(?:\[Item~(?!Increment[^][]*])[^][]*])*\[Item~Increment[^][]*]\z') {
    Write-Host "$breadcrumb good"
} else {
    Write-Host "!!! $breadcrumb"
}

}

这会产生不正确的结果。

!!!没有 [项目~增量] 好 [Item~Increment%] 好 [Item~modifiedDate][Item~Increment%] 好 !!! [项目~增量][项目~修改日期] !!! [项目~增量][项目~增量%] !!! [项目~增量]_

我认为那里有两个不正确的结果,但我使用了错误的模式。 :(

【问题讨论】:

  • 不是 Powershell 人,但一般] 需要是字符类中的第一个字符,或者是否定字符类中^ 之后的第二个字符。
  • '_[Item~modifiedDate][Item~Increment(#)]' -match '\[Item~Increment[^\[\]]*\]$' 返回True'_[Item~Increment(#)][Item~modifiedDate]' -match '\[Item~Increment[^\[\]]*\]$' 返回False。这就是你想要的,不是吗?

标签: regex powershell regex-negation


【解决方案1】:

你可以使用

\A(?!.*\[Item~[^][]*])|\A(?:\[Item~(?!Increment[^][]*])[^][]*])*\[Item~Increment[^][]*]\z

this regex demo

详情

  • \A(?!.*\[Item~[^][]*]) - 在字符串的开头,尽可能多地检查除LF字符之外的任何零个或多个字符之后是否有[Item~...]子字符串,如果找到,则匹配失败
  • | - 或
  • \A - 字符串开头
  • (?: - 非捕获组的开始(用于量化模式序列的容器):
    • \[Item~ - [Item~ 子字符串
    • (?!Increment[^][]*]) - 如果存在Increment 字符串,则匹配失败的负前瞻,然后是除[] 之外的零个或多个字符,然后是紧邻当前位置右侧的] 字符李>
    • [^][]* - 除了[] 之外的零个或多个字符
    • ] - 一个 ] 字符
  • )* - 重复模式序列零次或多次
  • \[Item~Increment - \[Item~Increment 字符串
  • [^][]*] - 除了 [] 之外的零个或多个字符,然后是 ] 字符
  • \z - 字符串的最后。

如果您不想使用那种大模式并且需要提供两个不同的错误消息,您可以将替换解包到单独的正则表达式检查中。

查看此 Powershell 演示:

$rx_1 = '\[Item~[^][]*]' # Item is in string check
$rx_2 = '\A(?:\[Item~[^][]*])*\[Item~Increment[^][]*]\z' # II must be at the end of string
$rx_3 = '\[Item~Increment[^][]*](?!\z)' # II not at the end of string
foreach ($breadcrumb in $breadcrumbs) {
    if ($breadcrumb -notmatch $rx_1) {  # If no Item is in string
        Write-Host "$breadcrumb good"   # It is valid
    } else {
        if ($breadcrumb -match $rx_2) {         # If the string only contains Items
            if ($breadcrumb -notmatch $rx_3) {  # ...and no II is found not at the end
                Write-Host "$breadcrumb good"   # it is good
            } else {
                Write-Host 'An [Item~Increment] token not at the end of the string!'
            }
        } else {
             Write-Host 'No [Item~Increment] token at the end of the string or invalid format!'
        }
    }
}

输出:

【讨论】:

  • 如此接近。但是正如您在修订后的 OP 中看到的那样,当增量令牌加倍时,我仍然会得到一个糟糕的结果。但是我在 PowerShell 中工作,并且转录为 PowerShell 命名法可能会造成问题。
  • @Gordon 从您的编辑中,我看到您没有使用我的解决方案。我的第二个正则表达式只允许 one [Item~Increment...] 在字符串的末尾。请尝试 my 解决方案,并让知道什么不起作用。另外,为什么[regex]::Matches?要检查字符串是否匹配正则表达式,在 PS 中,您只需使用 $isMatched = $breadcrumb -match $regex(如果需要区分大小写匹配,请将 -match 替换为 -cmatch)。
  • 我更新了 OP 以更详细地显示我在您的方法中失败的地方。很确定这与我转置到 PowerShell 有关,但不确定我哪里出错了。虽然我只是用 \A not ^ & \z not $ 完全尝试了你的,但我得到了同样不正确的结果。我不知道那些字符串开始和结束的选项!会学到三样东西。 :)
  • @Gordon 但是你使用了第一个正则表达式,我建议使用 第二个 一个,\A(?:\[Item~(?!Increment[^][]*])[^][]*])*\[Item~Increment[^][]*]\z,参见demo
  • 唯一的“有效”字符串是 1) [Item~Increment]、2) [Item~Increment%] 和 3) [Item~modifiedDate][Item~Increment%]。这是预期的输出吗?
猜你喜欢
  • 1970-01-01
  • 2012-02-17
  • 1970-01-01
  • 1970-01-01
  • 2011-12-16
  • 2017-04-13
  • 2018-10-11
  • 2019-08-03
相关资源
最近更新 更多