2022年8月7日

选择 (OR) |

选择是正则表达式中的一个术语,实际上是一个简单的“或”。

在正则表达式中,它用竖线 | 表示。

例如,我们想要找出编程语言:HTML、PHP、Java 或 JavaScript。

对应的正则表达式为:html|php|java(script)?

用例如下:

let regexp = /html|php|css|java(script)?/gi;

let str = "First HTML appeared, then CSS, then JavaScript";

alert( str.match(regexp) ); // 'HTML', 'CSS', 'JavaScript'

我们看到过类似的东西 —— 方括号。它允许我们在多个字符中进行选择,例如 gr[ae]y 匹配 graygrey

方括号只允许字符或字符类。选择允许任何表达式。正则表达式 A|B|C 表示表达式 ABC 其一均可。

例如:

  • gr(a|e)y 等同于 gr[ae]y
  • gra|ey 表示 graey

要将选择应用于模式中一部分内容的选择,我们可以将其括在括号中:

  • I love HTML|CSS 匹配 I love HTMLCSS
  • I love (HTML|CSS) 匹配 I love HTMLI love CSS

示例:用于时间匹配的正则表达式

在之前的章节中有个任务是构建用于查找形如 hh:mm 的时间字符串,例如 12:00。但是简单的 \d\d:\d\d 太模糊了。它也会匹配 25:99(因为 25 和 99 都与模式匹配,但这不是有效的时间)。

如何构建更好的模式?

我们可以应用更精细的匹配。首先,对于时间:

  • 如果第一位数是 01,那么下一位数可以是任何数值:[01]\d
  • 否则,如果第一位数是 2,那么下一位数必须是 [0-3]
  • 不允许其他的首位数。

我们可以使用选择在正则表达式中编写这两种变体:[01]\d|2[0-3]

接下来,分钟必须为从 0059 的数。写成正则表达式即为 [0-5]\d:第一个数字 0-5,然后是任何数字。

如果我们将小时和分钟的正则表达式组合在一起,我们会得到:[01]\d|2[0-3]:[0-5]\d

我们差不多完成了,但有一个问题。选择 | 现在恰好位于 [01]\d2[0-3]:[0-5]\d 之间。

也就是说:它只匹配符号左侧或右侧任一表达式。

[01]\d  |  2[0-3]:[0-5]\d

此模式查找 [01]\d2[0-3]:[0-5]\d

但这是错误的,应该只在正则表达式的“小时”部分使用选择,以允许 [01]\d2[0-3]。让我们通过将“小时”括在括号中来纠正这个问题:([01]\d|2[0-3]):[0-5]\d

最终的解决方案:

let regexp = /([01]\d|2[0-3]):[0-5]\d/g;

alert("00:00 10:10 23:59 25:99 1:2".match(regexp)); // 00:00,10:10,23:59

任务

有很多编程语言,例如 Java、JavaScript、PHP、C 或 C++。

构建一个正则表达式,用来匹配字符串 Java JavaScript PHP C++ C 中包含的编程语言:

let regexp = /你的正则表达式/g;

alert("Java JavaScript PHP C++ C".match(regexp)); // Java JavaScript PHP C++ C

首先想到的解法是列出所有编程语言,在它们之间加上 | 符号。

但运行结果不符合预期:

let regexp = /Java|JavaScript|PHP|C|C\+\+/g;

let str = "Java, JavaScript, PHP, C, C++";

alert( str.match(regexp) ); // Java,Java,PHP,C,C

正则表达式引擎注意查找选择。也就是说:它先检查是否存在 Java,不存在 —— 接着匹配 JavaScript 及其后的字符串。

结果,JavaScript 永远匹配不到,因为 Java 先被匹配了。

CC++ 同理。

这个问题有两个解决方式:

  1. 更改顺序以先检查较长的匹配项:JavaScript|Java|C\+\+|C|PHP
  2. 合并相同前缀:Java(Script)?|C(\+\+)?|PHP

测试一下:

let regexp = /Java(Script)?|C(\+\+)?|PHP/g;

let str = "Java, JavaScript, PHP, C, C++";

alert( str.match(regexp) ); // Java,JavaScript,PHP,C,C++

“bb-tag” 形如 [tag]...[/tag]tag 匹配 burlquote 中之一。

例如:

[b]text[/b]
[url]http://google.com[/url]

BB-tags 可以嵌套。但标签不能自嵌套,比如:

可以:
[url] [b]http://google.com[/b] [/url]
[quote] [b]text[/b] [/quote]

不可以:
[b][b]text[/b][/b]

标签可以包含换行,通常:

