跳转至

第4章:正则表达式

一、元字符和集合

1. 正则表达式的概念

正则表达式(Regular Expressions)是一种用于描述字符串模式的形式化语言,其核心功能在于匹配、查找、替换文本中的特定结构

  • 定义:正则表达式是一套由普通字符与特殊符号(元字符)组成的规则系统,用于精确或模糊地描述一类字符串的共同特征。

  • 应用范围

  • 主要用于字符串的匹配操作(如判断某行是否包含特定格式)和替换操作(如批量修改日志中的IP地址)。
  • 在 Linux 系统中,广泛应用于以下工具:
    • 文本编辑器:vi
    • 内容查看工具:more
    • 文本过滤命令:grep
    • 编译器生成工具:yacclex
    • 流式文本处理器:sedawk
  • 在非 Linux 环境中,如 Visual StudioMicrosoft Word 等现代文本编辑器也支持正则表达式进行高级查找与替换。

  • 注意事项

  • 正则表达式 ≠ 文件名通配符(如 *.txt):
    • 正则表达式用于文本内容处理(如 grep 'a.c' file 匹配 "abc"、"a2c" 等);
    • 通配符用于文件路径匹配(如 ls *.c 列出 C 源文件),两者语法规则完全不同。
  • 不同软件对正则表达式的实现存在差异
    • 例如,BRE(基本正则表达式)、ERE(扩展正则表达式)、PCRE(Perl 兼容正则表达式)在语法细节(如分组、量词)上有所不同;
    • 使用时需注意当前工具支持的正则类型(如 grep 默认使用 BRE,egrep 使用 ERE)。

2. 正则表达式中的特殊字符(元字符)

