文本处理
文本处理是 Shell 脚本最强大的功能之一。Linux 提供了一系列专门用于文本处理的工具,包括 grep、sed、awk 等。掌握这些工具,可以高效地完成日志分析、数据提取、文本转换等任务。
grep - 文本搜索
grep(Global Regular Expression Print)是最常用的文本搜索工具,用于在文件中搜索匹配指定模式的行。
基本用法
grep [options] pattern [file...]
简单搜索
# 在文件中搜索包含 "error" 的行
grep "error" /var/log/syslog
# 搜索多个文件
grep "error" file1.txt file2.txt
# 从标准输入搜索
cat file.txt | grep "error"
常用选项
| 选项 | 含义 |
|---|---|
-i | 忽略大小写 |
-v | 反向匹配(显示不匹配的行) |
-n | 显示行号 |
-c | 只显示匹配行数 |
-l | 只显示包含匹配的文件名 |
-L | 只显示不包含匹配的文件名 |
-w | 匹配整个单词 |
-x | 匹配整行 |
-r / -R | 递归搜索目录 |
-E | 使用扩展正则表达式 |
-F | 固定字符串匹配(不解释为正则) |
-o | 只显示匹配的部分 |
-A n | 显示匹配行及其后 n 行 |
-B n | 显示匹配行及其前 n 行 |
-C n | 显示匹配行及其前后各 n 行 |
--color | 高亮显示匹配内容 |
示例
# 忽略大小写搜索
grep -i "ERROR" file.txt
# 显示行号
grep -n "error" file.txt
# 反向匹配
grep -v "^#" file.txt # 显示不以 # 开头的行(过滤注释)
# 递归搜索目录
grep -r "TODO" ./src/
# 只显示匹配的文件名
grep -l "error" *.log
# 统计匹配行数
grep -c "error" file.txt
# 匹配整个单词
grep -w "error" file.txt # 匹配 "error" 但不匹配 "errors"
# 显示上下文
grep -C 3 "error" file.txt # 显示匹配行前后各 3 行
# 只显示匹配部分
grep -o "[0-9]\+" file.txt # 只输出数字
# 使用扩展正则表达式
grep -E "error|warning" file.txt
# 固定字符串匹配(性能更好)
grep -F "special.chars*" file.txt
正则表达式
grep 支持基本正则表达式(BRE)和扩展正则表达式(ERE)。
基本正则表达式(默认)
# 匹配行首
grep "^Start" file.txt
# 匹配行尾
grep "end$" file.txt
# 匹配任意单个字符
grep "a.c" file.txt # 匹配 abc, a1c, a_c 等
# 匹配前一个字符 0 次或多次
grep "ab*c" file.txt # 匹配 ac, abc, abbc 等
# 匹配字符集
grep "[aeiou]" file.txt # 匹配任意元音字母
grep "[0-9]" file.txt # 匹配任意数字
# 匹配行首或行尾
grep "^$" file.txt # 匹配空行
扩展正则表达式(使用 -E 或 egrep)
# 匹配前一个字符 1 次或多次
grep -E "a+" file.txt
# 匹配前一个字符 0 次或 1 次
grep -E "colou?r" file.txt # 匹配 color 或 colour
# 匹配指定次数
grep -E "a{3}" file.txt # 匹配 aaa
grep -E "a{2,4}" file.txt # 匹配 aa, aaa, aaaa
# 或运算
grep -E "cat|dog" file.txt
# 分组
grep -E "(ab)+" file.txt # 匹配 ab, abab, ababab 等
实用技巧
# 查找进程(排除 grep 自身)
ps aux | grep "[n]ginx"
# 查找大文件
find . -type f -size +100M | xargs grep -l "pattern"
# 统计代码行数(排除空行和注释)
grep -v "^$\|^\s*#" file.sh | wc -l
# 查找 IP 地址
grep -E "[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}" file.txt
# 查找邮箱
grep -E "[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}" file.txt
sed - 流编辑器
sed(Stream Editor)是一个强大的流编辑器,用于对文本进行过滤和转换。它逐行处理输入,可以对行进行替换、删除、插入等操作。
基本语法
sed [options] 'command' [file...]
常用选项
| 选项 | 含义 |
|---|---|
-n | 抑制自动输出 |
-e | 添加编辑命令 |
-f | 从文件读取命令 |
-i | 直接修改文件 |
-r / -E | 使用扩展正则表达式 |
替换命令
基本语法:s/pattern/replacement/flags
# 替换每行第一个匹配
sed 's/old/new/' file.txt
# 替换每行所有匹配
sed 's/old/new/g' file.txt
# 替换第 n 个匹配
sed 's/old/new/2' file.txt # 替换每行第 2 个匹配
# 只打印替换的行
sed -n 's/old/new/p' file.txt
# 忽略大小写
sed 's/old/new/gi' file.txt
# 使用不同的分隔符
sed 's#/old/path#/new/path#g' file.txt
# 使用 & 引用匹配内容
sed 's/[0-9]\+/[&]/g' file.txt # 给数字加方括号
# 使用分组引用
sed -E 's/([a-z]+)@([a-z]+)/\2@\1/g' file.txt # 交换 @ 前后内容
行操作
# 删除行
sed '3d' file.txt # 删除第 3 行
sed '1,5d' file.txt # 删除第 1-5 行
sed '$d' file.txt # 删除最后一行
sed '/pattern/d' file.txt # 删除匹配的行
sed '/^$/d' file.txt # 删除空行
# 打印特定行
sed -n '5p' file.txt # 打印第 5 行
sed -n '5,10p' file.txt # 打印第 5-10 行
sed -n '/pattern/p' file.txt # 打印匹配的行
# 插入和追加
sed '3i\new line' file.txt # 在第 3 行前插入
sed '3a\new line' file.txt # 在第 3 行后追加
# 替换整行
sed '3c\new content' file.txt # 替换第 3 行
# 在行首或行尾添加内容
sed 's/^/prefix: /' file.txt # 行首添加
sed 's/$/ :suffix/' file.txt # 行尾添加
多命令执行
# 使用 -e 执行多个命令
sed -e 's/old/new/g' -e 's/foo/bar/g' file.txt
# 使用分号分隔
sed 's/old/new/g; s/foo/bar/g' file.txt
# 使用 { } 组合命令
sed '/pattern/{s/old/new/g; s/foo/bar/g;}' file.txt
直接修改文件
# 直接修改文件(危险操作,建议先备份)
sed -i 's/old/new/g' file.txt
# 创建备份后修改
sed -i.bak 's/old/new/g' file.txt
实用示例
# 删除 HTML 标签
sed 's/<[^>]*>//g' file.html
# 删除行首空白
sed 's/^[ \t]*//' file.txt
# 删除行尾空白
sed 's/[ \t]*$//' file.txt
# 删除连续空行
sed '/^$/N;/^\n$/D' file.txt
# 给行编号
sed '=' file.txt | sed 'N;s/\n/\t/'
# 提取特定字段
sed -E 's/.*user=([^ ]+).*/\1/' file.txt
# 转换大小写
sed 's/[a-z]/\u&/g' file.txt # 转大写(GNU sed)
sed 's/[A-Z]/\l&/g' file.txt # 转小写(GNU sed)
awk - 文本处理语言
awk 是一种强大的文本处理语言,特别适合处理结构化文本(如 CSV、日志文件)。它将每行分割成字段,可以方便地进行数据提取、统计和格式化。
基本语法
awk [options] 'pattern { action }' [file...]
内置变量
| 变量 | 含义 |
|---|---|
$0 | 整行内容 |
$1, $2, ... | 第 1、2、... 个字段 |
NF | 字段数量 |
NR | 当前记录号(行号) |
FNR | 当前文件的记录号 |
FS | 字段分隔符(默认空格) |
RS | 记录分隔符(默认换行) |
OFS | 输出字段分隔符 |
ORS | 输出记录分隔符 |
FILENAME | 当前文件名 |
基本用法
# 打印整行
awk '{ print }' file.txt
awk '{ print $0 }' file.txt
# 打印特定字段
awk '{ print $1 }' file.txt # 打印第 1 列
awk '{ print $1, $3 }' file.txt # 打印第 1 和第 3 列
# 打印最后一列
awk '{ print $NF }' file.txt
# 打印倒数第二列
awk '{ print $(NF-1) }' file.txt
# 使用自定义分隔符
awk -F: '{ print $1 }' /etc/passwd
awk -F',' '{ print $1, $2 }' file.csv
# 设置输出分隔符
awk -F',' -v OFS='\t' '{ print $1, $2 }' file.csv
模式匹配
# 匹配特定行
awk '/pattern/ { print }' file.txt
# 匹配特定字段
awk '$1 == "error" { print }' file.txt
awk '$3 > 100 { print }' file.txt
# 正则匹配
awk '$1 ~ /^error/ { print }' file.txt # 第 1 列以 error 开头
awk '$1 !~ /debug/ { print }' file.txt # 第 1 列不包含 debug
# 组合条件
awk '$1 == "error" && $3 > 100 { print }' file.txt
awk '$1 == "error" || $1 == "warning" { print }' file.txt
行范围
# 打印指定行
awk 'NR == 5' file.txt # 第 5 行
awk 'NR >= 5 && NR <= 10' file.txt # 第 5-10 行
# 模式范围
awk '/start/,/end/' file.txt # 从 start 到 end 之间的行
# 跳过前 N 行
awk 'NR > 5' file.txt
内置函数
字符串函数
# 字符串长度
awk '{ print length($0) }' file.txt
# 子字符串
awk '{ print substr($1, 1, 3) }' file.txt # 取第 1 列的前 3 个字符
# 分割字符串
awk '{ split($0, arr, ":"); print arr[1] }' file.txt
# 查找和替换
awk '{ gsub(/old/, "new"); print }' file.txt
awk '{ sub(/old/, "new"); print }' file.txt # 只替换第一个
# 大小写转换
awk '{ print toupper($1) }' file.txt
awk '{ print tolower($1) }' file.txt
# 匹配位置
awk '{ print index($0, "pattern") }' file.txt
数学函数
# 基本运算
awk '{ print $1 + $2 }' file.txt
awk '{ print $1 * $2 }' file.txt
# 数学函数
awk '{ print int($1) }' file.txt # 取整
awk '{ print sqrt($1) }' file.txt # 平方根
awk '{ print log($1) }' file.txt # 自然对数
awk '{ print exp($1) }' file.txt # 指数
awk '{ print sin($1) }' file.txt # 正弦
awk '{ print rand() }' file.txt # 随机数
BEGIN 和 END 块
BEGIN 块在处理输入前执行,END 块在处理完所有输入后执行。
# 计算行数
awk 'END { print NR }' file.txt
# 计算字段总和
awk '{ sum += $1 } END { print sum }' file.txt
# 计算平均值
awk '{ sum += $1 } END { print sum/NR }' file.txt
# 设置格式
awk 'BEGIN { FS=":"; OFS="\t" } { print $1, $3 }' /etc/passwd
# 打印表头
awk 'BEGIN { print "Name\tAge" } { print $1, $2 }' file.txt
数组
awk 支持关联数组,可以用于统计和分组。
# 统计每个值出现的次数
awk '{ count[$1]++ } END { for (key in count) print key, count[key] }' file.txt
# 求每组的和
awk '{ sum[$1] += $2 } END { for (key in sum) print key, sum[key] }' file.txt
# 去重
awk '!seen[$0]++' file.txt
# 统计单词频率
awk '{ for (i=1; i<=NF; i++) count[$i]++ } END { for (w in count) print w, count[w] }' file.txt
控制流
awk 支持完整的控制流语句。
# if-else
awk '{ if ($3 > 100) print $1, "large"; else print $1, "small" }' file.txt
# for 循环
awk '{ for (i=1; i<=NF; i++) print $i }' file.txt
# while 循环
awk '{ i=1; while (i<=NF) { print $i; i++ } }' file.txt
# 数组遍历
awk '{ count[$1]++ } END { for (key in count) print key, count[key] }' file.txt
实用示例
# 分析访问日志(统计 IP 访问次数)
awk '{ print $1 }' access.log | sort | uniq -c | sort -rn | head
# 分析访问日志(统计每个 URL 的访问次数)
awk '{ print $7 }' access.log | sort | uniq -c | sort -rn | head
# 分析访问日志(统计 HTTP 状态码)
awk '{ print $9 }' access.log | sort | uniq -c | sort -rn
# 计算文件大小总和
ls -l | awk '{ sum += $5 } END { print sum }'
# 查找最大的文件
ls -l | sort -k5 -rn | awk 'NR==1 { print $9, $5 }'
# 格式化输出
awk 'BEGIN { printf "%-10s %s\n", "Name", "Score" } { printf "%-10s %d\n", $1, $2 }' file.txt
# 提取特定行范围并格式化
awk 'NR>=2 && NR<=10 { printf "%-20s %10d\n", $1, $2 }' file.txt
# CSV 文件处理
awk -F',' 'NR>1 { sum += $3; count++ } END { print "Average:", sum/count }' data.csv
# 合并相邻行
awk '{ printf "%s ", $0 } NR%2==0 { print "" }' file.txt
# 过滤并转换
awk -F: '$3 >= 1000 { print $1, $3 }' /etc/passwd
cut - 字段提取
cut 命令用于从每行中提取指定的字段或字符。
基本用法
# 按字节位置提取
cut -b 1-5 file.txt # 提取每行第 1-5 个字节
cut -b 1,3,5 file.txt # 提取第 1、3、5 个字节
# 按字符位置提取
cut -c 1-10 file.txt # 提取每行第 1-10 个字符
# 按字段提取
cut -d: -f1 /etc/passwd # 以 : 分隔,提取第 1 个字段
cut -d',' -f1,3 file.csv # 以 , 分隔,提取第 1 和第 3 个字段
cut -d',' -f2- file.csv # 提取第 2 个字段及之后的所有字段
示例
# 提取用户名列表
cut -d: -f1 /etc/passwd
# 提取文件扩展名
echo "file.txt" | cut -d. -f2
# 提取 IP 地址的各部分
echo "192.168.1.1" | cut -d. -f1-2 # 输出:192.168
# 提取日期部分
date | cut -d' ' -f1-3
sort - 排序
sort 命令用于对文本行进行排序。
常用选项
| 选项 | 含义 |
|---|---|
-n | 按数值排序 |
-r | 逆序排序 |
-k | 指定排序字段 |
-t | 指定字段分隔符 |
-u | 去重 |
-f | 忽略大小写 |
-M | 按月份排序 |
示例
# 基本排序
sort file.txt
# 数值排序
sort -n numbers.txt
# 逆序排序
sort -rn numbers.txt
# 按指定字段排序
sort -t: -k3 -n /etc/passwd # 按第 3 列(UID)数值排序
# 按多个字段排序
sort -t',' -k1,1 -k2,2n file.csv # 先按第 1 列字母排序,再按第 2 列数值排序
# 去重排序
sort -u file.txt
# 检查是否已排序
sort -c file.txt && echo "已排序" || echo "未排序"
uniq - 去重
uniq 命令用于去除连续的重复行。
常用选项
| 选项 | 含义 |
|---|---|
-c | 显示每行出现的次数 |
-d | 只显示重复的行 |
-u | 只显示不重复的行 |
-i | 忽略大小写 |
示例
# 去除连续重复行
sort file.txt | uniq
# 统计每行出现次数
sort file.txt | uniq -c
# 只显示重复行
sort file.txt | uniq -d
# 只显示出现一次的行
sort file.txt | uniq -u
# 统计出现次数并排序
sort file.txt | uniq -c | sort -rn
tr - 字符转换
tr 命令用于转换或删除字符。
基本用法
# 字符替换
tr 'a-z' 'A-Z' < file.txt # 小写转大写
tr 'A-Z' 'a-z' < file.txt # 大写转小写
# 删除字符
tr -d '0-9' < file.txt # 删除所有数字
tr -d '\n' < file.txt # 删除所有换行
# 压缩重复字符
tr -s ' ' < file.txt # 压缩连续空格为单个空格
tr -s '\n' < file.txt # 压缩连续空行为单个空行
# 删除非指定字符
tr -d -c '0-9' < file.txt # 只保留数字
tr -d -c 'a-zA-Z\n' < file.txt # 只保留字母和换行
示例
# 转换换行符
tr '\r\n' '\n' < dos.txt > unix.txt
# 删除控制字符
tr -d '\000-\037' < file.txt
# ROT13 加密
echo "hello" | tr 'a-zA-Z' 'n-za-mN-ZA-M'
wc - 统计
wc 命令用于统计行数、字数和字节数。
基本用法
# 统计行数
wc -l file.txt
# 统计单词数
wc -w file.txt
# 统计字符数
wc -c file.txt
# 统计字节数
wc -m file.txt
# 统计最长行的长度
wc -L file.txt
# 统计多个文件
wc file1.txt file2.txt
组合使用
这些工具的真正威力在于组合使用。
日志分析
# 统计访问最多的 IP
awk '{ print $1 }' access.log | sort | uniq -c | sort -rn | head -10
# 统计 HTTP 状态码分布
awk '{ print $9 }' access.log | sort | uniq -c | sort -rn
# 查找特定时间段的错误
awk '$4 >= "[01/Jan/2024:00:00:00" && $4 <= "[01/Jan/2024:23:59:59" && $9 ~ /^5/ { print }' access.log
# 统计每分钟的请求数
awk '{ print substr($4, 2, 17) }' access.log | sort | uniq -c
数据处理
# 提取 CSV 中的特定列并排序
cut -d',' -f2 data.csv | sort | uniq
# 计算数值列的总和
awk -F',' '{ sum += $3 } END { print sum }' data.csv
# 合并两个文件的相同列
join -t',' -1 1 -2 1 <(sort file1.csv) <(sort file2.csv)
# 比较两个文件的差异
diff <(sort file1.txt) <(sort file2.txt)
代码分析
# 统计代码行数(排除空行和注释)
grep -v '^\s*$\|^\s*#' script.sh | wc -l
# 查找 TODO 注释
grep -rn "TODO" ./src/
# 统计每个作者的提交数
git log --format='%an' | sort | uniq -c | sort -rn
小结
本章介绍了 Shell 中最常用的文本处理工具:
- grep:文本搜索,支持正则表达式
- sed:流编辑器,用于文本替换和转换
- awk:强大的文本处理语言,适合结构化数据处理
- cut:字段提取
- sort:排序
- uniq:去重
- tr:字符转换
- wc:统计
这些工具组合使用可以完成几乎所有的文本处理任务。下一章将学习 Shell 的高级特性,包括输入输出重定向、进程替换、信号处理等。