匹配对称的括号

本文探讨了使用正则表达式匹配对称括号的挑战,尤其在处理配置文件和源代码时。通过示例解析了不同正则表达式的适用场景,并介绍了如何通过递归模式匹配嵌套括号。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

引用地址:http://book.51cto.com/art/200801/64658.htm

《精通正则表达式》本书主要讲了表达式的正则引擎及其工作原理,本文主要为匹配对称的括号 。

作者:Friedl,J.E.F.著/余晟译来源:电子工业出版社博文视点|2008-01-21 16:30

匹配对称的括号
Matching Balanced Sets of Parentheses

对称的圆括号、方括号之类的符号匹配起来非常麻烦。在处理配置文件和源代码时,经常需要匹配对称的括号。例如,解析C语言代码时可能需要处理某个函数的所有参数。函数的参数包含在函数名称之后的括号里,而这些参数本身又有可能包含嵌套的函数调用或是算式中的括号。我们先不考虑嵌套的括号,你或许会想到「\bfoo\([^])*\」,但这行不通。

秉承C的光荣传统,我把示范函数命名为foo。表达式中的标记部分是用来捕获参数的。对于foo(2,4.0)和foo(somevar,3.7)之类的参数,这个表达式完全没问题。但是,它也可以匹配foo(bar(somevar),3.7),这可不是我们需要的。所以要用到比「[^)]*」更聪明的办法。

为了匹配括号部分,我们可以尝试下面的这些正则表达式:

1. \(.*\)  括号及括号内部的任何字符。
2. \([^)]*\)  从一个开括号到最近的闭括号。
3. \([^()]*\)  从一个开括号到最近的闭括号,但是不容许其中包含开括号。

图5-1显示了对一行简单代码应用这些表达式的结果。

图5-1:三个表达式的匹配位置

我们看到,第一个正则表达式匹配的内容太多(注2),第二个正则表达式匹配的内容太少,第三个正则表达式无法匹配。孤立地看,第三个正则表达式能够匹配‘(this)’,但是因为表达式要求它必须紧接在foo之后,所以无法匹配。所以,这三个表达式都不合格。

真正的问题在于,大多数系统中,正则表达式无法匹配任意深度的嵌套结构。在很长的时间内,这是放之四海而皆准的规则,但是现在Perl、.NET和PCRE/PHP都提供了解决的办法(参见第328、436、475页)。但是,即使不用这些功能,我们也可以用正则表达式来匹配特定深度的嵌套括号,但不是任意深度的嵌套括号。处理单层嵌套的正则表达式是:
「\[^()]*(\([^()]*\)[^()]*)*\)」

这样类推下去,更深层次的嵌套就复杂得可怕。但是,下面的Perl程序,在指定嵌套深度$depth之后,生成的正则表达式可以匹配最大深度为$depth的嵌套括号。它使用的是Perl的“string x count”运算符,这个运算符会把string重复count次:
$regex = '\('.'(?:[^()]|\(' x $depth . '[^()]*' . '\))*' x $depth .'\)';

这个表达式留给读者分析。

