本文以 Linux 中的 GNU grep 为例,区分基本正则(BRE)与扩展正则(ERE)。其他工具的语法可能不同,正则不能到哪儿都一把梭。

正则表达式

需要注意

由普通字符(如大小写字母和数字)和元字符(存在特殊含义)组成。同一个符号在不同位置或正则类型中,含义可能不同。

默认区分大小写;需要忽略大小写时,可以使用 grep -i。元字符的语法含义与普通字母的大小写匹配是两回事。

基本正则表达式

元字符 作用
* 前一个模式项重复0次或任意多次
如 z* 可匹配空字符串,所以 grep 'z*' 连不含 z 的行也会选中;匹配一个或多个 z 可写成 zz*
. 匹配除了换行符外任意一个字符
^ 匹配行首
如:^hello会匹配以hello开头的行
$ 匹配行尾
如:hello$会匹配以hello结尾的行
[] 匹配中括号中指定的任意一个字符,只匹配一个字符
如: [aoeiu]匹配任意一个元音字母
[0-9]匹配任意一位数字
[a-z][0-9]匹配一个小写字母和一位数字构成的两字符片段
[^] 匹配除中括号的字符以外的任意一个字符
如: [^0-9]匹配任意一位非数字字符
[^a-z]表示任意一位非小写字母
\ 转义符
用于取消特殊符号的含义
\{n\} 表示其前面的模式项恰好出现n次
如: [0-9]\{4\}匹配4位数字
^1[3-9][0-9]\{9\}$ 检查常见的11位手机号格式(不验证号码是否真实存在)
\{n,\} 表示其前面的模式项出现不小于n次
例如: [0-9]\{2,\}表示两位及以上的数字
\{n,m\} 表示其前面的模式项至少出现n次,最多出现m次
如: [a-z]\{6,8\}匹配6到8位的小写字母

扩展正则表达式

扩展元字符 作用
+ 前一个模式项重复1次或任意多次
等价于{1,}
如go+gle会匹配gogle、google或gooogle,
当然如果o有更多个,也能匹配
? 前一个模式项重复0次或1次
等价于{0,1}
如colou?r可以匹配colour或color
( ) 把括号中的表达式作为一个分组,便于对整个分组应用重复等操作