在正则表达式中,某些字符具有特殊含义,称为元字符(Metacharacters)。PPT 明确指出有 6 个基本元字符

  • 元字符列表. * [ \ ^ $

这些字符在正则表达式中不表示其字面意义,而是代表某种匹配逻辑。其余所有字符(包括字母、数字、标点等)默认与其自身匹配。

  • 转义机制
  • 若希望匹配元字符的字面值,需在其前加反斜线 \ 进行转义。
  • 示例:
    • 正则表达式 end\. 仅匹配字符串 "end.",其中 \. 表示字面的句点;
    • 若写作 end.,则会匹配 "enda""end3" 等任意以 "end" 开头后接任意字符的字符串。

注意:反斜线本身也是元字符,因此匹配字面反斜线需写为 \\


3. 单字符正则表达式

正则表达式的基本构建单元是“单字符正则表达式”,即一次只匹配一个字符的模式。

  • 普通字符:如 ab/ 等非元字符,直接匹配其自身。例如:
  • 正则 a 匹配字符串中的 "a"
  • 正则 / 匹配路径中的斜杠。

  • 转义字符支持

  • 对 6 个元字符进行转义后可匹配其字面形式:
    • \. → 匹配 .
    • \* → 匹配 *
    • \$ → 匹配 $
    • \^ → 匹配 ^
    • \[ → 匹配 [
    • \\ → 匹配 \
  • 示例:正则表达式 \\* 表示“一个反斜线后跟一个星号”,因此匹配字符串 \*
    • 但不匹配 \\*(因为该字符串包含两个反斜线和一个星号,而 \\* 中的 * 是量词,表示前面的 \ 出现 0 次或多次——此处易混淆,PPT 强调此写法实际意图为匹配字面 \*,故应理解为两个转义字符 \\* 的组合,即 \\\* 才更准确;但根据 PPT 原文,其意图是说明 \\* 被解释为“字面 \ 后跟字面 *”,可能隐含上下文为 BRE 中 * 需转义才为字面)。
  • 非法转义:如 \u\x(在基础正则中)等组合未被定义,属于 undefined(未定义)行为;后续软件(如 Perl、Python)可能赋予其特殊含义(如 Unicode 转义),但在传统 Unix 工具中应避免使用。

  • 圆点 .

  • 是最重要的单字符通配符,匹配任意单个字符(通常不包括换行符 \n);
  • 示例:正则 a.c 可匹配 "abc""a2c""a c" 等。

4. 单字符正则表达式:定义集合(字符类)

通过方括号 [...] 可定义一个字符集合(Character Class),表示匹配其中任意一个字符。

  • 基本语法
  • [abcd] 表示匹配 abcd 中的任意一个字符;
  • 集合内字符顺序无关,重复无影响。

  • 元字符在集合内的行为

  • [...] 内部,大多数元字符失去特殊含义,被视为普通字符;
  • 特别地,.*\ 在集合中表示其字面值;
  • 示例:[\\*.] 是一个包含三个字符的集合,可匹配:

    • 反斜线 \
    • 星号 *
    • 句点 .
  • 区间表示法(使用连字符 -

  • [起始-结束] 表示连续字符范围;
  • 常见用法:
    • [a-d] 等价于 [abcd]
    • [A-Z] 匹配任意大写字母
    • [a-zA-Z0-9] 匹配任意英文字母或数字(即“字母数字”)
  • 注意:若 - 出现在集合的末尾(或开头),则不表示区间,而是普通字符;

    • 例如:[ad-] 仅匹配 ad- 三个字符;
    • 同理,[-ad] 也匹配 -ad
  • 补集(否定集合)

  • 在集合开头使用 ^ 表示“匹配不在该集合中的任意字符”;
    • 示例:[^a-z] 匹配任意非小写字母的字符(如大写字母、数字、标点等);
  • 特殊情况:若需在补集中包含 ][,需注意转义或位置;
    • 示例:[^][] 表示“匹配既不是 [ 也不是 ] 的字符”;
    • 解析:第一个 ] 紧跟在 ^ 后,作为普通字符加入集合;第二个 ] 是集合的结束符;
  • 关键规则^ 只有在集合的第一个位置才表示补集;
    • 若出现在其他位置(如 [a-z^]),则 ^ 被视为普通字符;
    • 因此 [a-z^] 可匹配 26 个小写字母 + ^,共 27 个字符。

总结:字符集合是构建灵活匹配模式的基础,结合区间与补集可高效描述复杂字符类别。

二、组合与锚点

1. 单字符正则表达式的组合

正则表达式通过将单字符正则表达式按特定规则组合,形成更复杂的匹配模式。PPT 指出:“长的正则表达式由单字符正则表达式构成”。

  • 连接(串接)
    多个单字符正则表达式依次排列,表示连续匹配
  • 示例:

    • abc:严格匹配连续三个字符 "a"、"b"、"c";
    • [A-Z].[0-9]:匹配一个大写字母 + 任意字符 + 一个数字,如 "X3y5" 中的 "X3y" 不匹配(因后接 y 非数字),但 "M@7" 可匹配。
  • 星号 *(Kleene 闭包)
    在基本正则表达式(BRE)中,* 是一个量词,作用于其前一个单字符正则表达式,表示该单元出现 0 次或多次

  • 关键特性:
    • * 不是独立字符,必须紧跟在可重复单元之后;
    • 匹配是贪婪的(尽可能多匹配)。
  • PPT 提供的典型示例:

    • 12*4
    • 匹配 142 出现 0 次),
    • 匹配 1242 出现 1 次),
    • 匹配 12242 出现 2 次),
    • 不匹配 1234(因 2* 只能重复 2,不能匹配 3)。
    • [A-Z][0-9]*
    • 匹配 A(数字部分出现 0 次),
    • 匹配 A1C45D768 等(一个大写字母后跟任意长度数字串),
    • 不匹配 b64512(首字符非大写)、T56t(末尾 t 非数字且不在模式中)。
  • 空格与转义组合示例
    PPT 强调正则表达式可处理带空格和转义符号的复杂文本结构

  • [Cc]hapter *[1-4]
    • [Cc] 匹配 "C" 或 "c";
    • hapter 字面匹配;
    • * 表示零个或多个空格(注意:空格本身是普通字符,* 作用于空格);
    • [1-4] 匹配数字 1–4;
    • 因此可匹配 "Chapter2"(无空格)、"chapter 3"(一个空格)、"Chapter 4"(多个空格)等。
  • a\[i\]*=*b\[j\]*\\*\*c\[k\]
    • 此模式用于匹配类似 a[i]=b[j]\*c[k] 的字符串;
    • \[\] 转义为字面方括号;
    • *= 表示“零或多个空格 + 等号”(实际应为 *= * 更准确,但 PPT 原文如此,意在说明 * 可作用于空格以容忍两侧空白);
    • \\*\* 匹配字面 \*(即反斜线 + 星号),其中 \\ 为字面 \\* 为字面 *
    • 整体容许多余空格出现在 =\* 两侧。