<think>我们正在讨论如何修改正则表达式 `(0|1|1\.0+|0?\.\d+)` 以匹配括号。注意,用户的问题中已经包含了括号,但用户可能希望匹配字符串中实际出现的括号字符(如 '(' 或 ')'),而不是作为分组符号的括号。 回顾用户的上一个问题,是关于比较两个正则表达式是否等价的。现在,用户的新问题是关于修改这个正则表达式以匹配括号。根据引用[1]和[3],正则表达式中的括号通常用于分组,但如果要匹配实际的括号字符,我们需要转义它们。 当前表达式:`(0|1|1\.0+|0?\.\d+)` 这个表达式中的括号是分组括号,它们并不匹配字符 '(' 或 ')'。如果我们希望匹配包含括号的字符串,比如在数字周围可能有括号,例如 "(0.5)" 或 "(1)",那么我们需要修改正则表达式。 修改目标:我们希望匹配的数字可能被括号包围,例如: - "(0)" -> 匹配 - "(1.0)" -> 匹配 - "0.5" -> 仍然匹配(没有括号的情况) - "1" -> 仍然匹配 因此,我们需要在现有表达式的基础上,允许可选的括号出现在整个数字的两侧。 修改步骤: 1. 在现有正则表达式的开头添加可选的左括号:使用 `\(?` 表示可选的左括号(注意:括号在正则中是特殊字符,所以左括号用`\(`,右括号用`\)`,`?`表示0个或1个)。 2. 在现有正则表达式的结尾添加可选的右括号:同样使用 `\)?`。 3. 但是注意:括号必须成对出现?用户可能希望匹配单个括号?但通常括号是成对出现的,所以我们可以考虑将整个数字部分用分组括起来,然后在分组前后分别添加可选的括号。但这样可能会允许不对称括号(如只有左括号没有右括号),所以我们可以使用非捕获组来将括号作为一个整体可选。 更合理的做法:将一对括号作为可选的,即要么同时有左右括号,要么都没有。我们可以使用非捕获组(以避免捕获组增加)和量词`?`来表示整个括号部分是可选的。 修改后的正则表达式:`\(?(0|1|1\.0+|0?\.\d+)\)?` 但是,这个表达式允许单个括号(例如,只有左括号或只有右括号)。如果我们要求括号必须成对出现,那么应该这样写:`(?:\(([0-1]|1\.0+|0?\.\d+)\)|([0-1]|1\.0+|0?\.\d+))`,但这会使得表达式变得复杂,而且重复了内部的模式。 另一种更简洁的方式:使用非捕获组将括号和内容作为一个整体,然后这个整体是可选的。但是注意,我们不能简单地在整个表达式外面加可选的括号,因为那样会允许左括号在数字前,右括号在数字后,但不要求同时存在。 如果我们要求括号必须成对出现,那么可以这样: `(?:\( (0|1|1\.0+|0?\.\d+) \))?` 但这样写实际上会匹配空字符串(因为整个组是可选的),而且括号内的内容仍然是必须匹配的。另外,这样写我们无法同时匹配没有括号的情况和有成对括号的情况。 因此,我们可以将表达式拆分成两种情况: - 情况1:没有括号,直接匹配数字部分:`(0|1|1\.0+|0?\.\d+)` - 情况2:有括号括号内是数字部分:`\((0|1|1\.0+|0?\.\d+)\)` 然后用或操作符将两种情况合并:`(?:0|1|1\.0+|0?\.\d+|\((?:0|1|1\.0+|0?\.\d+)\))` 但是这样写重复了数字部分,我们可以用分组和反向引用来避免重复,但这样更复杂,而且有些引擎不支持。 一个更好的办法是:将数字部分提取出来,然后将其作为一个整体,然后这个整体可以出现在括号内,也可以没有括号。我们可以使用非捕获组来避免额外的捕获。 修改后的正则表达式: 首先,将数字部分定义为一个非捕获组:`(?:0|1|1\.0+|0?\.\d+)` 然后,整个表达式可以是:这个非捕获组,或者被括号包围的这个非捕获组:`(?:\( (?:0|1|1\.0+|0?\.\d+) \) | (?:0|1|1\.0+|0?\.\d+) )` 但是这样写有两个分支,而且两个分支的数字部分相同。我们可以用以下方式简化: 整个表达式:`(?:\( ?)?(?:0|1|1\.0+|0?\.\d+)(?: ?\))?` 但这样仍然允许不对称括号(比如只有左括号或只有右括号)。所以,如果我们要求括号成对出现,就不能用这种简单的可选。 因此,我们回到两个分支的方案: 正则表达式:`\(?(?:0|1|1\.0+|0?\.\d+)\)?` 允许不对称括号(可能不符合要求)。 或者,我们可以使用条件判断(但大多数正则引擎不支持)或者使用环视(这会使表达式复杂,而且可能不支持)。 根据实际需求,如果用户允许不对称括号,那么使用`\(?`和`\)?`是简单的。但如果要求成对,则必须使用两个分支。 考虑到实际使用中,括号通常是成对的,我建议使用两个分支,并避免重复数字部分的方法:将数字部分写在一个非捕获组中,然后两个分支分别匹配括号和有括号的情况。 最终表达式: 数字部分:`(?:0|1|1\.0+|0?\.\d+)` 整个表达式:`(?:(?:0|1|1\.0+|0?\.\d+)|\((?:0|1|1\.0+|0?\.\d+)\))` 但是这样写重复了数字部分,我们可以用捕获组来引用,但这样会引入捕获。或者,如果支持的话,可以用命名组,但这里我们不需要捕获,所以重复一次也可以接受。 另一种写法:用相同的非捕获组两次: `(?:0|1|1\.0+|0?\.\d+|\((?:0|1|1\.0+|0?\.\d+)\))` 这样,整个表达式匹配: - 分支1:数字部分(无括号) - 分支2:括号内包含相同的数字部分 注意:在分支2中,括号内的数字部分不会单独捕获(因为使用了非捕获组)。 测试用例: - "0" -> 匹配(分支1) - "1.0" -> 匹配(分支1) - "(0)" -> 匹配(分支2) - "(1.0)" -> 匹配(分支2) - "(0.5)" -> 匹配(分支2,因为0.5在数字部分中) - "0.5" -> 匹配(分支1) - ")0(" -> 不匹配(因为分支要求括号是成对出现的,且左括号在左,右括号在右) - "(" 或 ")" -> 不匹配(因为没有数字部分) 这个表达式满足成对括号的要求。 但是,我们也可以考虑在数字部分前后允许空格?用户可能输入空格,比如 "( 0.5 )"。如果需要,可以在括号内添加`\s*`来匹配可能的空格。 修改为允许空格: `(?:0|1|1\.0+|0?\.\d+|\s*\(\s*(?:0|1|1\.0+|0?\.\d+)\s*\))` 这样,在括号内部和外部都可以有空格。 总结:根据用户需求,如果只需要匹配数字(包括有括号和无括号),且括号必须成对出现,我们可以使用上述表达式。 但是,用户原来的表达式是用于匹配0到1之间的数(包括0,1,0.5,1.0等)。现在加上括号后,我们仍然保持这个功能。 因此,修改后的正则表达式为: 允许括号成对出现,且括号内是数字部分,同时数字部分也可以没有括号。 最终表达式(允许空格): `(?:\s*(?:0|1|1\.0+|0?\.\d+)\s*|\s*\(\s*(?:0|1|1\.0+|0?\.\d+)\s*\))` 注意:这个表达式可能会匹配整个字符串(包括空格),如果我们希望严格匹配,可能需要调整空格的位置。 如果不需要空格,那么可以去掉`\s*`。 根据用户需求,我们提供两个版本: 1. 不允许空格:`(?:0|1|1\.0+|0?\.\d+|\((?:0|1|1\.0+|0?\.\d+)\))` 2. 允许空格:`(?:\s*(?:0|1|1\.0+|0?\.\d+)\s*|\s*\(\s*(?:0|1|1\.0+|0?\.\d+)\s*\))` 注意:在允许空格的版本中,数字部分前后以及括号内部前后都可以有空格。另外,整个表达式使用了非捕获组。 但是,我们也可以简化:将整个表达式视为一个整体,然后允许这个整体前后有空格,并且这个整体可以带括号也可以不带,但要求括号成对。我们可以这样: `\s*(?:\((?:0|1|1\.0+|0?\.\d+)\)|(?:0|1|1\.0+|0?\.\d+))\s*` 这样,我们匹配: - 可选空格 - 然后要么是括号内的数字部分,要么是数字部分(无括号) - 然后可选空格 这个表达式更清晰,而且避免了重复数字部分(因为分支中使用了相同的数字部分)。 所以,我推荐这个表达式: `\s*(?:\((?:0|1|1\.0+|0?\.\d+)\)|(?:0|1|1\.0+|0?\.\d+))\s*` 测试: - "0" -> 匹配 - "1.0" -> 匹配 - "(0)" -> 匹配 - "(1.0)" -> 匹配 - " 0.5 " -> 匹配 - " ( 0.5 ) " -> 匹配(注意,这里需要说明:这个表达式允许整体前后有空格,但括号内部不允许有空格?因为括号内的部分没有设计空格。所以上面的表达式在括号内部没有允许空格。如果要允许括号内部有空格,我们需要修改括号内的部分:在括号内添加空格匹配) 修改为允许括号内部有空格: `\s*(?:\(\s*(?:0|1|1\.0+|0?\.\d+)\s*\)|(?:0|1|1\.0+|0?\.\d+))\s*` 这样,在左括号后面和右括号前面都可以有空格。 因此,最终的正则表达式为: `\s*(?:\(\s*(?:0|1|1\.0+|0?\.\d+)\s*\)|(?:0|1|1\.0+|0?\.\d+))\s*` 这个表达式可以匹配: "0", "1", "1.0", "0.5", ".5", "(0)", "(1)", "(0.5)", "( .5 )", " 1.0 " 等。 注意:原来的数字部分中,`0?\.\d+` 可以匹配 ".5",所以括号内也可以匹配"( .5 )"(注意括号内的空格)。 但是,这个表达式也会匹配空字符串吗?不会,因为数字部分必须匹配至少一个字符(例如,分支1:0,1,1.0+等,分支2:括号内的内容同样要求数字部分非空)。 因此,这个表达式满足要求。 如果用户不需要匹配空格,可以去掉所有`\s*`。 所以,根据用户需求,我们给出两个版本: 版本1(不允许空格): `(?:0|1|1\.0+|0?\.\d+|\((?:0|1|1\.0+|0?\.\d+)\))` 版本2(允许空格): `\s*(?:\(\s*(?:0|1|1\.0+|0?\.\d+)\s*\)|(?:0|1|1\.0+|0?\.\d+))\s*` 用户可以根据实际情况选择。 注意:在版本2中,整个匹配的字符串前后可能有空格,如果用于验证输入,可能需要配合开始和结束锚点(如^和$)以确保整个字符串匹配。 例如,在验证用户输入时,我们可以这样写(允许空格版本): `^\s*(?:\(\s*(?:0|1|1\.0+|0?\.\d+)\s*\)|(?:0|1|1\.0+|0?\.\d+))\s*$` 这样确保整个字符串必须符合这个模式。 最后,我们回到用户的问题:如何修改正则表达式 `(0|1|1\.0+|0?\.\d+)` 以匹配括号。我们提供了上述版本。 注意:原来的表达式有捕获组,我们修改后的表达式使用了非捕获组(避免不必要的捕获)。如果用户需要捕获数字部分(去除括号和空格),可以在数字部分使用捕获组,但这样会使得表达式更复杂。这里我们只考虑匹配,不考虑捕获。 因此,回答如下:</think>要修改正则表达式 `(0|1|1\.0+|0?\.\d+)` 以匹配括号(即允许数字被括号包围),需根据需求分两种情况处理: --- ### 情况1:允许不对称括号(单独左/右括号) 直接在现有表达式前后添加可选的左/右括号: ```regex \(?(0|1|1\.0+|0?\.\d+)\)? ``` - **匹配示例**: - `0` → 匹配 - `(1)` → 匹配 - `0.5)` → 匹配(只有右括号) - `(1.0` → 匹配(只有左括号) - **缺点**:允许不对称括号(如 `(1` 或 `0.5)`)。 --- ### 情况2:严格匹配成对括号(推荐) 使用分支结构确保括号成对出现: ```regex (?:\((0|1|1\.0+|0?\.\d+)\)|(0|1|1\.0+|0?\.\d+)) ``` 或更简洁的非捕获组版本: ```regex (?:(0|1|1\.0+|0?\.\d+)|\(((?:0|1|1\.0+|0?\.\d+))\)) ``` - **匹配示例**: - `0` → 匹配(无括号) - `(1)` → 匹配(成对括号) - `1.0` → 匹配(无括号) - `(0.5)` → 匹配(成对括号) - `(1.0` → **不匹配**(缺少右括号) - `0.5)` → **不匹配**(缺少左括号) --- ### 进阶:允许括号内带空格 若需匹配如 `( 0.5 )` 的格式,添加 `\s*` 匹配空格: ```regex \s*\(\s*((?:0|1|1\.0+|0?\.\d+))\s*\)\s*|\s*((?:0|1|1\.0+|0?\.\d+))\s* ``` - **功能**: - `\s*` 匹配零或多个空格 - 支持 `0.5`、`(0.5)`、`( 1.0 )` 等格式 --- ### 关键修改点说明 1. **转义括号**: 括号 `()` 是正则中的元字符,需转义为 `\(` 和 `\)` 才能匹配实际字符。 2. **分支结构 `|`**: 通过 `(A|B)` 分支处理“有括号”和“无括号”两种情况。 3. **非捕获组 `(?:...)`**: 避免生成冗余捕获组(如 `(?:0|1|...)`)[^1][^3]。 4. **空格处理 `\s*`**: 灵活匹配括号内外的空格(如 `( 1.0 )`)。 > 推荐使用 **成对括号版本** 避免非法格式。实际应用时,根据需求添加行首/行尾锚点(如 `^...$`)确保严格匹配
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值