标签 正则表达式 下的文章

来学习下搜索文件中内容的基本操作,然后下载我们的备忘录作为 grep 和正则表达式的快速参考指南。

 title=

grep 全局正则表达式打印 Global Regular Expression Print )是由 Ken Thompson 早在 1974 年开发的基本 Unix 命令之一。在计算领域,它无处不在,通常被用作为动词(“搜索一个文件中的内容”)。如果你的谈话对象有极客精神,那么它也能在真实生活场景中使用。(例如,“我会 grep 我的内存条来回想起那些信息。”)简而言之,grep 是一种用特定的字符模式来搜索文件中内容的方式。如果你感觉这听起来像是文字处理器或文本编辑器的现代 Find 功能,那么你就已经在计算行业感受到了 grep 的影响。

grep 绝不是被现代技术抛弃的远古命令,它的强大体现在两个方面:

  • grep 可以在终端操作数据流,因此你可以把它嵌入到复杂的处理中。你不仅可以在一个文本文件中查找文字,还可以提取文字后把它发给另一个命令。
  • grep 使用正则表达式来提供灵活的搜索能力。

虽然需要一些练习,但学习 grep 命令还是很容易的。本文会介绍一些我认为 grep 最有用的功能。

安装 grep

Linux 默认安装了 grep

MacOS 默认安装了 BSD 版的 grep。BSD 版的 grep 跟 GNU 版有一点不一样,因此如果你想完全参照本文,那么请使用 HomebrewMacPorts 安装 GNU 版的 grep

基础的 grep

所有版本的 grep 基础语法都一样。入参是匹配模式和你需要搜索的文件。它会把匹配到的每一行输出到你的终端。

$ grep gnu gpl-3.0.txt
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
<http://www.gnu.org/licenses/>.
<http://www.gnu.org/philosophy/why-not-lgpl.html>.

grep 命令默认大小写敏感,因此 “gnu”、“GNU”、“Gnu” 是三个不同的值。你可以使用 --ignore-case 选项来忽略大小写。

$ grep --ignore-case gnu gpl-3.0.txt
                    GNU GENERAL PUBLIC LICENSE
  The GNU General Public License is a free, copyleft license for
the GNU General Public License is intended to guarantee your freedom to
GNU General Public License for most of our software; it applies also to
[...16 more results...]
<http://www.gnu.org/licenses/>.
<http://www.gnu.org/philosophy/why-not-lgpl.html>.

你也可以通过 --invert-match 选项来输出所有没有匹配到的行:

$ grep --invert-match \
--ignore-case gnu gpl-3.0.txt
                      Version 3, 29 June 2007

 Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
[...648 lines...]
Public License instead of this License.  But first, please read

管道

能搜索文件中的文本内容是很有用的,但是 POSIX 的真正强大之处是可以通过“管道”来连接多条命令。我发现我使用 grep 最好的方式是把它与其他工具如 cuttrcurl 联合使用。

假如现在有一个文件,文件中每一行是我想要下载的技术论文。我可以打开文件手动点击每一个链接,然后点击火狐浏览器的选项把每一个文件保存到我的硬盘,但是需要点击多次且耗费很长时间。而我还可以搜索文件中的链接,用 --only-matching 选项打印出匹配到的字符串。

$ grep --only-matching http\:\/\/.*pdf example.html
http://example.com/linux_whitepaper.pdf
http://example.com/bsd_whitepaper.pdf
http://example.com/important_security_topic.pdf

输出是一系列的 URL,每行一个。而这与 Bash 处理数据的方式完美契合,因此我不再把 URL 打印到终端,而是把它们通过管道传给 curl

$ grep --only-matching http\:\/\/.*pdf \
example.html | curl --remote-name

这条命令可以下载每一个文件,然后以各自的远程文件名命名保存在我的硬盘上。

这个例子中我的搜索模式可能很晦涩。那是因为它用的是正则表达式,一种在大量文本中进行模糊搜索时非常有用的”通配符“语言。

正则表达式

没有人会觉得 正则表达式 regular expression (简称 “regex”)很简单。然而,我发现它的名声往往比它应得的要差。诚然,很多人在使用正则表达式时“过于炫耀聪明”,直到它变得难以阅读,大而全,以至于复杂得换行才好理解,但是你不必过度使用正则。这里简单介绍一下我使用正则表达式的方式。

首先,创建一个名为 example.txt 的文件,输入以下内容:

Albania
Algeria
Canada
0
1
3
11

最基础的元素是不起眼的 . 字符。它表示一个字符。

$ grep Can.da example.txt
Canada

模式 Can.da 能成功匹配到 Canada 是因为 . 字符表示任意一个字符。

