shell编程正则表达式与文本处理器完全解析
作者:翻斗包菜
前言
在shell编程的世界里,正则表达式与文本处理器是两大不可或缺的利器。正则表达式(Regular Expression,简称RE)作为一种强大的字符串匹配工具,能够通过特定的字符串合来描述和匹配一系列符合特定的规则的字符串。它不仅脚本编程中表演着重要角色,还是文本编辑器及多种程序设计语言中的核心功能之一
一、正则表达式基础
1. 什么是正则表达式
正则表达式(Regular Expression,简称 Regex)是一种用于匹配、查找和处理文本的强大工具。在 Shell 编程中,它是处理文本数据的核心技术之一,广泛应用于日志分析、数据提取、文本替换等场景。正则表达式的核心思想是使用特定的字符组合来描述文本的模式,从而实现对文本的精准操作。
2. 正则表达式的分类
在 Linux 系统中,正则表达式主要分为两类:
表格
| 类型 | 说明 | 适用工具 |
|---|---|---|
| 基础正则表达式(BRE) | 功能相对基础,元字符较少,部分元字符需要转义 | grep、sed、vi 等 |
| 扩展正则表达式(ERE) | 功能更丰富,支持更多元字符,无需转义 | egrep、awk、sed -r 等 |
3. 基础正则表达式元字符
基础正则表达式是正则表达式的核心,掌握这些元字符是使用正则表达式的基础。
表格
| 元字符 | 作用 | 示例 |
|---|---|---|
^ | 匹配行首,锚定到行的开始位置 | ^root 匹配以 root 开头的行 |
$ | 匹配行尾,锚定到行的结束位置 | nologin$ 匹配以 nologin 结尾的行 |
. | 匹配任意单个字符(换行符除外) | r..t 匹配 root、rbot、r56t 等 |
* | 匹配前一个字符 0 次或多次,包括 0 次 | ro*t 匹配 rt、rot、root、rooot 等 |
[] | 匹配方括号内任意一个字符,支持范围表示 | [Rr]oot 匹配 Root 或 root;[0-9] 匹配任意数字 |
[^] | 匹配不在方括号内的任意字符,即取反 | [^0-9] 匹配非数字字符;[^a-z] 匹配非小写字母 |
\{n\} | 匹配前一个字符恰好 n 次,n 为非负整数 | o\{2\} 匹配两个连续的 o,如 "oo" |
\{n,\} | 匹配前一个字符至少 n 次,n 为非负整数 | o\{2,\} 匹配两个或更多连续的 o,如 "oo"、"ooo" 等 |
\{n,m\} | 匹配前一个字符 n 到 m 次,n≤m,均为非负整数 | o\{2,3\} 匹配两个或三个连续的 o |
4. 扩展正则表达式元字符
扩展正则表达式在基础正则表达式的基础上增加了更多功能,使用起来更加灵活。
表格
| 元字符 | 作用 | 示例 | |
|---|---|---|---|
+ | 匹配前一个字符 1 次或多次,至少 1 次 | go+gle 匹配 gogle、google、goooogle 等 | |
? | 匹配前一个字符 0 次或 1 次,最多 1 次 | colou?r 匹配 color 或 colour | |
() | 分组,将多个字符作为一个整体进行匹配和引用 | (go)+ 匹配 go、gogo、gogogo 等;(123)\1 匹配 "123123" | |
| ` | ` | 或操作,匹配其中一个选项 | root|admin 匹配 root 或 admin;a|b 匹配 a 或 b |
5. 正则表达式的优先级
正则表达式的元字符有不同的优先级,从高到低依次为:
- 转义符
\ - 分组
() - 限定符
*、+、?、{n}、{n,}、{n,m} - 锚点
^、$ - 或操作
|
了解优先级有助于正确构建复杂的正则表达式。
二、文本处理工具:sed
1. sed 简介
sed(Stream Editor)是一个流编辑器,主要用于对输入流(文件或管道输入)进行基本的文本转换。它逐行处理输入,并将结果输出到标准输出。sed 是一个非交互式的编辑器,非常适合在脚本中使用。
2. sed 的工作原理
sed 的工作流程可以分为以下几个步骤:
- 从输入流中读取一行内容到模式空间(pattern space)
- 根据提供的编辑指令对模式空间中的内容进行处理
- 将处理后的内容输出到标准输出(默认情况下)
- 清空模式空间,读取下一行,重复上述过程
此外,sed 还有一个保持空间(hold space),用于临时存储数据,可以在处理过程中与模式空间进行数据交换,实现更复杂的文本处理。
3. sed 的基本语法
sed [选项] '模式或编辑指令' 文件1 文件2... sed -f 脚本文件 文件1 文件2...
常用选项:
表格
| 选项 | 作用 |
|---|---|
-n | 只输出经过处理的行,默认输出所有行 |
-e | 允许多个编辑指令,多个指令之间用分号分隔 |
-f | 从脚本文件中读取编辑指令,适合复杂的 sed 脚本 |
-i | 直接修改原文件,而不是输出到标准输出,使用时需谨慎 |
-r | 支持扩展正则表达式,否则默认使用基础正则表达式 |
-s | 将多个输入文件视为独立的流,而不是一个连续的流 |
4. sed 的编辑指令
sed 的编辑指令非常丰富,涵盖了文本处理的各个方面。
表格
| 指令 | 作用 | 示例 |
|---|---|---|
p | 打印匹配的行,通常与-n选项配合使用 | sed -n '/root/p' /etc/passwd |
d | 删除匹配的行 | sed '/^#/d' test.txt |
s | 替换匹配的字符串,是 sed 最常用的指令 | sed 's/old/new/g' test.txt |
a | 在匹配行后添加内容,支持多行添加 | sed '/^root/a\New line' test.txt |
i | 在匹配行前插入内容,支持多行插入 | sed '/^root/i\New line' test.txt |
c | 替换匹配的整行,将匹配行替换为新内容 | sed '/^root/c\New line' test.txt |
y | 按字符转换,将字符一一对应转换 | sed 'y/abc/ABC/' test.txt |
= | 打印匹配行的行号 | sed -n '/error/=' /var/log/messages |
r | 从文件中读取内容并插入到匹配行后 | sed '/^#/r header.txt' test.txt |
w | 将匹配行写入到文件中 | sed -n '/error/w error.log' /var/log/messages |
5. sed 用法示例
(1) 输出文本
# 输出包含root的行
sed -n '/root/p' /etc/passwd
# 输出第1到3行
sed -n '1,3p' /etc/passwd
# 输出奇数行
sed -n '1~2p' test.txt
# 输出最后一行
sed -n '$p' test.txt
# 输出包含"error"的行及其行号
sed -n '/error/{=;p}' /var/log/messages(2) 删除文本
# 删除空行 sed '/^$/d' test.txt # 删除以#开头的注释行 sed '/^#/d' test.txt # 删除第2行 sed '2d' test.txt # 删除第1到5行 sed '1,5d' test.txt # 删除从匹配行到文件末尾的所有行 sed '/^#/,$d' test.txt
(3) 替换文本
# 全局替换old为new sed 's/old/new/g' test.txt # 只替换每行的第一个匹配 sed 's/old/new/' test.txt # 替换第1到5行中的old为new sed '1,5s/old/new/g' test.txt # 使用扩展正则表达式替换 sed -r 's/(root|admin)/user/g' /etc/passwd # 替换行首的空格为制表符 sed 's/^ /\t/' test.txt # 将每行的第一个单词转换为大写 sed 's/^[a-z]/\U&/' test.txt
(4) 插入和追加文本
# 在第1行前插入内容 sed '1i\This is the first line' test.txt # 在最后一行后追加内容 sed '$a\This is the last line' test.txt # 在包含root的行后追加内容 sed '/root/a\This is a root line' /etc/passwd # 在匹配行前插入多行内容 sed '/^#/i\# This is a comment\n# Added by sed' test.txt
(5) 高级用法:使用保持空间
# 反转文件行序
sed '1!G;h;$!d' test.txt
# 将匹配行与下一行合并
sed '/pattern/{N;s/\n/ /}' test.txt三、文本处理工具:awk
1. awk 简介
awk 是一种编程语言,专门用于文本处理和数据提取。它比 sed 更强大,可以对文本进行更复杂的分析和处理。awk 的名字来源于其创始人 Alfred Aho、Peter Weinberger 和 Brian Kernighan 的姓氏首字母。
2. awk 的工作原理
awk 的工作流程可以分为以下几个阶段:
- BEGIN 块:在处理输入数据之前执行一次,通常用于初始化变量、打印表头等。
- 主体处理:逐行读取输入数据,将每行数据按照指定的分隔符分割成多个字段($1, $2, ...),然后根据提供的模式和动作对数据进行处理。
- END 块:在处理完所有输入数据之后执行一次,通常用于输出统计结果、打印表尾等。
3. awk 的基本语法
awk 选项 '模式或条件 {编辑指令}' 文件1 文件2...
awk -f 脚本文件 文件1 文件2...常用选项:
表格
| 选项 | 作用 |
|---|---|
-F | 指定字段分隔符,默认是空格或制表符 |
-v | 定义变量并赋值,可以在 awk 程序中使用 |
-f | 从脚本文件中读取 awk 程序,适合复杂的 awk 脚本 |
4. awk 的内置变量
awk 提供了许多内置变量,方便我们处理文本数据。
表格
| 变量 | 作用 |
|---|---|
FS | 字段分隔符(Field Separator),默认是空格或制表符 |
NF | 当前行的字段个数(Number of Fields) |
NR | 当前处理的行号(Number of Records),从 1 开始递增 |
FNR | 当前文件的行号(File Number of Records),处理多个文件时有用 |
$0 | 当前行的整行内容 |
$n | 当前行的第 n 个字段(第 n 列),n 从 1 开始 |
FILENAME | 当前处理的文件名 |
RS | 数据记录分隔符(Record Separator),默认是换行符 |
OFS | 输出字段分隔符(Output Field Separator),默认是空格 |
ORS | 输出记录分隔符(Output Record Separator),默认是换行符 |
5. awk 的模式和动作
awk 程序由一系列 "模式 - 动作" 对组成:
plaintext
模式 { 动作 } - 模式:用于匹配输入行的条件,可以是正则表达式、比较表达式、逻辑表达式、BEGIN 块、END 块等。如果模式匹配成功,则执行对应的动作。
- 动作:当模式匹配时执行的命令,通常是打印或处理数据。动作由大括号包裹,包含一个或多个 awk 语句。
如果省略模式,动作将对所有行执行;如果省略动作,默认动作是打印整行({print $0})。
6. awk 的常用函数
awk 提供了许多内置函数,方便我们处理字符串、数学运算等。
表格
| 函数类型 | 函数名 | 作用 |
|---|---|---|
| 字符串函数 | length(str) | 返回字符串 str 的长度 |
substr(str, start, length) | 返回字符串 str 中从 start 位置开始,长度为 length 的子串 | |
index(str, substr) | 返回子串 substr 在字符串 str 中第一次出现的位置 | |
split(str, arr, sep) | 将字符串 str 按照分隔符 sep 分割成数组 arr | |
gsub(pattern, replacement, str) | 在字符串 str 中全局替换 pattern 为 replacement | |
sub(pattern, replacement, str) | 在字符串 str 中替换第一个 pattern 为 replacement | |
| 数学函数 | sin(x) | 返回 x 的正弦值 |
cos(x) | 返回 x 的余弦值 | |
sqrt(x) | 返回 x 的平方根 | |
rand() | 返回 0 到 1 之间的随机数 | |
srand(x) | 设置随机数种子 | |
| 时间函数 | mktime(str) | 将字符串 str 转换为时间戳 |
strftime(format, timestamp) | 将时间戳转换为指定格式的字符串 |
7. awk 用法示例
(1) 按行输出文本
# 输出所有内容,等同于cat test.txt
awk '{print}' test.txt
awk '{print $0}' test.txt
# 输出第1到3行
awk 'NR==1,NR==3{print}' test.txt
awk '(NR>=1)&&(NR<=3){print}' test.txt
# 输出第1行和第3行
awk 'NR==1||NR==3{print}' test.txt
# 输出所有奇数行
awk '(NR%2)==1{print}' test.txt
# 输出所有偶数行
awk '(NR%2)==0{print}' test.txt
# 输出以root开头的行
awk '/^root/{print}' /etc/passwd
# 输出以nologin结尾的行
awk '/nologin$/{print}' /etc/passwd
# 统计以/bin/bash结尾的行数
awk 'BEGIN {x=0};/\/bin\/bash$/{x++};END {print x}' /etc/passwd
# 统计文件中的空行数
awk 'BEGIN {x=0};/^$/{x++};END {print x}' test.txt(2) 按字段输出文本
# 输出每行中(以空格或制表符分隔)的第3个字段
awk '{print $3}' test.txt
# 输出每行中的第1、3个字段
awk '{print $1,$3}' test.txt
# 输出密码为空的用户记录
awk -F ":" '$2==""{print}' /etc/shadow
awk 'BEGIN {FS=":"}; $2==""{print}' /etc/shadow
# 输出以冒号分隔且第7个字段中包含bash的行的第1个字段
awk -F ":" '$7~"/bash"{print $1}' /etc/passwd
# 输出包含8个字段且第1个字段中包含nfs的行的第1、2个字段
awk '($1~"nfs")&&(NF==8){print $1,$2}' /etc/services
# 输出第7个字段既不是/bin/bash也不是/sbin/nologin的所有行
awk -F ":" '($7!="/bin/bash")&&($7!="/sbin/nologin"){print}' /etc/passwd
# 计算文件中所有数字的总和
awk '{sum += $1} END {print sum}' numbers.txt(3) 通过管道、双引号调用 Shell 命令
# 调用wc -l命令统计使用bash的用户个数
awk -F: '/bash$/{print | "wc -l"}' /etc/passwd
# 调用who命令并统计在线用户数
awk 'BEGIN {while ("who" | getline) n++ ; {print n-2}}'
# 调用hostname并输出当前主机名
awk 'BEGIN {"hostname" | getline ; print $0}'
# 遍历目录下的所有文件并输出文件名
awk 'BEGIN {while ("ls" | getline) print $0}'(4) 高级用法:处理多文件和格式化输出
# 处理多个文件,输出每个文件的文件名和行数
awk '{lines[FILENAME]++} END {for (file in lines) print file ": " lines[file]}' file1.txt file2.txt file3.txt
# 格式化输出,将数据对齐输出
awk '{printf "%-15s %-10s %-5d\n", $1, $2, $3}' data.txt四、正则表达式与文本处理实战案例
案例 1:提取系统用户信息
从 /etc/passwd 文件中提取用户名、用户 ID、组 ID 和登录 shell:
awk -F ':' '{printf "%-15s %-5d %-5d %-20s\n", $1, $3, $4, $7}' /etc/passwd 案例 2:统计日志文件中的错误次数
从系统日志 /var/log/messages 中统计包含 "error"、"warning" 和 "info" 的行数:
awk '/error/{error++} /warning/{warning++} /info/{info++} END {print "Errors: " error, "Warnings: " warning, "Info: " info}' /var/log/messages 案例 3:批量修改文件名
将当前目录下所有.txt 文件的扩展名改为.bak:
ls *.txt | awk '{print "mv", $0, substr($0, 1, length($0)-4) ".bak"}' | sh 案例 4:提取 CSV 文件中的特定列并计算平均值
从 data.csv 文件中提取第 2 列和第 4 列数据,并计算第 4 列的平均值:
awk -F ',' '{sum += $4; print $2, $4} END {print "Average: " sum/NR}' data.csv 案例 5:替换配置文件中的参数值
将 nginx.conf 文件中的 worker_processes 值从 1 修改为 4,并将 worker_connections 从 1024 修改为 4096:
sed -i -e 's/worker_processes 1;/worker_processes 4;/' -e 's/worker_connections 1024;/worker_connections 4096;/' nginx.conf
案例 6:分析 Apache 访问日志
从 Apache 访问日志中统计每个 IP 地址的访问次数,并按次数从高到低排序:
awk '{print $1}' access.log | sort | uniq -c | sort -nr 案例 7:提取 HTML 文件中的链接
从 index.html 文件中提取所有的链接(href 属性值):
grep -o 'href="[^"]*"' index.html | sed 's/href="//;s/"$//'
案例 8:处理多行日志
将多行日志合并为单行,方便后续处理:
sed -n '/^[0-9]/ {x;p;d}; H' app.log 五、sed 与 awk 的对比与选择
1. 核心差异
表格
| 特性 | sed | awk |
|---|---|---|
| 处理单位 | 以行为单位 | 以字段为单位 |
| 主要功能 | 文本替换、删除、插入、追加等 | 数据提取、统计、分析、格式化输出等 |
| 编程能力 | 简单的脚本能力 | 完整的编程语言能力,支持变量、数组、函数、控制流等 |
| 适用场景 | 简单的文本转换和行操作 | 复杂的数据处理和分析 |
2. 选择建议
选择 sed 的场景:
- 需要对文本进行简单的替换、删除、插入或追加操作
- 处理的文本以行为单位,不需要对行内的字段进行复杂处理
- 需要编写简洁高效的脚本
选择 awk 的场景:
- 需要从文本中提取特定字段并进行统计、分析或计算
- 需要处理格式化的数据(如 CSV、日志文件等)
- 需要进行复杂的文本处理和数据转换
- 需要编写更复杂的逻辑和控制流
六、总结与进阶建议
正则表达式和文本处理工具是 Linux Shell 编程中不可或缺的核心技能。通过掌握正则表达式的元字符和语法,以及 sed 和 awk 这两个强大的文本处理工具,我们可以高效地处理各种文本数据,完成复杂的文本分析和转换任务。
- sed:以行为单位进行文本处理,擅长文本替换、删除、插入和追加等操作,是处理行级文本的利器。
- awk:以字段为单位进行文本处理,擅长数据提取、统计和分析,是处理结构化文本数据的强大工具。
在实际工作中,我们可以根据具体需求选择合适的工具和方法。对于简单的文本替换和行操作,sed 通常更高效;对于复杂的数据提取和分析,awk 则更为强大。同时,将正则表达式与这些工具结合使用,可以发挥出更大的威力。
进阶建议
- 多练习:通过大量的实战案例来熟悉正则表达式和文本处理工具的使用,积累经验。
- 阅读官方文档:深入学习 sed 和 awk 的官方文档,了解更多高级功能和用法。
- 学习 awk 编程:掌握 awk 的编程特性,如变量、数组、函数、控制流等,编写更复杂的文本处理脚本。
- 结合其他工具:将 sed、awk 与 grep、sort、uniq、cut 等工具结合使用,构建更强大的文本处理管道。
- 编写脚本:将常用的文本处理操作编写成脚本,提高工作效率,避免重复劳动。
掌握这些技能,将使我们在 Linux 系统管理、自动化脚本编写和数据处理等工作中更加得心应手,大大提高工作效率。
到此这篇关于shell编程正则表达式与文本处理器完全解析的文章就介绍到这了,更多相关shell正则表达式与文本处理内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