如(dog)+会匹配dog、dogdog、dogdogdog等,
因为被( )包含的字符会当成一个整体
但`hello (world

💡提示:补充:| 表示“或”,例如 dog|cat 匹配 dog 或 cat。ERE 还支持 {n}、{n,}、{n,m},不需要像 BRE 那样给花括号加反斜杠。

POSIX字符类

形如 [:…:],必须放在方括号表达式中使用,例如 [[:digit:]],不能直接写成 [:digit:]。下面使用完整写法;“相当于”一列按 LC_ALL=C 的常见字符范围理解,其他语言环境可能不同。

POSIX 元字符 相当于 作用
[[:alnum:]] [0-9a-zA-Z] 匹配字母和数字字符
[[:digit:]] [0-9] 匹配数字字符
[[:alpha:]] [a-zA-Z] 匹配字母字符
[[:lower:]] [a-z] 匹配小写字母字符
[[:upper:]] [A-Z] 匹配大写字母字符
[[:cntrl:]] 匹配控制字符
[[:graph:]] 匹配可打印的非空白字符
[[:space:]] 空白字符 匹配空白字符
包括空格、制表符、换行符等
[[:blank:]] 匹配空格和制表符字符

运算符优先级

正则的“优先级”指模式如何组合。以扩展正则为例,从高到低可以这样理解:

运算 说明
()、[] 分组或字符集合先作为一个单元
*、+、?、{n,m} 重复紧邻的前一个模式项
连接 相邻的模式按顺序匹配,例如 ab
| 选择多个分支,优先级最低

例如 m|food 匹配 m 或 food;(m|f)ood 才匹配 mood 或 food。(?:...)、(?=...) 属于其他正则语法,不适用于本文的 BRE/ERE。

匹配规则

基本模式匹配

模式是正则表达式最基本的元素,它是一组描述字符串特征的字符集,用于匹配字符串。

举例

  • ^love 匹配的是以love开头的字符串
  • man$ 匹配的是以 man 结尾的字符串
  • ^bash$ 仅匹配bash这个字符串(精确匹配)
  • 匹配以制表符开头的行,可以在 Bash 中写 grep $'^\t' 文件名:这里由 Bash 将 \t 转换为真正的制表符,不是依赖 grep 解释 \t。
    • 普通 grep 逐行匹配,换行符不属于当前行的内容,不能照搬其他语言中的 \n 写法来跨行匹配。

字符集合匹配

在程序中,要判断输入的电话号码、邮件地址等是否有效,用上述规则匹配显然是无法满足的;因此需要使用相应的字符模式的方法来描述,可以用方括号表达式描述字符集合。

举例

  • [a-z] 匹配任意一个小写字母(此处按 LC_ALL=C 理解)
  • [A-Z] 匹配任意一个大写字母(此处按 LC_ALL=C 理解)
  • grep -v '^$' 文件名 排除空行;[^$] 表示匹配一个不是字面量 $ 的字符,不能当成排除空行的规则
  • grep -E '^[+-]?[0-9]+(\.[0-9]+)?$' 文件名 匹配可带正负号的整数或小数;此示例不包括 .5、5. 和科学计数法
  • POSIX字符类
  • 正则表达式的组合应用

规则提取

grep命令是一种强大的文本搜索工具,它能使用正则表达式搜索文本,在文件中全局查找 指定的正则表达式,并打印所有包含该表达式的行。

如何理解

就像你在Windows中用记事本打开*.txt文件,通过Ctrl + F 在文本中查找某个字符串一样

孪生兄弟

  • grep 支持基本正则表达式

  • grep -E 使用扩展正则表达式;旧命令名 egrep 已不推荐使用

  • grep -F 按固定字符串匹配,元字符也按字面量处理;旧命令名 fgrep 已不推荐使用,实际速度取决于模式和输入

使用说明

基本语法:grep [选项] [模式] 文件名

说明

  • 选项:用于控制grep的行为
  • 模式:正则表达式模式
  • 文件:要搜索的文件的列表

选项

选项 说明
-i 在搜索的时候忽略大小写
-n 显示结果所在行号
-c 统计匹配到的行数
注意:是匹配到的总行数,不是匹配到的次数
-o 只显示符合条件的字符串
即:每个符合条件的字符串单独显示一行
-v 输出不带匹配内容的行
反向查询(反向匹配)
-w 匹配整个单词
匹配片段的两端必须是行边界或非单词字符;GNU grep 的单词字符包括字母、数字和下划线
-A 行数 在输出的时候包含结果所在行之后的指定行数
(A=After,之后)
-B行数 在输出的时候包含结果所在行之前的指定行数
(B=Before,之前)
-C 行数 在输出的时候包含结果所在行之前和之后的指定行数
(C=Context,上下文)
-e 指定匹配模式;可重复使用,如 grep -e 'hello' -e 'shell' regex
-q 静默模式,不输出任何信息
使用 echo $? 查看是否匹配;0 表示匹配到,1表示没有匹配到
-E 使用扩展正则表达式
相当于使用egrep
–color=auto 表示对匹配到的文本着色显示
也可直接用--color选项

退出状态码

在grep操作完成后,其退出状态有特殊含义,如下所示。

  • 找到匹配的表达式 0

  • 没找到匹配的表达式 1

  • 发生错误(例如找不到指定文件)时,GNU grep 返回 2

💡提示:-q 在找到匹配时可能返回 0,即使同时遇到了某些读取错误;它适合判断“是否匹配”,不能据此保证所有输入都读取成功。

基本用法

这里创建一段特定文本(命名为regex),内容如下。

1
2
3
4
5
6
7
8
world hello
sayhello
hellosay
shell : root
class : shell
weclome to shell World
10,2023
saY shell

搜索包含shell的行

1
grep "shell" regex

image-20231025122521431

搜索仅行首包含shell的行

1
grep "^shell" regex

image-20231025122535166

搜索仅行尾包含shell的行

1
grep "shell$" regex

image-20231025122547146

搜索仅包含shell的行

1
grep "^shell$" regex

image-20231025122628051

搜索包含以 hello 开头的单词的行

1
grep "\<hello" regex

image-20231025122641592

搜索包含以 hello 结尾的单词的行

1
grep "hello\>" regex

image-20231025122655665

搜索包含完整单词 hello 的行

1
grep "\<hello\>" regex

image-20231025122706587

搜索包含字符 y 的行(忽略大小写)

1
grep -i "y" regex

image-20231025122912516

扩展说明

grep 默认使用基本正则表达式,grep -E 使用扩展正则表达式。下面的 \?、\+、\w、\W、\b、\<、\> 按 GNU grep 的语法理解,其中部分是 GNU 扩展,不能假设所有实现都支持。

正则最好用单引号包起来,减少 Shell 对 $ 等字符的展开;原来的简单截图示例保留双引号写法。

结合正则表达式

元字符 描述
[[:digit:]] 或 [0-9] 匹配任意单个数字
[[:lower:]] 或 [a-z] 匹配任意单个小写字母
[[:upper:]] 或 [A-Z] 匹配任意单个大写字母
[[:alpha:]] 或 [a-zA-Z] 匹配任意单个大写字母或小写字母
[[:alnum:]] 或 [0-9a-zA-Z] 匹配任意单个字母或数字

结合特定模式

元字符 描述
^ 锚定行首
$ 锚定行尾
. 匹配任意一个字符
* 让前一个模式项重复零次或多次
\? 匹配其前面的字符0次或者1次
\+ 匹配其前面的字符1次或者多次
\{m\} 匹配其前面的字符m次(\为转义符)
\{m,n\} 匹配其前面的字符至少m次,至多n次
[] 匹配一个指定范围内的字符
而[^]匹配指定范围外的任意单个字符 (排除)
\< 或 \b \< 锚定词首,\b 匹配单词边界
\<匹配对象\> 限定整个单词,不限定整行
\> 或 \b \> 锚定词尾,\b 匹配单词边界
若需精确匹配整行,使用 ^匹配对象$
\(\) 将多个字符当做一个整体进行处理
\1 模式从左侧起,第一个左括号及与之匹配的右括号之间模式匹配的内容
\2 模式从左侧起,第二个左括号及与之匹配的右括号之间模式匹配到的内容
\w 匹配单个单词字符,即字母、数字或下划线,等价于 [[:alnum:]_]
\W 匹配单个非单词字符,等价于 [^[:alnum:]_]
.* 匹配任意长度的任意字符

💡提示

  • 其中\1 、\2 是后向引用,引用前面的分组括号中的模式所匹配到的字符

  • 在某行文本的检查中,如果使用\(\)则分组括号中的模式匹配到的某内容可以被引用

小试一下

同样是 114514,查字面量与查正则,可不是一个路数:

1
2
3
4
printf '%s\n' '114514' 'homo114514' '1145141919' | grep -E '^114514$'
#只输出114514
printf '%s\n' 'homo.txt' 'homoXtxt' | grep -F 'homo.txt'
#只输出homo.txt,点号按普通字符处理

grep -c 统计的是行,grep -o 才会把同一行里的不同匹配分别输出。例如:

1
2
3
4
printf '%s\n' '114514 114514' | grep -c '114514'
#输出1
printf '%s\n' '114514 114514' | grep -o '114514'
#输出两行114514