可以使用下面这些符号来使 . 通配符表示多个字符:

  • ? 匹配前面的模式零次或一次
  • * 匹配前面的模式零次或多次
  • + 匹配前面的模式一次或多次
  • {4} 匹配前面的模式 4 次(或是你在括号中写的其他次数)

了解了这些知识后,你可以用你认为有意思的所有模式来在 example.txt 中做练习。可能有些会成功,有些不会成功。重要的是你要去分析结果,这样你才会知道原因。

例如,下面的命令匹配不到任何国家:

$ grep A.a example.txt

因为 . 字符只能匹配一个字符,除非你增加匹配次数。使用 * 字符,告诉 grep 匹配一个字符零次或者必要的任意多次直到单词末尾。因为你知道你要处理的内容,因此在本例中零次是没有必要的。在这个列表中一定没有单个字母的国家。因此,你可以用 + 来匹配一个字符至少一次且任意多次直到单词末尾:

$ grep A.+a example.txt
Albania
Algeria

你可以使用方括号来提供一系列的字母:

$ grep [A,C].+a example.txt
Albania
Algeria
Canada

也可以用来匹配数字。结果可能会震惊你:

$ grep [1-9] example.txt
1
3
11

看到 11 出现在搜索数字 1 到 9 的结果中,你惊讶吗?

如果把 13 加到搜索列表中,会出现什么结果呢?

这些数字之所以会被匹配到,是因为它们包含 1,而 1 在要匹配的数字中。

你可以发现,正则表达式有时会令人费解,但是通过体验和练习,你可以熟练掌握它,用它来提高你搜索数据的能力。

下载备忘录

grep 命令还有很多文章中没有列出的选项。有用来更好地展示匹配结果、列出文件、列出匹配到的行号、通过打印匹配到的行周围的内容来显示上下文的选项,等等。如果你在学习 grep,或者你经常使用它并且通过查阅它的帮助页面来查看选项,那么你可以下载我们的备忘录。这个备忘录使用短选项(例如,使用 -v,而不是 --invert-matching)来帮助你更好地熟悉 grep。它还有一部分正则表达式可以帮你记住用途最广的正则表达式代码。 现在就下载 grep 备忘录!


via: https://opensource.com/article/21/3/grep-cheat-sheet

作者:Seth Kenlon 选题:lujun9972 译者:lxbwolf 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

大约一周前,我在编辑一个程序时想要更改一些变量名。我之前认为这将是一个简单的正则表达式查找/替换。只是这没有我想象的那么简单。

变量名为 a10v10x10,我想分别将它们改为 a30v30x30。我想到使用 BBEdit 的查找窗口并输入:

 title=

我不能简单地 30 替换为 10,因为代码中有一些与变量无关的数字 10。我认为我很聪明,所以我不想写三个非正则表达式替换,a10v10x10,每个一个。但是我却没有注意到替换模式中的蓝色。如果我这样做了,我会看到 BBEdit 将我的替换模式解释为“匹配组 13,后面跟着 0,而不是”匹配组 1,后面跟着 30,后者是我想要的。由于匹配组 13 是空白的,因此所有变量名都会被替换为 0

你看,BBEdit 可以在搜索模式中匹配多达 99 个分组,严格来说,我们应该在替换模式中引用它们时使用两位数字。但在大多数情况下,我们可以使用 \1\9 而不是 \01\09,因为这没有歧义。换句话说,如果我尝试将 a10v10x10 更改为 azvzxz,那么使用 \1z的替换模式就可以了。因为后面的 z 意味着不会误解释该模式中 \1

因此,在撤消替换后,我将模式更改为这样:

 title=

它可以正常工作。

还有另一个选择:命名组。这是使用 var 作为模式名称:

 title=

我从来都没有使用过命名组,无论正则表达式是在文本编辑器还是在脚本中。我的总体感觉是,如果模式复杂到我必须使用变量来跟踪所有组,那么我应该停下来并将问题分解为更小的部分。

顺便说一下,你可能已经听说 BBEdit 正在庆祝它诞生25周年。当一个有良好文档的应用有如此长的历史时,手册的积累能让人愉快地回到过去的日子。当我在 BBEdit 手册中查找命名组的表示法时,我遇到了这个说明:

 title=

BBEdit 目前的版本是 12.5。第一次出现于 2001 年的 V6.5。但手册希望确保长期客户(我记得是在 V4 的时候第一次购买)不会因行为变化而感到困惑,即使这些变化几乎发生在二十年前。


via: https://leancrew.com/all-this/2019/02/regex-groups-and-numerals/

作者:Dr.Drang 选题:lujun9972 译者:geekpi 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

