30、正则表达式最佳实践

正则表达式最佳实践

1. 确定合适的范围

正则表达式功能强大,PowerShell 同样如此。在解决问题时,人们很容易陷入思维定式,试图仅用正则表达式来解决问题。因此,要评估不同的解决方法,可思考以下问题:
- 应该逐行处理文本还是将其作为一个整体处理?在不同的场景中,这两种方式的效率有所不同。使用单行模式的循环语句通常更简单,但对于跨多行的匹配目标则不适用。
- 是否可以用多个简单的模式和 PowerShell 语句来达到相同的结果?简单的模式更容易调试,而复杂的模式在多次切换编程逻辑和正则逻辑时可能更高效。
- 是否需要进行预处理?在将输入文本传递给提取模式之前,对其进行过滤和格式化(可以使用更多的正则表达式或其他方法),能够简化问题,这在处理无约束输入时尤为重要。
- 哪种方法创建起来更快?哪种方法运行起来更快?有些解决方案可能易于实现,但会牺牲性能。要考虑模式匹配所需的效率,并在交付解决方案的时间和效率之间进行平衡。

在编写正则表达式模式来解决问题之前,需要制定一个策略。这可能包括用各种输入测试模式片段,或者查看样本数据的一致性。也可以评估不同方法(如逐行匹配和全文匹配)的性能。在这个规划阶段,解决方案的思路通常会逐渐清晰,接下来只需将各个部分组合起来并进行优化。

2. 迭代开发

即使事先制定了策略,首次尝试的正则表达式模式也很少能直接投入使用。通常会有一些被忽略的情况,导致出现意外的行为。这意味着在模式符合要求之前,可能需要多次改进和重新测试。对模式进行优化的程度取决于解决方案的需求以及交付的时间要求。迭代开发是创建正则表达式模式及其相关工具的有效方法。

下面通过一个例子来展示迭代开发的过程。需求是模式能够捕获单个句子,并分离出句子中的每个单词,且不包含标点符号。最初的模式在解决了灾难性回溯问题后如下:

