跳到主要内容

文本处理

文本处理是 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 # 匹配空行

扩展正则表达式(使用 -Eegrep

# 匹配前一个字符 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 的高级特性,包括输入输出重定向、进程替换、信号处理等。