正则表达式 Regular expressions (简写为 regex 或者 regexp)基本上是定义一种搜索模式的字符串,可以被用来执行“搜索”或者“搜索并替换”操作,也可以被用来验证像密码策略等条件。

正则表达式是一个我们可利用的非常强大的工具,并且使用正则表达式的优点是它能在几乎所有计算机语言中被使用。所以如果你使用 Bash 脚本或者创建一个 python 程序时,我们可以使用正则表达式,或者也可以写一个单行搜索查询。

在这篇教程中,我们将会学习一些正则表达式的基本概念,并且学习如何在 Bash 中通过 grep 使用它们,但是如果你希望在其他语言如 python 或者 C 中使用它们,你只能使用正则表达式部分。那么让我们通过正则表达式的一个例子开始吧,

正则表达式看起来像 /t[aeiou]l/ 这个样子。

但这是什么意思呢?它意味着所提到的正则表达式将寻找一个词,它以 t 开始,在中间包含字母 a e i o u 中任意一个,并且字母 l 最为最后一个字符。它可以是 teltal 或者 til,可以匹配一个单独的词或者其它单词像 tiltbrutal 或者 telephone 的一部分。

grep 使用正则表达式的语法是 $ grep "regex_search_term" file_location

如果不理解,不要担心,这只是一个例子,来展示可以利用正则表达式获取什么,相信我,这是最简单的例子。我们可以从正则表达式中获取更多。现在我们将从正则表达式基础的开始。

基础的正则表示式

现在我们开始学习一些被称为 元字符 MetaCharacters 的特殊字符。它们可以帮助我们创建更复杂的正则表达式搜索项。下面提到的是基本元字符的列表,

  • . 点将匹配任意字符
  • [ ] 将匹配一个字符范围
  • [^ ] 将匹配除了括号中提到的那个之外的所有字符
  • * 将匹配零个或多个前面的项
  • + 将匹配一个或多个前面的项
  • ? 将匹配零个或一个前面的项
  • {n} 将匹配 n 次前面的项
  • {n,} 将匹配 n 次或更多前面的项
  • {n,m} 将匹配在 n 和 m 次之间的项
  • {,m} 将匹配少于或等于 m 次的项
  • \ 是一个转义字符,当我们需要在我们的搜索中包含一个元字符时使用

现在我们将用例子讨论所有这些元字符。

. (点)

它用于匹配出现在我们搜索项中的任意字符。举个例子,我们可以使用点如:

$ grep "d.g" file1

这个正则表达式意味着我们在名为 ‘file1’ 的文件中查找的词以 d 开始,以 g结尾,中间可以有 1 个字符的字符串。同样,我们可以使用任意数量的点作为我们的搜索模式,如 T......h,这个查询项将查找一个词,以 T 开始,以 h 结尾,并且中间可以有任意 6 个字符。

[ ]

方括号用于定义字符范围。例如,我们需要搜索一些特别的单词而不是匹配任何字符,

$ grep "N[oen]n" file2

这里,我们正寻找一个单词,以 N开头,以 n 结尾,并且中间只能有 oe 或者 n 中的一个。 在方括号中我们可以提到单个到任意数量的字符。

我们在方括号中也可以定义像 a-e或者 1-18 作为匹配字符的列表。

[^ ]

这就像正则表达式的 not 操作。当使用 [^ ] 时,它意味着我们的搜索将包括除了方括号内提到的所有字符。例如,

$ grep "St[^1-9]d" file3

这意味着我们可以拥有所有这样的单词,它们以 St 开始,以字母 d 结尾,并且不得包含从 19 的任何数字。

到现在为止,我们只使用了仅需要在中间查找单个字符的正则表达式的例子,但是如果我们需要更多字符该怎么办呢。假设我们需要找到以一个字符开头和结尾的所有单词,并且在中间可以有任意数量的字符。这就是我们使用乘数元字符如 + *? 的地方。

{n}{n,m}{n,} 或者 {,m} 也是可以在我们的正则表达式项中使用的其他乘数元字符。

* (星号)

以下示例匹配字母 k 的任意出现次数,包括一次没有:

$ grep "lak*" file4

它意味着我们可以匹配到 lakela 或者 lakkkk

+

以下模式要求字符串中的字母 k 至少被匹配到一次:

$ grep "lak+" file5

这里 k 在我们的搜索中至少需要发生一次,所以我们的结果可以为 lake 或者 lakkkk,但不能是 la

?

在以下模式匹配中

$ grep "ba?b" file6

匹配字符串 bbbab,使用 ? 乘数,我们可以有一个或零个字符的出现。

非常重要的提示

当使用乘数时这是非常重要的,假设我们有一个正则表达式