(?m)\b(?>([\w"'\(\)/-]+)[;,]?\s*)+[.?!]+

这个模式现在表现得更好了,但当至少有一个句子没有以有效的终止符(.?!:)结尾时,仍然会有不必要的计算。这是因为引擎会回溯并尝试从每个后续的单词边界开始寻找匹配。例如,对于字符串 ‘This has no end’,引擎会尝试以下匹配:

(This) (has) (no) (end)
(has) (no) (end)
(no) (end)
(end)

在每种情况下,由于缺少句子终止符,匹配都会失败,但引擎会一直尝试更短的匹配,直到没有完整的单词为止。这虽然不是灾难性回溯,但会影响模式的效率。

可以使用更高级的句子起始断言来消除对单词边界的需求,从而提高模式的效率。正向回顾后发断言 (?<=^\s*|[.?!]\s+) 表示在当前位置之前,必须是以下两种情况之一:
- 句子终止符和至少一个空格
- 字符串/行的开头和零个或多个空格

将这个断言集成到模式中,得到:

(?m)(?<=^\s*|[.?!]\s+)(?>([\w"'\(\)/-]+)[;,]?\s*)+[.?!]+

以下是一个测试新旧模式效率的示例代码:

$BadSentence = 'lots of words ' * 100
$OldPattern = '(?m)\b(?>([\w"''\(\)/-]+)[;,]?\s*)+[.?!]+'
$NewPattern = '(?m)(?<=^\s*|[.?!]\s+)(?>([\w"''\(\)/-]+)[;,]?\s*)+[.?!]+'

Write-Host (
    'Old pattern: {0} ms' -f (
        Measure-Command { [regex]::Matches($BadSentence, $OldPattern) }
    ).TotalMilliseconds
)

Write-Host (
    'New pattern: {0} ms' -f (
        Measure-Command { [regex]::Matches($BadSentence, $NewPattern) }
    ).TotalMilliseconds
)

运行结果显示,旧模式耗时 5.7201 毫秒,新模式耗时 0.4184 毫秒,新模式的效率有了显著提升。

然而,这个模式仍然无法匹配包含意外字符的句子。可以使用负自定义字符类来解决这个问题。类 [^.?!;,\s] 可以匹配除空格(\s)、句子终止符(.?!)和句子分隔符(:,)之外的任何字符。修改后的模式如下:

(?m)(?<=^\s*|[.?!]\s+)(?>([^.?!;,\s]+)[;,]?\s*)+[.?!]+

但这种排除少数字符的方法会引入一个新问题。单词组现在会捕获与单词相邻的字符,如引号和括号。例如,模式会将 ‘(Hello world)’ 捕获为 ‘(Hello’ 和 ‘world)’,这不符合不包含标点符号的要求。可以进一步扩展模式,以处理可能出现在单词前后的字符,并将它们排除在捕获之外。

由于模式现在变得很长,可以使用 IgnorePatternWhitespace 选项,将模式分散在多行中:

(?mx)
(?<=^\s*|[.?!]\s+)
# 1. 句子起始断言
(?>
    # 单词匹配组,无回溯
    [\('"/:.]*
    # 2. 单词前的任何字符
    ([^.?!;,\s\(\)"'/]+)
    # 3. 单词字符(捕获)
    [;,\)'"/]*
    # 3. 单词后的任何字符
    \s*
    # 4. 零个或多个空格
)+
# 匹配一个或多个单词
[.?!]+
# 5. 一个或多个句子终止符

这个模式现在可以处理单词前后的字符,捕获部分(第 3 部分)排除了这些额外的字符。这样做可以防止贪婪匹配消耗第 4 部分要捕获的字符,或者在回溯时第 2 部分将字符让给第 3 部分。此外,该模式现在还可以匹配数字中的小数点(如 6.47 和 .39)。

为了验证模式是否符合要求,需要进行测试。以下是一个测试示例:

$MyString = "The customer's name is Jane E. Doe."
$MyPattern = '(?mx)(?<=^\s*|[.?!]\s+)(?>[\(''"/:.]*([^.?!;,\s\(\)"''/]+)' +
    '[;,\)''"/]*\s*)+[.?!]+'

[regex]::Matches($MyString, $MyPattern).ForEach{
    Write-Host ('Sentence: "{0}"' -f $_.Value)
    $_.Groups[1].Captures.ForEach{
        Write-Host ('Word: "{0}"' -f $_.Value)
    }
}

运行结果显示,该模式将 “customer’s” 中的撇号解释为两个单词的边界,将 ‘Jane E. Doe’ 中的句点解释为句子终止符,这不符合预期。

首先解决撇号的问题,可以从主单词组中移除对撇号的排除,并使用负回顾后发断言确保单词末尾不会出现撇号。修改后的模式如下:

(?mx)
(?<=^\s*|[.?!]\s+)
# 1. 句子起始断言
(?>
    # 单词匹配组,无回溯
    [\('"/:.]*
    # 2. 单词前的任何字符
    ([^.?!;,\s\(\)"/]+)
    # 3. 单词字符(捕获)
    (?<!')
    # 6. 不能以撇号结尾
    [;,\)'"/]*
    # 3. 单词后的任何字符
    \s*
    # 4. 零个或多个空格
)+
# 匹配一个或多个单词
[.?!]+
# 5. 一个或多个句子终止符

接下来解决句点的问题。可以在单词组中添加一个更早的替代项 [A-Z]\. 来处理名字首字母中的句点。修改后的模式如下:

(?mx)
(?<=^\s*|[.?!]\s+)
# 1. 句子起始断言
(?>
    # 单词匹配组,无回溯
    [\('"/:.]*
    # 2. 单词前的任何字符
    ([A-Z]\.|[^.?!;,\s\(\)"/]+)
    # 3. 单词字符(捕获)
    (?<!')
    # 6. 不能以撇号结尾
    [;,\)'"/]*
    # 3. 单词后的任何字符
    \s*
    # 4. 零个或多个空格
)+
# 匹配一个或多个单词
[.?!]+
# 5. 一个或多个句子终止符

这个修改后的模式考虑了单字母名字首字母的情况,但会引入一个新的边缘情况。如果一个句子以单个大写字母和句点结尾,模式会继续匹配下一个句子。例如,对于句子 “Smith takes Vitamin D. Jones doesn’t take anything.”,计算机很难区分这是两个句子还是一个包含 ‘Vitamin D. Jones’ 名字的句子。虽然这是一个边缘情况,但带首字母的名字比以单个大写字母结尾的句子更常见,因此在这种情况下,这种修改是合理的。

在开发正则表达式时,还需要考虑以下几个方面,这些方面可能会推动模式的进一步演变:
- 是否应该独立捕获连字符连接的复合词(如 cross-platform)?现有模式将它们视为单个单词。
- 是否应该将子句分隔符(如破折号 —)视为单词分隔符?如果破折号周围没有空格,现有模式会将它们视为一个连字符连接的单词。
- 是否应该排除更多单词周围的开闭标点符号?现有模式会将一些标点符号(如花括号 {})与单词一起捕获。可以使用 Unicode 类别(如开头标点 \p{Ps} 和结尾标点 \p{Pe})来处理这个问题。
- 是否应该将带小数点的数字视为单个单词?现有模式将它们视为两个单词。
- 是否应该处理缩写(如 ‘Mrs.’ 和 ‘Prof.’)?现有模式会在这些地方断开句子。
- 是否应该处理冒号后跟换行符的情况?现有模式会将后面的文本视为连续的句子。
- 是否应该使用 Unicode 类别处理其他语言的标点符号?现有模式仅旨在匹配英语句子。

以下是一个满足部分要求但未涵盖所有边缘情况的模式示例:

$Sentences = @'
This sentence contains "quotes", (brackets), and the number 42.01.
Mr. E. Smith's [first] name is John—E is for example.
PowerShell is cross-platform.
This sentence isn''t valid
'@

$MyPattern = '(?m)(?<=^\s*|[.?!]\s+)(?>[\(''"/:.]*([A-Z]\.|' +
    '[^.?!;,\s\(\)"/]+)(?<!'')[;,\)''"/]*\s*)+[.?!]+'

[regex]::Matches($Sentences, $MyPattern).ForEach{
    Write-Host ('Sentence: "{0}"' -f $_.Value)
    Write-Host (
        'Words: {0}{1}' -f ($_.Groups[1].Captures.Value -join '/'),
        [Environment]::NewLine
    )
}

这个示例表明,在各种上下文中测试正则表达式模式非常重要。如果发现一种开发路径行不通,可能需要回到分析和规划阶段,这是正常的,就像正则表达式引擎可以回溯一样,人类也可以调整思路。

3. 边缘情况和近似匹配

在正则表达式开发中,对抗性测试常常被忽视。在开发和测试模式时,通常主要关注模式应该匹配的内容,但同样重要的是考虑模式可能匹配的内容。模式需要处理以下三种类型的输入:
- 与模式匹配的输入
- 几乎与模式匹配的输入
- 与模式不匹配的输入

其中,第二种情况往往会带来麻烦。除了测试模式匹配解决方案的有效和无效输入外,还需要测试近似匹配。有两种近似匹配会导致意外和不期望的行为:
- 应该与模式匹配但未匹配的输入,这是假阴性。
- 不应该与模式匹配但匹配了的输入,这是假阳性。

以下是一个匹配 IP 地址的模式示例:

$MyPattern =
    '(?:(?<Octets>25[0-5]|2[0-4][0-9]|1?[0-9]{1,2})\.){3}' +
    '(?<Octets>25[0-5]|2[0-4][0-9]|1?[0-9]{1,2})'

$Tests = [ordered]@{
    'Valid 1' = '198.51.100.255'
    # 有效
    'Valid 2' = '198.051.100.255'
    # 有效
    'Invalid 1' = '198.51.256.255'
    # 块 3 无效 >255
    'Invalid 2' = '198.51.100.256'
    # 块 4 无效 >255
}

$Tests.Keys.ForEach{
    $Result = [regex]::Match($Tests[$_], $MyPattern)
    $Match = $Result.Success ? $Result.Value : '-'
    [pscustomobject]@{
        Test = $_
        Input = $Tests[$_]
        Match = $Match
    }
}

运行结果如下:
| Test | Input | Match |
| ---- | ---- | ---- |
| Valid 1 | 198.51.100.255 | 198.51.100.255 |
| Valid 2 | 198.051.100.255 | - |
| Invalid 1 | 198.51.256.255 | - |
| Invalid 2 | 198.51.100.256 | 198.51.100.25 |

在这个示例中, Valid 2 是假阴性,因为模式无法处理 ‘051’ 中的零; Invalid 2 是假阳性,因为模式在不消耗最后一个 ‘6’ 的情况下仍然可以匹配。可以使用锚点(如 ^ 和 $)来解决假阳性问题,使用 [01]? 代替 1? 来解决假阴性问题。

这个示例强调了测试正则表达式模式和模式匹配解决方案的重要性。要尽可能多地使用不同的输入进行测试,问自己 “如何破坏这个模式?” 模式应该能够处理合理预期的任何输入。

4. 线程安全

在正则表达式对象的上下文中,线程安全是一个需要考虑的方面。一般情况下,不需要担心正则表达式对象的线程安全问题,但在某些场景下,如跨 PowerShell 运行空间访问正则表达式结果时,就需要关注这个问题。

[Regex] 类的实例是不可变且线程安全的,这意味着可以在多个线程中创建和使用正则表达式对象。然而,单个结果对象(如 [Match] [Group] [Capture] )及其关联的集合(如 [MatchCollection] [GroupCollection] [CaptureCollection] )并不是自动线程安全的。这是因为这些类中的一些使用了延迟求值来提高性能。

在可能的情况下,尽量在单个线程中访问正则表达式结果对象。如果必须在多个线程之间共享单个结果对象,可以使用静态的 Synchronized() 方法来获取对象的线程安全实例。这个方法会让引擎计算每个组的每个捕获,从而得到一个可以在不同线程之间使用的不可变对象。

以下是一个使用同步正则表达式结果的示例:

$MyString = 'F09FA694'
$HexValues = [regex]::Match($MyString, '(?i)([a-f0-9]{2})+')
$SyncMatch = [System.Text.RegularExpressions.Match]::Synchronized($HexValues)

$ApiUrl = 'https://ucdapi.org/unicode/latest/codepoint/hex/{0}'
[ref]$Counter = 0

$SyncMatch.Groups[1].Captures | ForEach-Object -Parallel {
    $Index = [System.Threading.Interlocked]::Increment($using:Counter)
    Write-Verbose "[Thread $Index]: Processing match '$_'" -Verbose
    $Data = Invoke-Restmethod -Uri ($using:ApiUrl -f $_.Value)
    '[Thread {0} Result] 0x{1} ({2}) = {3}' -f
    $Index, $_.Value.ToUpper(), [Convert]::ToInt32($_.Value, 16),
    ($Data.name ? $Data.name : $Data.name1)
} -ThrottleLimit 3 -Verbose

运行结果可能类似于:

VERBOSE: [Thread 2]: Processing match '9F'
VERBOSE: [Thread 1]: Processing match 'F0'
VERBOSE: [Thread 3]: Processing match 'A6'
VERBOSE: [Thread 4]: Processing match '94'
[Thread 1 Result] 0xF0 (240) = LATIN SMALL LETTER ETH
[Thread 3 Result] 0xA6 (166) = BROKEN BAR
[Thread 2 Result] 0x9F (159) = APPLICATION PROGRAM COMMAND
[Thread 4 Result] 0x94 (148) = CANCEL CHARACTER

需要注意的是,执行顺序可能与捕获顺序不匹配。 ForEach-Object -Parallel 使用 PowerShell 运行空间并行处理,PowerShell 处理输入集合的顺序是不确定的。使用线程安全的计数器可以显示每个捕获开始处理的顺序。由于 Web API 响应时间的差异,处理完成的顺序可能与输入顺序和开始顺序都不同。

如果必须跨线程枚举结果集合(如 [MatchCollection] [GroupCollection] [CaptureCollection] ),可以使用实例的 SyncRoot 属性,在访问时锁定对象,以确保同步访问。由于 PowerShell 没有 lock 语句,可以使用 [System.Threading.Monitor] 来确保对集合的同步访问。

以下是一个同步访问正则表达式结果集合的示例:

$MyString = '0xF0 0x9F 0x8C 0x8A 0xF0 0x9F 0xA6 0x94 0xF0 0x9F 0x8F 0xA1'
$MyPattern = '(?i)(0xF[0-4])(?: (0x[89a-f][0-9a-f])){3}'
$Utf8FourByte = [regex]::Matches($MyString, $MyPattern)
$ApiUrl = 'https://ucdapi.org/unicode/latest/chars/{0}'

$Utf8FourByte | ForEach-Object -Parallel {
    $LockTaken = $false
    try {
        [System.Threading.Monitor]::Enter($_.Groups.SyncRoot, [ref]$LockTaken)
        $LeadingByte = $_.Groups[1].Captures[0].Value
        $ContinuationBytes = $_.Groups[2].Captures.ForEach{ $_.Value }
        $UTF8 = $_.Groups[0].Value -ireplace '0x([0-9a-f]{2})', '$1'
    }
    catch {
        Write-Error -ErrorRecord $_
        return
    }
    finally {
        if ($LockTaken) {
            [System.Threading.Monitor]::Exit($_.Groups.SyncRoot)
        }
    }

    $Bytes = [byte[]]::new(4)
    $Bytes[0] = $LeadingByte -as [byte]
    for ($i = 0; $i -lt $ContinuationBytes.Count; $i++) {
        $Bytes[$i + 1] = $ContinuationBytes[$i] -as [byte]
    }

    $Char = [System.Text.Encoding]::UTF8.GetString($Bytes)
    $Data = Invoke-Restmethod -Uri ($using:ApiUrl -f $Char)

    [pscustomobject]@{
        UTF8 = $UTF8
        CodePoint = 'U+' +
        [System.Convert]::ToString($Data.codePoint, 16).ToUpper()
        Char = $Char
        Name = $Data.name ? $Data.name : $Data.name1
    }
}

这个示例中, [System.Threading.Monitor]::Enter(...) 尝试获取对象的排他锁。如果另一个线程已经锁定了该对象, Enter() 会等待直到锁被释放。 Exit() 会释放由 Enter() 锁定的对象。

5. 学习资源推荐

为了进一步学习正则表达式,可以参考以下资源:
- Modern IT Automation with PowerShell Extras 仓库 :GitHub 上的这个仓库包含一些更复杂的正则表达式模式及其实际应用,还有详细的分解和解释,可以在 Edition-01/Regex 文件夹中找到相关内容。
- 《Mastering Regular Expressions》 :作者是 Jeffrey Friedl,虽然最新版本是 2006 年的,但它是一本非常全面的正则表达式教程,包含特定语言的章节,包括 .NET。由于 .NET 的正则表达式实现近年来没有太大变化,书中的内容仍然适用于当前版本的 .NET 和 PowerShell。
- 《Regular Expressions Cookbook》 :作者是 Jan Goyvaerts 和 Steven Levithan,这本书可以作为创建正则表达式解决特定问题的直接指南。它介绍了常见的模式匹配问题,并在流行的编程环境中给出了解决方案,其中包括 .NET,因此这些解决方案无需修改即可在 PowerShell 中使用。
- 《Regular Expression Pocket Reference》 :作者是 Tony Stubblebine,这本手册提供了正则表达式语法的快速参考和一些现成的解决方案。
- Rex :这是一个由 Microsoft Research 的 RiSE Group 开发的工具,它使用符号有限自动机(SFA)为一个或多个 .NET 正则表达式模式高效生成匹配输入。它不仅产生了一些出版物,还可以用于评估自己的模式,帮助理解引擎如何解释模式。虽然原始的在线版本已经不可用,但仍然可以从 Microsoft 网站下载原始的 Rex 二进制文件。
- Automata .NET Library :Rex 是这个 GitHub 仓库的一部分,此外,该仓库还包含许多与有限状态自动机(FSA)和转换器(FST)相关的工具。其中的 Symbolic Regex Matcher 库是一种使用 SFA 解释正则表达式的高效替代方法。

通过不断学习和实践,结合这些资源,能够更好地掌握正则表达式的使用,提高模式匹配的效率和准确性。

下面是一个简单的 mermaid 流程图,展示正则表达式开发的基本流程:

graph LR
    A[确定问题] --> B[制定策略]
    B --> C[编写初始模式]
    C --> D[测试模式]
    D --> E{是否满足要求?}
    E -- 是 --> F[完成]
    E -- 否 --> G[分析问题]
    G --> H[改进模式]
    H --> D

这个流程图展示了从确定问题到最终完成正则表达式开发的基本步骤,包括制定策略、编写模式、测试、分析和改进等环节,体现了迭代开发的思想。

正则表达式最佳实践(续)

6. 总结与回顾

在正则表达式的使用过程中,我们已经了解了多个重要的方面,下面通过一个表格来总结前面提到的关键要点:
| 要点 | 说明 |
| ---- | ---- |
| 确定合适的范围 | 避免仅依赖正则表达式,评估不同解决方法,考虑处理文本的方式、是否预处理等 |
| 迭代开发 | 首次尝试的模式通常需多次改进和测试,根据需求和时间平衡优化程度 |
| 边缘情况和近似匹配 | 测试有效、无效和近似匹配的输入,处理假阴性和假阳性问题 |
| 线程安全 | [Regex] 类实例线程安全,单个结果对象及其集合需特殊处理 |

为了更清晰地展示这些要点之间的关系,我们可以用 mermaid 流程图来呈现:

graph LR
    A[确定合适的范围] --> B[迭代开发]
    B --> C[边缘情况和近似匹配]
    C --> D[线程安全]

这个流程图表明,在正则表达式的开发过程中,确定范围是基础,在此之上进行迭代开发,开发过程中要考虑边缘情况和近似匹配,最后在多线程场景下关注线程安全问题。

7. 操作步骤总结

在实际应用正则表达式时,我们可以按照以下步骤进行操作:
1. 确定问题 :明确要解决的模式匹配问题,例如提取特定格式的文本、验证输入的有效性等。
2. 制定策略 :思考多种解决方法,评估不同方法的效率和复杂度,确定处理文本的方式(逐行或整体),考虑是否需要预处理。
3. 编写初始模式 :根据策略编写正则表达式模式,可以从简单的模式开始,逐步添加功能。
4. 测试模式 :使用各种输入对模式进行测试,包括有效输入、无效输入和近似匹配的输入,检查是否能得到预期的结果。
5. 分析问题 :如果测试结果不符合要求,分析问题所在,例如是否存在假阴性或假阳性,是否有未考虑到的边缘情况。
6. 改进模式 :根据分析结果对模式进行改进,可能需要调整模式的结构、添加或删除某些元素。
7. 重复测试和改进 :不断重复测试和改进的过程,直到模式满足要求。
8. 考虑线程安全(如果需要) :如果在多线程环境中使用正则表达式,确保对结果对象的访问是线程安全的,可以使用 Synchronized() 方法或 SyncRoot 属性。

8. 常见问题及解决方法

在正则表达式的开发过程中,可能会遇到一些常见的问题,下面是一些问题及对应的解决方法:
| 问题 | 解决方法 |
| ---- | ---- |
| 灾难性回溯 | 使用原子组或更高级的断言来避免不必要的回溯,提高模式效率 |
| 假阴性和假阳性 | 检查模式是否遗漏了某些情况,使用锚点或更精确的匹配规则来解决 |
| 处理标点符号 | 调整模式,使用负自定义字符类或排除特定的标点符号,确保捕获的内容符合要求 |
| 线程安全问题 | 在多线程环境中,使用 Synchronized() 方法获取线程安全的结果对象,使用 SyncRoot 属性同步访问结果集合 |

9. 代码示例总结

以下是前面提到的一些重要代码示例的总结:
- 测试新旧模式效率

$BadSentence = 'lots of words ' * 100
$OldPattern = '(?m)\b(?>([\w"''\(\)/-]+)[;,]?\s*)+[.?!]+'
$NewPattern = '(?m)(?<=^\s*|[.?!]\s+)(?>([\w"''\(\)/-]+)[;,]?\s*)+[.?!]+'

Write-Host (
    'Old pattern: {0} ms' -f (
        Measure-Command { [regex]::Matches($BadSentence, $OldPattern) }
    ).TotalMilliseconds
)

Write-Host (
    'New pattern: {0} ms' -f (
        Measure-Command { [regex]::Matches($BadSentence, $NewPattern) }
    ).TotalMilliseconds
)
  • 测试模式匹配 IP 地址
$MyPattern =
    '(?:(?<Octets>25[0-5]|2[0-4][0-9]|1?[0-9]{1,2})\.){3}' +
    '(?<Octets>25[0-5]|2[0-4][0-9]|1?[0-9]{1,2})'

$Tests = [ordered]@{
    'Valid 1' = '198.51.100.255'
    # 有效
    'Valid 2' = '198.051.100.255'
    # 有效
    'Invalid 1' = '198.51.256.255'
    # 块 3 无效 >255
    'Invalid 2' = '198.51.100.256'
    # 块 4 无效 >255
}

$Tests.Keys.ForEach{
    $Result = [regex]::Match($Tests[$_], $MyPattern)
    $Match = $Result.Success ? $Result.Value : '-'
    [pscustomobject]@{
        Test = $_
        Input = $Tests[$_]
        Match = $Match
    }
}
  • 使用同步正则表达式结果
$MyString = 'F09FA694'
$HexValues = [regex]::Match($MyString, '(?i)([a-f0-9]{2})+')
$SyncMatch = [System.Text.RegularExpressions.Match]::Synchronized($HexValues)

$ApiUrl = 'https://ucdapi.org/unicode/latest/codepoint/hex/{0}'
[ref]$Counter = 0

$SyncMatch.Groups[1].Captures | ForEach-Object -Parallel {
    $Index = [System.Threading.Interlocked]::Increment($using:Counter)
    Write-Verbose "[Thread $Index]: Processing match '$_'" -Verbose
    $Data = Invoke-Restmethod -Uri ($using:ApiUrl -f $_.Value)
    '[Thread {0} Result] 0x{1} ({2}) = {3}' -f
    $Index, $_.Value.ToUpper(), [Convert]::ToInt32($_.Value, 16),
    ($Data.name ? $Data.name : $Data.name1)
} -ThrottleLimit 3 -Verbose
  • 同步访问正则表达式结果集合
$MyString = '0xF0 0x9F 0x8C 0x8A 0xF0 0x9F 0xA6 0x94 0xF0 0x9F 0x8F 0xA1'
$MyPattern = '(?i)(0xF[0-4])(?: (0x[89a-f][0-9a-f])){3}'
$Utf8FourByte = [regex]::Matches($MyString, $MyPattern)
$ApiUrl = 'https://ucdapi.org/unicode/latest/chars/{0}'

$Utf8FourByte | ForEach-Object -Parallel {
    $LockTaken = $false
    try {
        [System.Threading.Monitor]::Enter($_.Groups.SyncRoot, [ref]$LockTaken)
        $LeadingByte = $_.Groups[1].Captures[0].Value
        $ContinuationBytes = $_.Groups[2].Captures.ForEach{ $_.Value }
        $UTF8 = $_.Groups[0].Value -ireplace '0x([0-9a-f]{2})', '$1'
    }
    catch {
        Write-Error -ErrorRecord $_
        return
    }
    finally {
        if ($LockTaken) {
            [System.Threading.Monitor]::Exit($_.Groups.SyncRoot)
        }
    }

    $Bytes = [byte[]]::new(4)
    $Bytes[0] = $LeadingByte -as [byte]
    for ($i = 0; $i -lt $ContinuationBytes.Count; $i++) {
        $Bytes[$i + 1] = $ContinuationBytes[$i] -as [byte]
    }

    $Char = [System.Text.Encoding]::UTF8.GetString($Bytes)
    $Data = Invoke-Restmethod -Uri ($using:ApiUrl -f $Char)

    [pscustomobject]@{
        UTF8 = $UTF8
        CodePoint = 'U+' +
        [System.Convert]::ToString($Data.codePoint, 16).ToUpper()
        Char = $Char
        Name = $Data.name ? $Data.name : $Data.name1
    }
}

通过这些代码示例,我们可以更直观地理解正则表达式在不同场景下的应用和优化方法。在实际使用中,可以根据具体需求对这些代码进行修改和扩展。

总之,正则表达式是一个强大的工具,但要熟练掌握并有效使用它,需要不断地学习、实践和总结经验。通过遵循最佳实践,处理好各种细节和边缘情况,我们可以编写出高效、准确的正则表达式模式,解决各种复杂的模式匹配问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值