[quote]
  [b]text[/b]
[/quote]

构造一个正则表达式用于查找所有 BB-tags 及其内容。

例如:

let regexp = /your regexp/flags;

let str = "..[url]http://google.com[/url]..";
alert( str.match(regexp) ); // [url]http://google.com[/url]

如果标签嵌套,那么我们需要记录匹配的外层标签(如果需要,我们可以继续在其内容中搜索):

let regexp = /你的正则表达式/flags;

let str = "..[url][b]http://google.com[/b][/url]..";
alert( str.match(regexp) ); // [url][b]http://google.com[/b][/url]

起始标签是 \[(b|url|quote)\]

匹配字符串直到遇到结束标签 —— 让我们使用模式 .*? 和修饰符 s 来匹配包括换行符在内的任何字符,然后向结束标签添加反向引用。

完整模式为:\[(b|url|quote)\].*?\[/\1]

代码运行如下:

let regexp = /\[(b|url|quote)].*?\[\/\1]/gs;

let str = `
  [b]hello![/b]
  [quote]
    [url]http://google.com[/url]
  [/quote]
`;

alert( str.match(regexp) ); // [b]hello![/b],[quote][url]http://google.com[/url][/quote]

请注意,除了转义 [ 之外,我们还必须为结束标签 [\/\1] 转义一个斜线,因为通常斜线会关闭模式。

构建一个正则表达式以查找双引号 "..." 中的字符串。

字符串应该支持转义,就像 JavaScript 字符串一样。例如,引号可以插入为 \",换行符可以插入为 \n,而反斜线本身可以插入为 \\

let str = "Just like \"here\".";

请注意,转义的引号 \" 不会结束字符串匹配。

所以,我们应该匹配两个引号之间的内容,且忽略中间转义的引号。

这是任务的基本部分,否则这个任务就没什么意思了。

要匹配的字符串示例:

.. "test me" ..
.. "Say \"Hello\"!" ..(其中有被转义的引号)
.. "\\" ..(其中有双反斜线)
.. "\\ \"" ..(其中有双反斜线和被转义的引号)

在 JavaScript 中,双反斜线用于把反斜线转义为字符串,如下所示:

let str = ' .. "test me" .. "Say \\"Hello\\"!" .. "\\\\ \\"" .. ';

// 存储中的字符串
alert(str); //  .. "test me" .. "Say \"Hello\"!" .. "\\ \"" ..

解决方案:/"(\\.|[^"\\])*"/g

一步一步来分析一下:

  • 首先匹配左双引号 "
  • 接着如果有反斜线 \\,则匹配其后跟随的任意字符(.)。(技术上,我们必须在模式中用双反斜线,因为它是一个特殊的字符,但实际上是一个反斜线字符)。
  • 如果没有,则匹配除双引号(表示字符串的末尾)和反斜线(排除仅存在反斜杠的情况,反斜杠仅在和其后字符一起使用时有效)外的任何字符:[^"\\]
  • ……继续匹配直到遇到右双引号

代码运行如下:

let regexp = /"(\\.|[^"\\])*"/g;
let str = ' .. "test me" .. "Say \\"Hello\\"!" .. "\\\\ \\"" .. ';

alert( str.match(regexp) ); // "test me","Say \"Hello\"!","\\ \""

写出一个正则表达式,用于查找 <style...> 标签。它应该匹配完整的标签:该标签可能没有特性(attributes)<style>,也可能有很多特性 <style type="..." id="...">

……同时正则表达式不应该匹配 <styler>

例如:

let regexp = /你的正则表达式/g;

alert( '<style> <styler> <style test="...">'.match(regexp) ); // <style>, <style test="...">

模式的开头很明显:<style

……但我们不能简单地将表达式写为 <style.*?>,因为会匹配上 <styler>

我们要匹配的是在 <style 之后紧跟着一个空格然后是可选的其他内容,或者直接是闭标签 >

写成正则表达式即为:<style(>|\s.*?>)

代码运行如下:

let regexp = /<style(>|\s.*?>)/g;

alert( '<style> <styler> <style test="...">'.match(regexp) ); // <style>, <style test="...">
教程路线图

评论

在评论之前先阅读本内容…
  • 如果你发现教程有错误,或者有其他需要修改和提升的地方 — 请 提交一个 GitHub issue 或 pull request,而不是在这评论。
  • 如果你对教程的内容有不理解的地方 — 请详细说明。
  • 使用 <code> 标签插入只有几个词的代码,插入多行代码可以使用 <pre> 标签,对于超过 10 行的代码,建议你使用沙箱(plnkrJSBincodepen…)