注意:此处 * 作用对象需明确——在 BRE 中,* 仅作用于紧邻的前一个字符或字符类。


2. 锚点:$^

锚点(Anchors)用于限定匹配发生的位置,而非匹配具体字符。PPT 明确指出 $^ 的含义依赖于其在正则表达式中的位置

  • 行尾锚点 $
  • $ 出现在正则表达式的最末尾时,表示匹配行的结束位置
    • 示例:123$ 仅匹配行尾为 "123" 的行(如 "abc123" 匹配,"123abc" 不匹配);
    • .$ 匹配行尾的任意单个字符。
  • $ 不在末尾,则被视为普通字符;

    • 示例:$123 匹配字面字符串 "$123"。
  • 行首锚点 ^

  • ^ 出现在正则表达式的最开头时,表示匹配行的起始位置
    • 示例:^printf 仅匹配以 "printf" 开头的行;
    • 不在行首的 "printf"(如 " printf")不匹配。
  • ^ 不在开头,则被视为普通字符;

    • 示例:Hel^lo 匹配字面字符串 "Hel^lo"。
  • 应用示例(vi 命令)
    PPT 结合 vi 编辑器展示锚点的实际用途:

  • :1,$s/[0-9]*/xx/g
    • 对全文(第 1 行到最后一行 $)执行替换;
    • 将所有由数字组成的子串(包括空匹配,因 * 允许 0 次)替换为 "xx";
    • 注意:由于 * 允许 0 次,可能在非数字位置也插入 "xx",实际使用中常改用 [0-9][0-9]*[0-9]\+
  • :10,50s/^ //g
    • 对第 10 至 50 行操作;
    • ^ 表示行首的四个空格;
    • 替换为空,即删除每行开头的四个空格
    • 此处 ^ 作为行首锚点,确保只删除行首空格,而非行中空格。

关键理解:^$位置断言,不消耗字符,仅限定匹配上下文。


3. 正则表达式扩展(ERE / PCRE)

PPT 指出,基本正则表达式(BRE)存在局限,因此发展出扩展正则表达式(ERE)Perl 兼容正则表达式(PCRE),提供更强大的语法。

  • 分组与逻辑
  • 分组:使用圆括号 () 将子表达式组合为一个单元;
    • 在 BRE 中需转义:\( ... \)
    • 在 ERE/PCRE 中直接使用 ( ... )
    • 示例:(xy)* 可匹配 ""(空)、"xy"、"xyxy" 等。
  • 逻辑“或”:使用 \|(BRE)或 |(ERE/PCRE);

    • 示例:(pink\|green)(BRE 写法)匹配 "pink" 或 "green";
    • egrepgrep -E 中可写作 (pink|green)
  • 重复限定符(扩展量词)
    相比 BRE 仅有 *,ERE/PCRE 引入更多量词:

  • +:匹配前一项 1 次或多次(等价于 \{1,\});
    • 示例:[0-9]+ 匹配至少一位的数字串(如 "1"、"123"),不匹配空字符串
  • ?:匹配前一项 0 次或 1 次(等价于 \{0,1\});
    • 示例:a? 匹配 "" 或 "a"。
  • \{m,n\}:BRE 中的区间量词(ERE 中为 {m,n});

    • 示例:[1-9][0-9]\{6,8\}
    • [1-9]:首位非零;
    • [0-9]\{6,8\}:后续 6 到 8 位数字;
    • 整体匹配 7 到 9 位的数字(如 "1234567"、"987654321")。
  • 预定义字符类
    为简化常用字符集合,引入命名类别:

  • POSIX 标准(适用于 grepsed 等):
    • [[:xdigit:]]:匹配十六进制数字(0-9a-fA-F);
    • 其他如 [[:digit:]][[:alpha:]] 等。
  • PCRE 扩展(如 grep -P):

    • \d:等价于 [0-9](数字);
    • \D:等价于 [^0-9](非数字);
    • 还有 \w(单词字符)、\s(空白符)等。
  • 高级锚点
    PPT 提及更灵活的锚定需求,超越 ^/$

  • 例如:“寻找一个数字串,但要求这个数字串不许出现在‘合计’两个字之后”;
  • 这类需求通常需要负向先行断言(negative lookbehind),如 (?<!合计)\d+(PCRE 支持);
  • 传统 BRE/ERE 无法直接实现,需结合上下文处理或多步过滤。