$ grep "S.*l" file7

我们得到的结果是 smallsilly,并且我们也得到了 Shane is a little to play ball。但是为什么我们得到了 Shane is a little to play ball?我们只是在搜索中寻找单词,为什么我们得到了整个句子作为我们的输出。

这是因为它满足我们的搜索标准,它以字母 s 开头,中间有任意数量的字符并以字母 l 结尾。那么,我们可以做些什么来纠正我们的正则表达式来只是得到单词而不是整个句子作为我们的输出。

我们在正则表达式中需要增加 ? 元字符,

$ grep "S.*?l" file7

这将会纠正我们正则表达式的行为。

\

\ 是当我们需要包含一个元字符或者对正则表达式有特殊含义的字符的时候来使用。例如,我们需要找到所有以点结尾的单词,所以我们可以使用:

$ grep "S.*\\." file8

这将会查找和匹配所有以一个点字符结尾的词。

通过这篇基本正则表达式教程,我们现在有一些关于正则表达式如何工作的基本概念。在我们的下一篇教程中,我们将学习一些高级的正则表达式的概念。同时尽可能多地练习,创建正则表达式并试着尽可能多的在你的工作中加入它们。如果有任何疑问或问题,您可以在下面的评论区留言。


via: http://linuxtechlab.com/bash-scripting-learn-use-regex-basics/

作者:SHUSAIN 译者:kimii 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

一个人希望掌握所有的正则表达式,却耗费了大量的时间,不知室外世事变迁,早已换了一个时代。

这个漫画讽刺了一些初学者,希望掌握一些比较复杂的东西的所有内容后再去做事,却不知道,这些东西过于复杂,你花费的时间可能很快就一文不值,因为它们可能已经过时了。

开发并不要求你掌握所有的内容,更多的时候,你只要能够掌握一些常用的,在真正需要用到的时候,再去查询一些比较详细的、不常用的内容即可。毕竟,没有人要求你马上做完所有事情。


via: https://turnoff.us/geek/mastering-regexp/

作者:Daniel Stori 译者&点评:Bestony 校对&合成:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

在 Linux 、类 Unix 系统中我该如何使用 Grep 命令的正则表达式呢?

Linux 附带有 GNU grep 命令工具,它支持 扩展正则表达式 extended regular expressions ,而且 GNU grep 在所有的 Linux 系统中都是默认有的。Grep 命令被用于搜索定位存储在您服务器或工作站上的任何信息。

正则表达式

正则表达式就是用于匹配每行输入的一种模式,模式是指一串字符序列。下面是范例:

^w1
w1|w2
[^ ]

grep 正则表达式示例

在 /etc/passswd 目录中搜索 'vivek'

grep vivek /etc/passwd

输出例子:

vivek:x:1000:1000:Vivek Gite,,,:/home/vivek:/bin/bash
vivekgite:x:1001:1001::/home/vivekgite:/bin/sh
gitevivek:x:1002:1002::/home/gitevivek:/bin/sh

搜索大小写任意的 vivek(即不区分大小写的搜索)

grep -i -w vivek /etc/passwd

搜索大小写任意的 vivek 或 raj

grep -E -i -w 'vivek|raj' /etc/passwd

上面最后的例子显示的,就是一个扩展的正则表达式的模式。

锚点

你可以分别使用 ^ 和 $ 符号来正则匹配输入行的开始或结尾。下面的例子搜索显示仅仅以 vivek 开始的输入行:

grep ^vivek /etc/passwd

输出例子:

vivek:x:1000:1000:Vivek Gite,,,:/home/vivek:/bin/bash
vivekgite:x:1001:1001::/home/vivekgite:/bin/sh

你可以仅仅只搜索出以单词 vivek 开始的行,即不显示 vivekgit、vivekg 等(LCTT 译注:即该单词后面是空格、符号等英文的单词分隔符。)

grep -w ^vivek /etc/passwd

找出以单词 word 结尾的行:

grep 'foo$' 文件名

匹配仅仅只包含 foo 的行:

grep '^foo$' 文件名

如下所示的例子可以搜索空行:

grep '^$' 文件名

字符类

匹配 Vivek 或 vivek:

grep '[vV]ivek' 文件名

或者

grep '[vV][iI][Vv][Ee][kK]' 文件名

也可以匹配数字 (即匹配 vivek1 或 Vivek2 等等):

grep -w '[vV]ivek[0-9]' 文件名

可以匹配两个数字字符(即 foo11、foo12 等):

grep 'foo[0-9][0-9]' 文件名

不仅仅局限于数字,也能匹配至少一个字母的:

grep '[A-Za-z]' 文件名

