在 Shell 编程中,正则表达式(Regular Expression)和文本处理器(如 grep, sed, awk 等)是两个极其重要的工具,它们允许我们以复杂且高效的方式搜索、处理和操作文本数据。本博客将简要介绍 Shell 编程中正则表达式的使用,并展示如何使用 grep, sed, 和 awk 这三个强大的文本处理器。
什么是正则表达式呢?
正则表达式,又称正规表示式、正规表示法、正规表达式、规则表达式、常规表示法(英语:Regular Expression,在代码中常简写为 regex、regexp 或 RE),计算机科学的一个概念。正则表达式使用单个字符串来描述、匹配一系列符合某个句法规则的字符串。在很多文本编辑器里,正则表达式通常被用来检索、替换那些符合某个模式的文本。
基础正则表达式是常用正则表达式最基础的部分。在 Linux 系统中常见的文件 处理工具中 grep 与 sed 支持基础正则表达式,而 egrep 与 awk 支持扩展正则表达式。
grep
是 Unix/Linux 系统中一个强大的命令行文本搜索工具,全称为 Global Regular Expression Print。它的核心功能是通过正则表达式(Regular Expression, Regex)在文件或输入流中快速匹配并输出符合条件的文本行。
grep [选项] "正则表达式模式" 文件名
简单示例:
grep "error" log.txt #在log.txt 中查找包含 "error"的行
grep
默认支持基础正则表达式(Basic Regular Expression, BRE),若需使用更强大的扩展正则表达式(Extended Regular Expression, ERE),需添加 -E
选项或使用 egrep
命令。
类型 | 语法支持 | 启用方式 | 示例 |
BRE | 基础正则(需转义’+,?,等) | 默认 | grep "a\{2\}" file(匹配连续两个a) |
ERE | 扩展正则(直接使用'+,?,等) | grep -E或egrep | egrep "a{2}" file |
*
:前一个字符出现0次或多次。
+
:重复一个或者一个以上的前一个字符
?
:零个或者一个的前一个字符
{n}
:精确匹配n次。
{n,}
:至少匹配n次。
{n,m}
:匹配n到m次。
选项 | 功能 | 示例 |
-i | 忽略大小写 | grep -i "Error" file |
-v | 反向匹配(输出不匹配的行) | grep -v "success" file |
-o | 仅输出匹配的文本(而非整行) | grep -o "\d{3}" file |
-n | 显示匹配行的行号 | grep -n "error" file |
-c | 统计匹配行的数量 | grep -c "warning" file |
-r或-R | 递归搜索目录下的所有文件 | grep -r "pattern" /path/to/dir |
-A num | 输出匹配行及之后 num 行 | grep -A 2 "error" file |
-B num | 输出匹配行及之前 num 行 | grep -B 2 "error" file |
-C num | 输出匹配行及前后各 num 行 | grep -C 2 "error" file |
grep "apple" fruits.txt #查找包含"apple"的行
grep -i "Apple" fruits.txt #忽略大小写
egrep "go{2}d" file # 匹配 "good"(o 出现 2 次)
egrep "a+b" file # 匹配 "ab", "aab", "aaab" 等
grep "^start" file # 匹配以 "start" 开头的行
grep "end$" file # 匹配以 "end" 结尾的行
grep "^start" file # 匹配以 "start" 开头的行
grep "end$" file # 匹配以 "end" 结尾的行
# 提取 IP 地址(简单示例)
egrep -o "([0-9]{1,3}\.){3}[0-9]{1,3}" access.log
grep -v "#" config.conf # 排除注释行(以 # 开头)
grep -c "ERROR" app.log # 统计 "ERROR" 出现的行数
sed是一个强大而简单的文本解析转换工具,可以读取文本,并根据指定的条件对文本进行编辑(删除,替换,添加,移动等),最后输出所有行或者仅输出处理的某些行。sed也可以在无交互的情况下实现相当复杂的文本处理操作,被广泛应用于Shell脚本中,用以完成各种自动化处理任务。
sed的工作流程主要包括读取,执行和显示三个过程。
读取:sed 从输入流(文件、管道、标准输入)中读取一行内容并存储到临时的缓冲区中(又称模式空间,pattern space)。
执行:默认情况下,所有的 sed 命令都在模式空间中顺序地执行,除非指定了行的地址,否则 sed 命令将会在所有的行上依次执行。
显示:发送修改后的内容到输出流。在发送数据后,模式空间将会被清空。
sed [选项] '命令' 文件名
常用选项:
选项 | 说明 |
-n | 使用安静(silent)模式。在一般 sed 的用法中,所有来自 STDIN 的数据一般都会被列出到终端上。但如果加上 -n 参数后,则只有经过sed 特殊处理的那一行(或者动作)才会被列出来。 |
-i | 直接修改读取的文件内容,而不是输出到终端。 |
-e | 直接在命令列模式上进行 sed 的动作编辑 |
-f | 直接将 sed 的动作写在一个文件内, -f filename 则可以运行 filename 内的 sed 动作 |
-r | sed 的动作支持的是延伸型正规表示法的语法。(默认是基础正规表示法语法) |
示例:
sed 's/old/new/' file.txt # 每行第一个 "old" 替换为 "new"
sed 's/old/new/g' file.txt # 替换所有 "old" 为 "new"
sed 's/old/new/2' file.txt # 每行第二个 "old" 替换为 "new"
sed 's/old/new/gi' file.txt # 全局替换,忽略大小写
示例:
sed '3d' file.txt # 删除第3行
sed '/pattern/d' file.txt # 删除包含 "pattern" 的行
sed '1,5d' file.txt # 删除1到5行
示例:
sed -n '3p' file.txt # 仅打印第3行
sed -n '/pattern/p' file.txt # 打印包含 "pattern" 的行
示例:
sed '3i\插入内容' file.txt # 在第3行前插入一行 "插入内容"
sed '/pattern/a\追加内容' file.txt # 在匹配行后追加一行
示例:
sed '/error/w error.log' app.log # 将包含 "error" 的行写入 error.log
地址范围用于限定命令作用的行,可以是行号或正则表达式匹配。
sed默认支持基础正则表达式(BRE),通过-r
或-E
启用扩展正则表达式(ERE)。
常见用法:
sed -r 's/([0-9]{4})-([0-9]{2})-([0-9]{2})/\2\/\3\/\1/' dates.txt
将YYYY-MM-DD转换为MM/DD/YYY
sed '/^$/d' file.txt # 删除所有空行
sed -n 's/.*Name: \(.*\) Age:.*/\1/p' data.txt
提取 "Name:"后的名字(假设格式为Name:Alice Age:30)。
AWK 是一种优良的文本处理工具。它允许您创建简短的程序,这些程序读取输入文件、为数据排序、处理数据、对输入执行计算以及生成报表,还有无数其他的功能。最简单地说,AWK 是一种用于处理文本的编程语言工具。
核心特点:
awk '模式 {动作}’ 文件名
或编写多行脚本:
awk -f script.awk 文件名
常用选项:
选项 | 说明 |
-F | 指定字段分隔符(如-F:,-F',') |
-v | 定义变量(如-v var=valur) |
-f | 从脚本文件读取命令 |
awk '{print $0}' file.txt # 打印所有行(等价于 `cat`)
awk 'NR == 3' file.txt # 打印第3行
awk 'NR >= 2 && NR <=5' file # 打印第2到5行
awk '$1 == "root"' /etc/passwd # 第1列为 "root" 的行
awk '$3 > 1000' /etc/passwd # 第3列大于1000的行
awk '/error/ {print $2}' log.txt # 匹配含 "error" 的行,打印第2列
awk '$1 ~ /^192\.168/ {print $0}' ips.txt # 第1列以 "192.168" 开头的行
$0 | 表示整个当前行 |
$1 | 每行的第一个字段 |
NF | 字段数量变量 |
BR | 每行的记录号,多文件记录递增 |
FNR | 与NR类似,不过多文件记录不递增,每个文件都从1开始 |
\t | 制表符 |
\n | 换行符 |
FS | BEGIN时定义分隔符 |
RS | 输入的记录分隔符,默认为换行符(即文本是按一行一行输入) |
~ | 匹配,与==相比不是精确比较 |
!~ | 不匹配,不精确比较 |
== | 等于,必须全部相等,精确比较 |
!= | 不等于,精确比较 |
&& | 逻辑与 |
|| | 逻辑或 |
+ | 匹配时表示1个或1个以上 |
/[0-9][0-9]*/ | 两个或两个以上数字 |
/[0-9][0-9]*/ | 一个或一个以上数字 |
FILENAME | 文件名 |
OFS | 输出字段分隔符,默认也是空格,可以改为制表符等 |
ORS | 输出的记录分隔符,默认为换行符,即处理结果也是一行一行输出到屏幕 |
-F'[:#/]' | 定义三个分隔符 |
awk '{print $1, $3}' file.txt # 打印第1列和第3列(默认空格分隔)
awk -F':' '{print $1, $7}' /etc/passwd # 以冒号分隔,打印用户名和Shell
awk '{printf "User: %-10s UID: %d\n", $1, $3}' /etc/passwd
# 筛选第2列大于50的行
awk '$2 > 50 {print $1, $2}' data.txt
# 结合逻辑运算符
awk '$1 == "admin" && $3 != "0" {print $0}' users.txt
# 计算第1列的总和
awk '{sum += $1} END {print sum}' data.txt
# 计算平均值
awk '{sum += $1; count++} END {print sum/count}' data.txt
# 统计每列的最大值
awk 'NR==1 {max=$1} $1>max {max=$1} END {print max}' data.txt
# 替换文本
awk '{gsub("old", "new"); print}' file.txt
# 提取IP地址
awk '{match($0, /([0-9]{1,3}\.){3}[0-9]{1,3}/); print substr($0, RSTART, RLENGTH)}' log.txt
# 统计单词频率
awk '{for (i=1; i<=NF; i++) count[$i]++} END {for (word in count) print word, count[word]}' text.txt