总结:ERE 和 PCRE 极大增强了正则表达式的表达能力,使复杂文本处理成为可能,但需注意工具兼容性(如 grep 默认 BRE,需 -E-P 启用扩展)。

三、三个与正则表达式相关的处理命令

1. grep:在文件中查找字符串(筛选)

grep(Global Regular Expression Print)是 Linux 中最常用的文本搜索工具,其核心功能是根据正则表达式模式筛选包含匹配内容的行

  • 基本语法
    Bash
    grep 模式 文件名列表
    
  • 若未指定文件,则从标准输入读取;
  • 默认输出匹配的整行内容。

  • 三种变体及其正则支持

  • grep:默认使用基本正则表达式(BRE),元字符如 (){}| 需转义才具特殊含义。
  • egrep(或 grep -E):使用扩展正则表达式(ERE),支持 ()|+? 等无需转义,模式描述更灵活。
  • fgrep(或 grep -F):固定字符串搜索(Fixed strings),不解释任何正则元字符,按字面字符串匹配,速度更快,适用于纯文本关键词查找;底层可使用 KMP、AC 等高效字符串匹配算法加速。

  • 常用选项(PPT 明确列出):

  • -n:在输出前加上行号
    • 示例:grep -n main *.c → 查找所有 .c 文件中含 "main" 的行,并显示行号;多文件时同时显示文件名。
  • -v反向匹配,输出不包含模式的行;
    • 示例:grep -v '[Dd]isable' dev.stat > dev.active → 过滤掉含 "Disable" 或 "disable" 的行,生成新文件。
  • -i忽略大小写
    • 示例:grep -i richard telnos → 匹配 "Richard"、"RICHARD"、"richard" 等。
  • -E / -G / -P
    • -E:启用 ERE(等价于 egrep);
    • -G:显式使用 BRE(默认行为);
    • -P:启用 Perl 兼容正则表达式(PCRE),支持 \d\w、前瞻断言等高级特性(需系统支持,可通过 man pcresyntax 查阅语法)。

注意:PPT 强调 grep 是“筛选”工具,侧重于选择性输出,而非修改内容。


2. sed:流编辑(文本加工)

sed(Stream Editor)是一种非交互式流编辑器,逐行读取输入,对每行应用编辑命令,输出结果。它擅长批量文本替换、删除、插入等加工操作

  • 基本用法
  • 单命令:sed '命令' 文件名列表
  • 多命令:
    • sed -e '命令1' -e '命令2' 文件
    • sed -f 命令文件 文件(从文件读取多条命令)
  • 可与管道结合:tail -f pppd.log | sed 's/.../.../g'

  • 核心命令:替换(s 命令)
    语法:s/pattern/replacement/flags

  • 模式(pattern):支持 BRE(部分实现支持 ERE);
    • 示例:s/145\.37\.23\.26/桥西/g → 将 IP 地址替换为地名(注意 . 需转义);
    • PPT 示例:cat pm.txt | sed 's/\[[^][]*\]//g' → 删除所有 [...] 形式的标记(如 [注释])。
  • 子表达式捕获与回溯引用

    • 在 BRE 中,用 \(...\) 定义子组;
    • 在替换串中用 \1, \2, ... 引用匹配内容(\0& 表示整个匹配);
    • 示例:
      Bash
      s/\([0-9][0-9]\)-\([0-9][0-9]\)-\([0-9]*\)/\3.\1.\2/g
      
    • 04-26-1997 转换为 1997.04.26
    • \1=月,\2=日,\3=年。
    • 批量重命名示例:
      Bash
      sed 's/.*-\([0-9][0-9]\).rmvb/mv "&" "第\1集.rmvb"/'
      
    • 输入:[快视频...]-69.rmvb
    • 输出:mv "[快视频...]-69.rmvb" "第69集.rmvb"
    • &(或 \0)代表整个匹配的字符串。
  • 实际应用场景(PPT 提供):

  • 实时日志脱敏/转换
    tail -f pppd.log | sed -f sed.cmd,其中 sed.cmd 包含多条 IP 替换规则。
  • 结构化数据提取与格式化:如日期转换、文件名重构等。

