Linux_正则表达式
正则表达式
Regular Expression
主要参考文档:
https://legacy.cplusplus.com/reference/regex/ECMAScript/
[[Linux_Shell编程#结合正则表达式的示例]]:case的示例中
1 | [][][]代表三个字符。 |
Quantifiers(*
、+
、?
、{n}
)
量词
代表通用的信息的部分。
*
:可能有(0到无数个)前导字符。*
只是指示符,本身不参与,指示前面的字符会重复0到无数次。+
:最少1个前导字符。?
:只有1个前导字符。{n}
:有n个前导字符。{n,}
:有大于等于n个的前导字符。{min,max}
:至少有min个,但不超过max个(左闭右开)
Special Pattern Characters([]
)
特殊模式字符
.
:匹配任意单个字符[...]
:匹配[]
中包含的任意字符。- 比如
[1234567890]
或[0-9]
都表示:字符0到9 [A-Z]
表示大写字母[A-Za-z]
表示大小写字母。- 分类字符,见《Character classes(字符类)》一节
- 比如
\s
:表示空格字符,空格、制表符都可以。(可替代[[:space:]]
)
更多特殊字符见手册。
https://legacy.cplusplus.com/reference/regex/ECMAScript/
.*
表示所有字符。
Character classes
字符类
[:alpha:]
:表示所有字母grep '[[:alpha:]]' file.txt
[:digit:]
:表示数字[:alnum:]
:表示字母+数字^[:alpha:]
:表示除了字母以外grep '[^[:alpha:]]' file.txt
Assertions(^
、$
)
断言
^
:出现在行首的指定字符串。- 注意,在
[]
内外,^
表达的意思是不一样的。- 在
[]
内,表示“非” - 在
[]
外,才表示“首” - 对于分类字符(比如
[:alpha:]
),需要放到两层[]
之内,分类字符的[]
之外,即:[^[:alpha:]]
。
- 在
- 注意,在
$
:出现在行尾的指定字符串。
Alternatives(或关系:|
)
找出带A或带B的行。
相当于求了两次grep,结果合在一起。
注意|
需要转义。
Groups
组
核心概念:分组的作用
- 将量词应用于字符序列: 这是最基本的作用。想象你要匹配多次出现的单词“hello”而不是单个字母“l”多次。你需要把“hello”当成一个整体来量化。
- 错误示例:
hel{2,}o
:这个匹配的是 ‘he’ + 至少2个’l’ + ‘o’, 可以匹配 “hello”, “hellllo”,但不能匹配 “hellohello” (两个hello连在一起)。 - 正确示例:
(hello){2,}
:这个匹配的是 "(hello)"这个整体 至少出现2次,可以匹配 “hellohello”, “hellohellohello” 等。
- 错误示例:
- 创建子匹配项(反向引用): 这是普通分组
(subpattern)
独特且强大的功能。它不仅仅是匹配,还会记住分组内匹配到的具体内容,并分配一个编号(按照左括号(
出现的顺序)。这些被记住的内容称为“子匹配项”或“捕获组”。
普通分组(分组且捕获):(subpattern)
- 将
subpattern
作为一个整体单元进行操作(特别是应用量词)。 - 捕获
subpattern
匹配到的实际文本内容。 - (重要)创建反向引用(在同一个正则表达式内部,用
\1
,\2
,\3
… 来指代第1、2、3…个分组捕获的内容)。 - 在正则匹配结果中,可以通过索引(如 C++ 的
smatch[1]
,smatch[2]
)获取每个子匹配项的值。 - 在替换操作中,可以通过类似
$1
,$2
,$3
… 的语法来引用这些子匹配项。
示例1:匹配重复的单词或连续字母
1 | 正则: (\w+)\s+\1 |
示例2:提取日期各部分(年、月、日)
1 | 正则: (\d{4})-(\d{2})-(\d{2}) |
被动/非捕获分组:(?:subpattern)
作用
- 将
subpattern
作为一个整体单元进行操作(应用量词)。 - 不会捕获匹配到的文本内容。
- 不会创建子匹配项。
- 不分配分组编号(因此不会影响其他普通分组的编号)。
- 无法在正则表达式内部用
\n
反向引用。 - 无法在匹配结果或替换操作中单独访问。
为什么需要它?
- 性能: 如果不关心分组内容,避免捕获可以提高效率(尤其是在大量重复匹配时)。
- 简化编号: 当你只想分组应用量词,但又不想这个分组计入编号体系、干扰你真正关心的捕获组(普通分组)时。它让后续普通分组的编号更清晰。
- 避免不必要的内存开销: 不用存储不关心的匹配文本。
示例:匹配文件扩展名(但只关心扩展名本身)
1 | 目标:从 "report.txt", "data.csv.zip", "image.png" 中提取扩展名 (txt, zip, png) |
示例2:应用量词但不捕获(纯粹为了结构)
1 | 匹配连续出现的 "hello" 或 "world" 两次 |
总结
grep
Get Regular Expression Print
*
1 | echo "AAAA" > aa.txt |
1 | grep B* aa.txt |
结果:
1 | AAAA |
如上结果,因为B*
代表前面的B可能会出现0
到n
次。所以,AAAA也匹配上了。
1 | grep BB* aa.txt |
结果:
1 | BB |
如上结果,这次没出现AAAA,因为我们限制了BB*
,即第一个字符必须是B后面可能出现0到n个B。可以用B+
替代上述语义:+
。
因此*
还是要慎用,能代表的范围太大了。
+
类似于*
,区别是1到n次。排除了0次的可能。
1 | grep 'B\+' aa.txt |
需要转义,并在单引号中使用。
结果:
1 | BB |
示例1
1 | echo "123.456" >> aa.txt |
怎么提取出.
前后全是数字的行?
1 | grep '[0-9]\+\.[0-9]\+' aa.txt |
以上命令表示:
\.
是转义,意思是中间有个.
.
前面有最少1个0到9
的字符.
后面有最少1个0到9
的字符
结果:
示例2 - 精确指示n个字符
需要用到{}
,注意在单引号中,{
和}
均需转义。
指示有4个A到Z
字符。
1 | grep '[A-Z]\{4\}' aa.txt |
结果:AAAA
指示有大于等于2个的A到Z
字符。在右括号前加,
1 | grep '[A-Z]\{3,\}' aa.txt |
结果:
1 | AAAA |
结合分类字符(如[:alpha:]
)
注意外面还需要加一层[]
想找到文件中带数字的行:
1 | grep '[[:digit:]]' aa.txt |
想找到文件中带字母的行:
想找到文件中带字母或有0到3字符的行:
想找到文件中有非字母字符的行:
想找到文件中有非字母、非数字字符的行:
?
只出现了1次前面的字符。
$
以什么字符结尾。
1 | grep '[C-Z]\+$' aa.txt |
表示:结尾最少有1个C到Z的字符。
如果aa.txt
的内容是:
1 | AAAA |
则执行grep '[C-Z]\+$' aa.txt
的结果:
既以…为开头,又以它为结尾?
1 | grep '^[C-Z]\+$' aa.txt |
我原先想的是,这个命令表示既以至少1个C到Z的字符
开头,又以至少1个C到Z的字符
结尾。(错误解释!)
让Deepseek分析后,不是这样解释的。而是:
正则表达式 ^[C-Z]\+$
要求:
- 整行必须完全由 C-Z 的大写字母组成(字母必须 ≥ C)
- A和B字母不被允许
- 无任何数字/符号/小写字母
因此,如果aa.txt
的内容是:
1 | AAAA |
则执行grep '^[C-Z]\+$' aa.txt
的结果:
表达式拆解与执行逻辑
-
^
锚点(行首)- 强制匹配必须从行首开始
-
[C-Z]
+\+
组合[C-Z]
只匹配 单个 C-Z 范围内的大写字母\+
表示前面的[C-Z]
至少出现一次(可以出现多次)- 关键效果:每一个位置都需匹配
[C-Z]
(不能是空格/数字/小写字母/其他字符)
-
$
锚点(行尾)- 强制匹配必须延伸到行尾结束
强制约束的逻辑链
当正则引擎执行 ^[C-Z]\+$
时,它的匹配规则如下:
- 从行首 (
^
) 开始检查第一个字符
→ 必须是C-Z
的大写字母 - 继续检查后续字符
→ 因为\+
要求连续多个匹配,所以第二个字符必须也是C-Z
的大写字母
→ 第三个、第四个…所有字符都必须满足 - 直到遇见行尾 (
$
)
→ 此时整行已被完全匹配
✨ 核心机制:
[C-Z]\+
作为连续的整体充当了 “填充内容” 的角色,它从行首一直延伸到行尾,不允许中间插入任何非 C-Z 字符。
这种严格约束主要用于验证格式纯净的字符串,典型场景包括:
- 检测不含空格的英文单词(如
"PYTHON"
) - 过滤无数字/符号的纯大写文本(如验证商品代码
"SKUXYZ"
) - 也可以有等效的、更简洁的形式:
grep -x '[C-Z]\+' aa.txt
(-x
表示整行匹配)
awk
Alfred Aho, Peter Weinberger, Brian Kernighan
grep是扫描每一整行的,按行为单位。而awk是按一行中的每个字段为单位查询的,类似于excel表格,可以切分各个字段。即可以按列操作。
结合正则表达式
在 awk命令中,
/tty/
中的/
(正斜杠) 是正则表达式(Regular Expression)的定界符。它表示中间的内容(tty)是一个需要匹配的模式。
1 | awk '/tty/{print $0}' ps.txt |
假如ps.txt
文件内容如上,我们想找到字段有tty
的字段。
则,在awk命令后面先用单引号' '
包裹,再在里面写斜杠/ /
包裹正则表达式。后半部分再用大括号{ }
包裹要进行的打印操作。
print可以自定义内容
可以用printf进行格式化输出
ps内容:
单独指定匹配每一行的第n列,其他列跳过
employees文件内容:
形式:
1 | awk '$2~/^[A-z][a-z]+/ {print $1}' employees |
即,在' '
内的前面加一个第几列$2
和波浪号~
。
第一句awk的意思是搜索每一行的所有列,查找以大写字母开头,后面至少有一个小写字母。匹配到后,打印该行的第1列。
第二句awk的意思是只搜索每一行的第2列,查找以大写字母开头,后面至少有一个小写字母。匹配到后,打印该行的第1列。
第二句awk的意思是只搜索每一行的第3列,查找以大写字母开头,后面至少有一个小写字母。匹配到后,打印该行的第1列。由于第三列全是数字,没有匹配到符合条件的行,所以打印空。
可以在{}
中对文件信息进行修改
以上语句的意思:找每一行的第1列中含Billy的,之后,把该行第1列修改为Gilly
。之后,打印匹配到的所有行。
在单引号中,内置了数字大小比较器
ps内容:
我们想要输出所有PID大于776的:
可以在awk后的' '
单引号内写$1>776
,它内置了把字符串转换为数字之后比较的操作,之后帮我们筛选出符合条件的。
甚至还可以在里面写一些简单的运算:('$1>776+1'
)
sed
Stream Editor
用于处理流。把字符串、文件按流的方式处理。流的特点是只能单向,不能后撤。