显示含有 "w" 或 "n" 字符的所有行:

grep [wn] 文件名

放在括号内的表达式,即包在 "[:" 和 ":]" 之间的字符类的名字,它表示的是属于此类的所有字符列表。标准的字符类名称如下:

  • [:alnum:] - 字母数字字符
  • [:alpha:] - 字母字符
  • [:blank:] - 空字符: 空格键符 和 制表符
  • [:digit:] - 数字: '0 1 2 3 4 5 6 7 8 9'
  • [:lower:] - 小写字母: 'a b c d e f g h i j k l m n o p q r s t u v w x y z'
  • [:space:] - 空格字符: 制表符、换行符、垂直制表符、换页符、回车符和空格键符
  • [:upper:] - 大写字母: 'A B C D E F G H I J K L M N O P Q R S T U V W X Y Z'

在这个例子所示的是匹配所有大写字母:

grep '[:upper:]' 文件名

通配符

你可以使用 "." 来匹配单个字符。例子中匹配以 "b" 开头以 "t" 结尾的3个字符的单词:

grep '\<b.t\>' 文件名

在这儿,

  • \< 匹配单词前面的空字符串
  • \> 匹配单词后面的空字符串

打印出只有两个字符的所有行:

grep '^..$' 文件名

显示以一个点和一个数字开头的行:

grep '^\.[0-9]' 文件名

点字符转义

下面要匹配到 IP 地址为 192.168.1.254 的正则式是不正确的:(LCTT 译注:可以匹配到该 IP 地址,但是也有可能匹配到间隔符号不是点的类似格式)

grep '192.168.1.254' /etc/hosts

三个点字符都需要转义:

grep '192\.168\.1\.254' /etc/hosts

下面的例子只能匹配出 IP 地址:(LCTT 译注:实际上由于 IP 地址中数字的取值范围,该正则表达式并不精确)

egrep '[[:digit:]]{1,3}\.[[:digit:]]{1,3}\.[[:digit:]]{1,3}\.[[:digit:]]{1,3}' 文件名

怎么样搜索以“-” 符号开头的匹配模式?

要使用 -e 选项来搜索匹配 '--test--' 字符串,如果不使用 -e 选项,grep 命令会试图把 '--test--' 当作自己的选项参数来解析:

grep -e '--test--' 文件名

怎么使用 grep 的“或”匹配?

使用如下的语法:

grep -E 'word1|word2' 文件名
或
egrep 'word1|word2' 文件名

或者是

grep 'word1\|word2' 文件名

怎么使用 grep 的“和”匹配?

使用下面的语法来显示既包含 'word1' 又包含 'word2' 的所有行

grep 'word1' 文件名 | grep 'word2'

怎么样使用序列检测?

使用如下的语法,您可以检测一个字符在序列中重复出现次数:

{N}
{N,}
{min,max}

要匹配字符 “v" 出现两次:

egrep "v{2}" 文件名

下面的命令能匹配到 "col" 和 "cool" :

egrep 'co{1,2}l' 文件名

下面的命令将会匹配出至少有三个 'c' 字符的所有行。

egrep 'c{3,}' 文件名

下面的例子会匹配 91-1234567890(即二个数字-十个数字) 这种格式的手机号。

grep "[[:digit:]]\{2\}[ -]\?[[:digit:]]\{10\}" 文件名

怎么样使 grep 命令高亮显示?

使用如下的语法:

grep --color 正则表达式 文件名

怎么样仅仅只显示匹配出的字符,而不是匹配出的行?

使用如下语法:

grep -o 正则表达式 文件名

正则表达式限定符

限定符描述
.匹配任意的一个字符。
?匹配前面的子表达式,最多一次。
*匹配前面的子表达式零次或多次。
+匹配前面的子表达式一次或多次。
{N}匹配前面的子表达式 N 次。
{N,}匹配前面的子表达式 N 次到多次。
{N,M}匹配前面的子表达式 N 到 M 次,至少 N 次至多 M 次。
-只要不是在序列开始、结尾或者序列的结束点上,表示序列范围。
^匹配一行开始的空字符串;也表示字符不在要匹配的列表中。
$匹配一行末尾的空字符串。
\b匹配一个单词前后的空字符串。
\B匹配一个单词中间的空字符串。
\<匹配单词前面的空字符串。
\>匹配单词后面的空字符串。

grep 和 egrep

egrep 等同于 grep -E 。它会以扩展的正则表达式的模式来解释模式。下面来自 grep 的帮助页:

基本的正则表达式元字符 ?、+、 {、 |、 ( 和 ) 已经失去了它们原来的意义,要使用的话用反斜线的版本 ?、+、{、|、( 和 ) 来代替。 传统的 egrep 并不支持 { 元字符,一些 egrep 的实现是以 { 替代的,所以一个可移植的脚本应该避免在 grep -E 使用 { 符号,要匹配字面的 { 应该使用 [}]。

GNU grep -E 试图支持传统的用法,如果 { 出在在无效的间隔规范字符串这前,它就会假定 { 不是特殊字符。

例如,grep -E '{1' 命令搜索包含 {1 两个字符的串,而不会报出正则表达式语法错误。

POSIX.2 标准允许这种操作的扩展,但在可移植脚本文件里应该避免这样使用。

参考:

  • grep 和 regex 帮助手册页(7)
  • grep 的 info 页

via: http://www.cyberciti.biz/faq/grep-regular-expressions/

作者:Vivek Gite 译者:runningwater 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

用正则表达式验证邮件地址似乎是一件简单的事情,但是如果要完美的验证一个合规的邮件地址,其实也许很复杂。

邮件地址的规范来自于 RFC 5322 。有一个网站 emailregex.com 专门列出各种编程语言下的验证邮件地址的正则表达式,其中很多正则表达式都是我听说过而从未见过的复杂——我想说,做这个网站的程序员是被邮件验证这件事伤害了多深啊!

其实,在产品环境中,一般来说并不需要这么复杂的正则表达式来做到99.99%正确。一般来说,从执行效率和测试覆盖率来说,只需要一个简单的版本即可:

/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i

那么下面我们来看看这些更严谨、更复杂的正则表达式吧:

验证邮件地址的通用正则表达式(符合 RFC 5322 标准)

(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])

由于各种语言对正则表达式的支持不同、语法差异和覆盖率不同,所以,不同语言里面的正则表达式也不同:

Python

这个是个简单的版本:

r"(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)"

Javascript

这个有点复杂了:

/^[-a-z0-9~!$%^&*_=+}{\'?]+(\.[-a-z0-9~!$%^&*_=+}{\'?]+)*@([a-z0-9_][-a-z0-9_]*(\.[-a-z0-9_]+)*\.(aero|arpa|biz|com|coop|edu|gov|info|int|mil|museum|name|net|org|pro|travel|mobi|[a-z][a-z])|([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}))(:[0-9]{1,5})?$/i

Swift

[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,6}

PHP

PHP 的这个版本就更复杂了,覆盖率就更大一些:

/^(?!(?:(?:\x22?\x5C[\x00-\x7E]\x22?)|(?:\x22?[^\x5C\x22]\x22?)){255,})(?!(?:(?:\x22?\x5C[\x00-\x7E]\x22?)|(?:\x22?[^\x5C\x22]\x22?)){65,}@)(?:(?:[\x21\x23-\x27\x2A\x2B\x2D\x2F-\x39\x3D\x3F\x5E-\x7E]+)|(?:\x22(?:[\x01-\x08\x0B\x0C\x0E-\x1F\x21\x23-\x5B\x5D-\x7F]|(?:\x5C[\x00-\x7F]))*\x22))(?:\.(?:(?:[\x21\x23-\x27\x2A\x2B\x2D\x2F-\x39\x3D\x3F\x5E-\x7E]+)|(?:\x22(?:[\x01-\x08\x0B\x0C\x0E-\x1F\x21\x23-\x5B\x5D-\x7F]|(?:\x5C[\x00-\x7F]))*\x22)))*@(?:(?:(?!.*[^.]{64,})(?:(?:(?:xn--)?[a-z0-9]+(?:-[a-z0-9]+)*\.){1,126}){1,}(?:(?:[a-z][a-z0-9]*)|(?:(?:xn--)[a-z0-9]+))(?:-[a-z0-9]+)*)|(?:\[(?:(?:IPv6:(?:(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){7})|(?:(?!(?:.*[a-f0-9][:\]]){7,})(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,5})?::(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,5})?)))|(?:(?:IPv6:(?:(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){5}:)|(?:(?!(?:.*[a-f0-9]:){5,})(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,3})?::(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,3}:)?)))?(?:(?:25[0-5])|(?:2[0-4][0-9])|(?:1[0-9]{2})|(?:[1-9]?[0-9]))(?:\.(?:(?:25[0-5])|(?:2[0-4][0-9])|(?:1[0-9]{2})|(?:[1-9]?[0-9]))){3}))\]))$/iD

Perl / Ruby

对与 PHP 的版本,Perl 和 Ruby 表示不服,可以更严谨:

(?:(?:
)?[ \t])*(?:(?:(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:
)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|\\.|(?:(?:
)?[ \t]))*"(?:(?:
)?[ \t])*)(?:\.(?:(?:
)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:
)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|\\.|(?:(?:
)?[\t]))*"(?:(?:
)?[ \t])*))*@(?:(?:
)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:
)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:
)?[ \t])*)(?:\.(?:(?:
)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:
)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:
)?[ \t])*))*|(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:
)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|\\.|(?:(?:
)?[ \t]))*"(?:(?:
)?[ \t])*)*\<(?:(?:
)?[ \t])*(?:@(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:
)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:
)?[\t])*)(?:\.(?:(?:
)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:
)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:
)?[ \t])*))*(?:,@(?:(?:
)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:
)?[\t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:
)?[ \t])*)(?:\.(?:(?:
)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:
)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:
)?[ \t])*))*)*:(?:(?:
)?[ \t])*)?(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:
)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|\\.|(?:(?:
)?[ \t]))*"(?:(?:
)?[ \t])*)(?:\.(?:(?:
)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:
)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|\\.|(?:(?:
)?[ \t]))*"(?:(?:
)?[ \t])*))*@(?:(?:
)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:
)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:
)?[ \t])*)(?:\.(?:(?:
)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:
)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:
)?[ \t])*))*\>(?:(?:
)?[ \t])*)|(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:
)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|\\.|(?:(?:
)?[ \t]))*"(?:(?:
)?[ \t])*)*:(?:(?:
)?[ \t])*(?:(?:(?:[^()<>@,;:\\".\[\]\000-\031]+(?:(?:(?:
)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|\\.|(?:(?:
)?[ \t]))*"(?:(?:
)?[ \t])*)(?:\.(?:(?:
)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:
)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|\\.|(?:(?:
)?[ \t]))*"(?:(?:
)?[ \t])*))*@(?:(?:
)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:
)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:
)?[ \t])*)(?:\.(?:(?:
)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:
)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:
)?[ \t])*))*|(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:
)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|\\.|(?:(?:
)?[ \t]))*"(?:(?:
)?[ \t])*)*\<(?:(?:
)?[ \t])*(?:@(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:
)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:
)?[ \t])*)(?:\.(?:(?:
)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:
)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:
)?[ \t])*))*(?:,@(?:(?:
)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:
)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:
)?[ \t])*)(?:\.(?:(?:
)?[ \t])*(?:[^()<>@,;:\\".\[\]\000-\031]+(?:(?:(?:
)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:
)?[ \t])*))*)*:(?:(?:
)?[ \t])*)?(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:
)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|\\.|(?:(?:
)?[ \t]))*"(?:(?:
)?[ \t])*)(?:\.(?:(?:
)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:
)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|\\.|(?:(?:
)?[ \t]))*"(?:(?:
)?[ \t])*))*@(?:(?:
)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:
)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:
)?[ \t])*)(?:\.(?:(?:
)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:
)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:
)?[ \t])*))*\>(?:(?:
)?[ \t])*)(?:,\s*(?:(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:
)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|\\.|(?:(?:
)?[ \t]))*"(?:(?:
)?[ \t])*)(?:\.(?:(?:
)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:
)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|\\.|(?:(?:
)?[ \t]))*"(?:(?:
)?[ \t])*))*@(?:(?:
)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:
)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:
)?[ \t])*)(?:\.(?:(?:
)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:
)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:
)?[ \t])*))*|(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:
)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|\\.|(?:(?:
)?[ \t]))*"(?:(?:
)?[ \t])*)*\<(?:(?:
)?[ \t])*(?:@(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:
)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:
)?[ \t])*)(?:\.(?:(?:
)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:
)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:
)?[ \t])*))*(?:,@(?:(?:
)?[\t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:
)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:
)?[ \t])*)(?:\.(?:(?:
)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:
)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:
)?[ \t])*))*)*:(?:(?:
)?[ \t])*)?(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:
)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|\\.|(?:(?:
)?[ \t]))*"(?:(?:
)?[ \t])*)(?:\.(?:(?:
)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:
)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|\\.|(?:(?:
)?[ \t]))*"(?:(?:
)?[ \t])*))*@(?:(?:
)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:
)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:
)?[ \t])*)(?:\.(?:(?:
)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:
)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:
)?[ \t])*))*\>(?:(?:
)?[ \t])*))*)?;\s