关键点:sed 是“加工”工具,直接修改文本内容,常用于自动化脚本。


3. awk:逐行扫描的文本处理语言(筛选+加工)

awk 不仅是一个命令,更是一门专为文本处理设计的小型编程语言,兼具筛选加工能力。

  • 基本结构条件 { 动作 }
  • awk 自动对输入的每一行执行程序;
  • 若满足“条件”,则执行“动作”;
  • 可定义多个语句块,用空格或分号分隔。

  • 自动行为与内置变量

  • 记录(Record):默认每行是一个记录;
    • 内置变量 NR:当前记录号(即行号)。
  • 域(Field):每行按空白(空格/制表符)分割为多个字段;

    • $1:第一个字段,$2:第二个字段,……;
    • $0:整行内容;
    • 字段数量存于 NF
  • 条件描述方式

  • 关系与逻辑运算符(类似 C 语言):
    • <, <=, ==, !=, >, >=
    • &&(与)、||(或)、!(非)
  • 正则表达式匹配
    • /regexpr/:若整行包含该模式,则执行动作;
    • $2 ~ /[1-9][0-9]*/:判断第二字段是否匹配“非零开头的数字串”;
    • !~ 表示不匹配。
  • 特殊条件块

    • BEGIN { ... }:在读取任何输入前执行(常用于初始化);
    • END { ... }:处理完所有行后执行(常用于汇总输出);
    • 无条件块:对所有行执行。
  • 动作描述方式

  • 变量赋值与算术运算:支持 +, -, *, /, % 等;
  • 流程控制if (...) { ... }for (i=1; i<=NF; i++) { ... }
  • 输出

    • print var1, var2, ...:输出变量,以空格分隔;
    • printf("格式", var1, var2, ...):格式化输出(类似 C 语言)。
  • 典型应用场景(PPT 暗示):

  • 从结构化日志中提取特定列;
  • 对数值字段求和、计数、平均;
  • 结合正则过滤并重组输出格式(如生成 CSV)。

总结:awk 是“筛选+加工”的全能工具,适合处理列式数据复杂逻辑


四、正则表达式应用实例(选讲)

PPT 通过三个综合性案例展示正则表达式在真实场景中的威力。

例1:累加作品发行量

  • 任务:从一段非结构化中文文本(如作家简介)中提取各作品的发行量并求和。
  • 文本特征:包含“畅销200多万册”、“110多万册”等描述。
  • 解决思路
  • 使用正则表达式匹配数字模式,如 [0-9]+[0-9]+万?;
  • 提取数字部分(可能需处理“万”单位);
  • awk 或脚本累加数值。
  • 技术要点:正则匹配 + 数值提取 + 累加计算。

例2:统计英文小说高频词

  • 任务:给定英文小说文本文件,找出出现频率最高的 200 个单词。
  • 解决步骤
  • 预处理:转为小写(tr 'A-Z' 'a-z'),去除标点(用正则替换非字母字符为空格);
  • 切分单词:利用 awk 按空白分割字段;
  • 计数:用 awk 哈希表统计每个单词出现次数;
    Awk
    { for(i=1; i<=NF; i++) count[$i]++ }
    END { for(word in count) print count[word], word }
    
  • 排序取前200sort -nr | head -200
  • 技术要点:正则清洗 + 字段分割 + 哈希计数 + 排序。

例3:绘制游客流量随时间变化的曲线

  • 任务:从景区流量网页中提取数据,生成 CSV 供绘图。
  • 流程
  • 获取数据wget http://.../flow.php -O 180801.html
  • 解析 HTML:用正则表达式提取景点名、时间、人数;
    • 示例目标格式:故宫,2018-08-01 9:00,1.26
  • 结构化输出:通过 sedawk 提取并重组为 CSV 行;
    • 需匹配如 <div>故宫</div><time>09:00</time><num>1.26</num> 等结构(PPT 未给 HTML 片段,但强调正则提取能力)。
  • 输出要求:严格格式化的 .csv 文件,便于 Excel 或 Python 绘图。
  • 技术要点:网络抓取 + 正则提取 + 格式标准化。

总结:这三个例子分别体现了正则表达式在信息抽取文本分析数据工程中的核心作用,展示了 grep/sed/awk 组合的强大生产力。