Perl 5.10 及以后版本

上面的版本,嗯,我可以说是天书吗?反正我是没有解读的想法了。当然,新版本的 Perl 语言还有一个更易读的版本(你是说真的么?)

/(?(DEFINE)
(?<address> (?&mailbox) | (?&group))
(?<mailbox> (?&name_addr) | (?&addr_spec))
(?<name_addr> (?&display_name)? (?&angle_addr))
(?<angle_addr> (?&CFWS)? < (?&addr_spec) > (?&CFWS)?)
(?<group> (?&display_name) : (?:(?&mailbox_list) | (?&CFWS))? ;
(?&CFWS)?)
(?<display_name> (?&phrase))
(?<mailbox_list> (?&mailbox) (?: , (?&mailbox))*)

(?<addr_spec> (?&local_part) \@ (?&domain))
(?<local_part> (?&dot_atom) | (?&quoted_string))
(?<domain> (?&dot_atom) | (?&domain_literal))
(?<domain_literal> (?&CFWS)? \[ (?: (?&FWS)? (?&dcontent))* (?&FWS)?
\] (?&CFWS)?)
(?<dcontent> (?&dtext) | (?&quoted_pair))
(?<dtext> (?&NO_WS_CTL) | [\x21-\x5a\x5e-\x7e])

(?<atext> (?&ALPHA) | (?&DIGIT) | [!#\$%&'*+-/=?^_`{|}~])
(?<atom> (?&CFWS)? (?&atext)+ (?&CFWS)?)
(?<dot_atom> (?&CFWS)? (?&dot_atom_text) (?&CFWS)?)
(?<dot_atom_text> (?&atext)+ (?: \. (?&atext)+)*)

(?<text> [\x01-\x09\x0b\x0c\x0e-\x7f])
(?<quoted_pair> \\ (?&text))

(?<qtext> (?&NO_WS_CTL) | [\x21\x23-\x5b\x5d-\x7e])
(?<qcontent> (?&qtext) | (?&quoted_pair))
(?<quoted_string> (?&CFWS)? (?&DQUOTE) (?:(?&FWS)? (?&qcontent))*
(?&FWS)? (?&DQUOTE) (?&CFWS)?)

(?<word> (?&atom) | (?&quoted_string))
(?<phrase> (?&word)+)

# Folding white space
(?<FWS> (?: (?&WSP)* (?&CRLF))? (?&WSP)+)
(?<ctext> (?&NO_WS_CTL) | [\x21-\x27\x2a-\x5b\x5d-\x7e])
(?<ccontent> (?&ctext) | (?&quoted_pair) | (?&comment))
(?<comment> \( (?: (?&FWS)? (?&ccontent))* (?&FWS)? \) )
(?<CFWS> (?: (?&FWS)? (?&comment))*
(?: (?:(?&FWS)? (?&comment)) | (?&FWS)))

# No whitespace control
(?<NO_WS_CTL> [\x01-\x08\x0b\x0c\x0e-\x1f\x7f])

(?<ALPHA> [A-Za-z])
(?<DIGIT> [0-9])
(?<CRLF> \x0d \x0a)
(?<DQUOTE> ")
(?<WSP> [\x20\x09])
)

(?&address)/x

Ruby (简单版)

Ruby 表示,其实人家还有个简单版本:

/\A([\w+\-].?)+@[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z/i

.NET

这样的版本谁没有啊——.NET 说:

^\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$

grep 命令

用 grep 命令在文件中查找邮件地址,我想你不会写个若干行的正则表达式吧,意思一下就行了:

$ grep -E -o "\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,6}\b" filename.txt

SQL Server

在 SQL Server 中也是可以用正则表达式的,不过这个代码片段应该是来自某个产品环境中的,所以,还体贴的照顾了那些把邮件地址写错的人:

 select email 
 from table_name where 
 patindex ('%[ &'',":;!+=\/()<>]%', email) > 0 -- Invalid characters
 or patindex ('[@.-_]%', email) > 0 -- Valid but cannot be starting character
 or patindex ('%[@.-_]', email) > 0 -- Valid but cannot be ending character
 or email not like '%@%.%' -- Must contain at least one @ and one .
 or email like '%..%' -- Cannot have two periods in a row
 or email like '%@%@%' -- Cannot have two @ anywhere
 or email like '%.@%' or email like '%@.%' -- Cannot have @ and . next to each other
 or email like '%.cm' or email like '%.co' -- Camaroon or Colombia? Typos. 
 or email like '%.or' or email like '%.ne' -- Missing last letter

Oracle PL/SQL

这个是不是有点偷懒?尤其是在那些“复杂”的正则表达式之后:

SELECT email 
FROM table_name
WHERE REGEXP_LIKE (email, '[A-Z0-9._%-]+@[A-Z0-9._%-]+\.[A-Z]{2,4}');

MySQL

好吧,看来最后也一样懒:

SELECT * FROM `users` WHERE `email` NOT REGEXP '^[A-Z0-9._%-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$';

那么,你有没有关于验证邮件地址的正则表达式分享给大家?