在Unix/Linux系统中,grep、sed和awk被称为"文本三剑客",是Shell脚本中处理文本数据最强大的三个工具。它们各自有独特的功能,又能相互配合使用,是每个Shell程序员必须掌握的核心技能。本章将详细讲解这三个工具的用法,帮助你成为文本处理的高手。
7.1 grep/egrep:文本搜索与过滤
grep(Global Regular Expression Print)是Unix系统中最重要的文本搜索工具之一,用于在文件或输出中查找匹配特定模式的行。
7.1.1 grep基本用法
bash
# 基本语法
grep [选项] 模式 [文件...]
# 在文件中搜索包含"hello"的行
grep "hello" file.txt
# 在多个文件中搜索
grep "hello" file1.txt file2.txt
# 搜索当前目录下的所有文件
grep "hello" *
# 递归搜索目录
grep -r "hello" /path/to/directory
7.1.2 常用选项
| 选项 | 描述 |
|---|---|
-i |
忽略大小写 |
-v |
反向匹配,显示不包含模式的行 |
-n |
显示行号 |
-c |
统计匹配的行数 |
-l |
只显示包含匹配的文件名 |
-L |
显示不包含匹配的文件名 |
-w |
匹配整个单词 |
-x |
匹配整行 |
-r |
递归搜索目录 |
-A |
显示匹配行后的n行 |
-B |
显示匹配行前的n行 |
-C |
显示匹配行前后的n行 |
bash
# 忽略大小写搜索
grep -i "hello" file.txt
# 显示不包含"hello"的行
grep -v "hello" file.txt
# 显示行号
grep -n "hello" file.txt
# 统计匹配行数
grep -c "hello" file.txt
# 只显示文件名
grep -l "hello" *.txt
# 匹配整个单词
grep -w "hello" file.txt
# 显示匹配行及后3行
grep -A 3 "hello" file.txt
# 显示匹配行及前3行
grep -B 3 "hello" file.txt
# 显示匹配行及前后各3行
grep -C 3 "hello" file.txt
7.1.3 基本正则表达式
grep支持基本正则表达式(Basic Regular Expression, BRE):
bash
# ^ 行首匹配
grep "^hello" file.txt # 匹配以hello开头的行
# $ 行尾匹配
grep "hello$" file.txt # 匹配以hello结尾的行
# . 匹配任意单个字符
grep "h.llo" file.txt # 匹配h和llo之间有一个字符的词
# * 匹配前一个字符零次或多次
grep "he*llo" file.txt # 匹配h后跟零个或多个e,再跟llo
# [] 字符类
grep "[hH]ello" file.txt # 匹配hello或Hello
grep "[0-9]" file.txt # 匹配包含数字的行
grep "[a-zA-Z]" file.txt # 匹配包含字母的行
# [^] 否定字符类
grep "[^0-9]" file.txt # 匹配不含数字的行
# \ 转义特殊字符
grep "\." file.txt # 匹配包含句点的行
# \{n\} 匹配n次
grep "he\{2\}llo" file.txt # 匹配hello(e出现2次)
7.1.4 egrep扩展正则表达式
egrep(或者grep -E)支持扩展正则表达式(Extended Regular Expression, ERE),无需转义即可使用更多元字符:
bash
# + 匹配前一个字符一次或多次
grep -E "he+llo" file.txt # 匹配helo、hello、heello等
# ? 匹配前一个字符零次或一次
grep -E "he?llo" file.txt # 匹配hllo或hello
# {} 匹配指定次数
grep -E "he{2,4}llo" file.txt # 匹配heeello、heeello、heeeello
# | 或运算符
grep -E "hello|world" file.txt # 匹配包含hello或world的行
# () 分组
grep -E "(hello)+" file.txt # 匹配hello、hellohello等
# \b 单词边界
grep -E "\bhello\b" file.txt # 匹配完整的单词hello
7.1.5 grep实战示例
bash
# 1. 搜索日志中的错误
grep -i "error" /var/log/syslog
# 2. 查找特定进程
ps aux | grep nginx
# 3. 查找特定用户的所有进程
ps -u username | grep -v grep
# 4. 统计空行数量
grep -c "^$" file.txt
# 5. 查找非注释行(以#开头的行)
grep -v "^#" file.conf
# 6. 查找IP地址
grep -E "([0-9]{1,3}\.){3}[0-9]{1,3}" file.txt
# 7. 查找邮箱地址
grep -E "[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}" file.txt
# 8. 查找电话号码(中国手机号)
grep -E "1[3-9][0-9]{9}" file.txt
# 9. 查找包含数字的行
grep -E "[0-9]" file.txt
# 10. 查找以特定字符串开头的行
grep -E "^#include" *.h
7.1.6 grep与管道结合
grep最强大的用法之一是与管道结合,过滤其他命令的输出:
bash
# 1. 查找当前目录下的目录
ls -l | grep "^d"
# 2. 查找最大的文件
ls -lS | grep "^-" | head -1
# 3. 查找正在运行的Java进程
ps aux | grep java | grep -v grep
# 4. 查找特定端口的进程
netstat -tuln | grep ":80 "
# 5. 查找特定用户的文件
find /home -user username | grep "\.txt$"
# 6. 从命令输出中提取信息
df -h | grep "/dev/sda"
# 7. 统计特定类型的文件数量
find . -type f | grep "\.sh$" | wc -l
# 8. 查找错误日志
dmesg | grep -i error
# 9. 查找最近修改的文件
ls -lt | head -20 | grep "\.txt"
# 10. 从JSON输出中提取特定字段
echo '{"name": "test", "value": 123}' | grep -o '"name"'
7.2 sed:流编辑器
sed(Stream Editor)是一个强大的非交互式文本编辑器,它逐行处理输入,并将结果输出到标准输出。sed特别擅长进行文本替换、删除、插入等操作。
7.2.1 sed基本用法
bash
# 基本语法
sed [选项] '命令' [文件...]
# 输出文件的第5行
sed -n '5p' file.txt
# 输出文件的第5-10行
sed -n '5,10p' file.txt
# 删除文件的第5行
sed '5d' file.txt
# 删除空行
sed '/^$/d' file.txt
7.2.2 常用选项
| 选项 | 描述 |
|---|---|
-n |
静默模式,不自动打印行 |
-e |
指定多个编辑命令 |
-f |
从文件读取编辑命令 |
-i |
直接修改文件(原地编辑) |
-r |
使用扩展正则表达式 |
7.2.3 替换命令
sed最常用的功能是进行文本替换:
bash
# 基本替换:s/pattern/replacement/
sed 's/hello/world/' file.txt # 替换每行第一个hello为world
sed 's/hello/world/g' file.txt # 替换所有hello为world
sed 's/hello/world/2' file.txt # 替换每行第二个hello为world
# 替换特定行
sed '5s/hello/world/' file.txt # 替换第5行的hello
sed '1,10s/hello/world/' file.txt # 替换1-10行的hello
# 使用分隔符
sed 's|hello|world|' file.txt # 使用|作为分隔符
sed 's#hello#world#' file.txt # 使用#作为分隔符
# 正则表达式替换
sed 's/[0-9]/*/g' file.txt # 将所有数字替换为*
sed 's/^/#/' file.txt # 在行首添加#
sed 's/$/:/' file.txt # 在行尾添加:
# 扩展正则表达式
sed -r 's/[0-9]+/*/g' file.txt # 使用扩展正则
sed -r 's/(hello)/\1world/' file.txt # 使用捕获组
7.2.4 删除命令
bash
# 删除行
sed '5d' file.txt # 删除第5行
sed '1,5d' file.txt # 删除1-5行
sed '/pattern/d' file.txt # 删除匹配pattern的行
sed '/^$/d' file.txt # 删除空行
sed '/^#/d' file.txt # 删除注释行
sed '/^$/d; /^#/d' file.txt # 删除空行和注释行
# 删除特定字符
sed 's/[aeiou]//g' file.txt # 删除所有元音字母
sed 's/ //g' file.txt # 删除所有空格
sed 's/\t//g' file.txt # 删除所有制表符
7.2.5 插入命令
bash
# 在匹配行前插入
sed '/pattern/i\新行内容' file.txt
# 在匹配行后插入
sed '/pattern/a\新行内容' file.txt
# 在文件开头插入
sed '1i\新行内容' file.txt
# 在文件末尾追加
sed '$a\新行内容' file.txt
# 示例
sed '/^#include/i\#define VERSION 1' file.c
sed '/^main/a\ return 0;' file.c
7.2.6 多命令组合
bash
# 使用 -e 指定多个命令
sed -e 's/hello/world/' -e 's/foo/bar/' file.txt
# 使用分号分隔
sed 's/hello/world/; s/foo/bar/' file.txt
# 从文件读取命令
sed -f commands.sed file.txt
7.2.7 sed实战示例
bash
# 1. 替换文件中的所有数字为#
sed 's/[0-9]/#/g' file.txt
# 2. 删除所有空行
sed '/^$/d' file.txt
# 3. 在每行行首添加行号
sed = file.txt | sed 'N;s/\n/\t/'
# 4. 将Windows换行符替换为Unix换行符
sed -i 's/\r$//' file.txt
# 5. 转换大小写(大写)
sed 's/[a-z]/\U&/g' file.txt # GNU sed
sed 'y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/' file.txt
# 6. 转换大小写(小写)
sed 's/[A-Z]/\L&/g' file.txt # GNU sed
# 7. 反转行顺序
sed '1!G;h;$!d' file.txt
# 8. 显示特定范围的行
sed -n '10,20p' file.txt
# 9. 删除行首空格
sed 's/^[ ]*//' file.txt
# 10. 在匹配模式的行前后添加空行
sed '/pattern/{ p; s/.*/\n/; }' file.txt
# 11. 提取IP地址
ifconfig | grep "inet " | sed 's/.*inet \([0-9.]*\).*/\1/'
# 12. 提取邮箱
grep -oE '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}' file.txt | sed 's/^/mailto:/'
# 13. 批量重命名文件
for f in *.txt; do mv "$f" "$(echo $f | sed 's/txt/log/')"; done
# 14. 在文件特定位置插入内容
sed '10i\这是第10行前插入的内容' file.txt
# 15. 原地编辑文件
sed -i 's/hello/world/g' file.txt
7.3 awk:文本分析
awk是一种强大的文本分析工具,它不仅仅是简单的文本搜索,而是可以执行更复杂的文本处理和数据提取任务。awk以行为单位处理输入,并将每行分割成字段进行处理。
7.3.1 awk基本用法
bash
# 基本语法
awk 'pattern {action}' [文件...]
# 打印所有行
awk '{print}' file.txt
# 打印第一列
awk '{print $1}' file.txt
# 使用分隔符
awk -F',' '{print $1}' file.csv # 使用逗号作为分隔符
awk -F: '{print $1}' /etc/passwd # 使用冒号作为分隔符
7.3.2 内置变量
awk提供了许多内置变量,方便文本处理:
| 变量 | 描述 |
|---|---|
$0 |
当前行的全部内容 |
$1, $2, ... |
第1, 2, ...个字段 |
NF |
字段总数(Number of Fields) |
NR |
当前记录号(Number of Record) |
FNR |
当前文件的记录号 |
FS |
字段分隔符(Field Separator) |
RS |
记录分隔符(Record Separator) |
OFS |
输出字段分隔符(Output Field Separator) |
ORS |
输出记录分隔符(Output Record Separator) |
bash
# 打印每行的第一个字段
awk '{print $1}' file.txt
# 打印每行的行号和内容
awk '{print NR, $0}' file.txt
# 打印每行的字段数
awk '{print NF}' file.txt
# 打印最后一列
awk '{print $NF}' file.txt
# 打印倒数第二列
awk '{print $(NF-1)}' file.txt
7.3.3 模式匹配
awk支持使用模式来筛选行:
bash
# 打印包含特定模式的行
awk '/pattern/' file.txt
# 打印第一个字段匹配模式的行
awk '$1 ~ /pattern/' file.txt
# 打印第一个字段不匹配模式的行
awk '$1 !~ /pattern/' file.txt
# 数值比较
awk '$3 > 100' file.txt
# 多条件组合
awk '/pattern1/ && /pattern2/' file.txt
awk '/pattern1/ || /pattern2/' file.txt
7.3.4 BEGIN和END块
awk还支持BEGIN和END块,分别在处理开始前和处理结束后执行:
bash
# 打印表头和表尾
awk 'BEGIN {print "姓名\t年龄\t分数"}
{print $1"\t"$2"\t"$3}
END {print "---结束---"}' file.txt
# 计算总和
awk 'BEGIN {sum=0} {sum+=$3} END {print "总和:", sum}' file.txt
7.3.5 条件语句
awk支持完整的条件语句:
bash
# if-else语句
awk '{if ($3 > 60) print $1" 通过"; else print $1" 不通过"}' file.txt
# 三元运算符
awk '{print ($3 >= 60) ? "通过" : "不通过"}' file.txt
7.3.6 循环和数组
awk支持循环和数组:
bash
# 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 '{arr[$1]++} END {for (k in arr) print k, arr[k]}' file.txt
7.3.7 函数
awk提供了许多内置函数:
bash
# 字符串函数
awk '{print length($0)}' file.txt # 长度
awk '{print toupper($1)}' file.txt # 转大写
awk '{print tolower($1)}' file.txt # 转小写
awk '{print substr($1, 1, 5)}' file.txt # 截取子串
awk '{print index($1, "test")}' file.txt # 查找位置
awk '{print gsub(/a/, "b", $1)}' file.txt # 全局替换
# 数学函数
awk 'BEGIN {print sqrt(16)}' # 平方根
awk 'BEGIN {print rand()}' # 随机数
awk 'BEGIN {print int(3.14)}' # 取整
# 自定义函数
awk 'function double(x) {return x*2} {print double($1)}' file.txt
7.3.8 awk实战示例
bash
# 1. 计算文件行数
awk 'END {print NR}' file.txt
# 2. 计算某个字段的总和
awk '{sum+=$3} END {print sum}' file.txt
# 3. 计算某个字段的平均值
awk '{sum+=$3} END {print sum/NR}' file.txt
# 4. 找出最大值和最小值
awk 'BEGIN {max=-999999} {if ($3>max) max=$3} END {print "最大值:", max}' file.txt
# 5. 统计每个唯一值出现的次数
awk '{count[$1]++} END {for (item in count) print item, count[item]}' file.txt
# 6. 格式化输出
awk 'BEGIN {printf "%-10s %5s\n", "姓名", "分数"} {printf "%-10s %5d\n", $1, $3}' file.txt
# 7. 处理CSV文件
awk -F',' '{print $1","$2","$3}' data.csv
# 8. 条件筛选
awk '$3 > 80 {print $1, $3}' file.txt
# 9. 多字段排序
awk '{print $3, $1, $2}' file.txt | sort
# 10. 提取IP地址
ifconfig | awk '/inet / {print $2}'
# 11. 统计CPU使用率
ps aux | awk 'NR>1 {cpu+=$3} END {print cpu}'
# 12. 统计内存使用
free -m | awk 'NR==2 {print "已用: "$3"MB, 可用: "$4"MB"}'
# 13. 分析日志
awk '/Error/ {count++} END {print "错误数量:", count}' error.log
# 14. 提取特定列
awk -F: '{print $1, $6}' /etc/passwd
# 15. 数据统计报表
awk -F, '{
month[$1]++
sales[$1]+=$3
}
END {
printf "%-10s %-10s %-10s\n", "月份", "订单数", "销售额"
printf "%-10s %-10s %-10s\n", "------", "------", "------"
for (m in month) {
printf "%-10s %-10d %-10.2f\n", m, month[m], sales[m]
}
}' sales.csv
7.4 三剑客组合使用
grep、sed和awk可以单独使用,也可以组合起来完成更复杂的任务。
7.4.1 管道组合
bash
# grep + sed:先过滤,再替换
cat file.txt | grep "error" | sed 's/error/ERROR/g'
# grep + awk:先过滤,再提取特定字段
ps aux | grep nginx | awk '{print $2}'
# sed + awk:先替换,再统计
sed 's/,/\t/g' data.csv | awk '{sum+=$3} END {print sum}'
# grep + sed + awk:组合使用
cat log.txt | grep "ERROR" | sed 's/\[//g' | awk '{print $1, $2}'
7.4.2 实际应用案例
bash
# 1. 统计项目中代码行数
find . -name "*.sh" -exec wc -l {} + | awk '{total+=$1} END {print "总行数:", total}'
# 2. 分析Web服务器日志
awk '/\[.*\]/ {print $1, $4, $6}' access.log | \
sed 's/\[//g; s/\]//g' | \
awk '{ip[$1]++; page[$4]++}
END {
print "最常访问的IP:"
for (i in ip) print i, ip[i]
print "\n最常访问的页面:"
for (p in page) print p, page[p]
}'
# 3. 处理CSV数据
cat data.csv | \
grep -v "^#" | \
sed 's/ //g' | \
awk -F, '{
if ($3 > 1000) {
print $1","$2","$3
sum+= $3
count++
}
}
END {
print "---"
print "总数:", count
print "总和:", sum
print "平均:", sum/count
}'
# 4. 批量修改文件
for file in *.txt; do
sed -i 's/old/new/g' "$file"
done
# 5. 提取并排序唯一值
cat file.txt | \
grep -oE "[0-9]+" | \
sort -n | \
uniq -c | \
awk '{print $2, $1}'
# 6. 日志分析脚本
#!/bin/bash
LOG_FILE=$1
echo "=== 日志分析报告 ==="
echo ""
echo "总请求数:"
wc -l < $LOG_FILE
echo ""
echo "错误请求数:"
grep -c " 500 " $LOG_FILE
echo ""
echo "按状态码统计:"
grep -oE " [0-9]{3} " $LOG_FILE | sort | uniq -c | sort -rn
echo ""
echo "最常访问的IP:"
awk '{print $1}' $LOG_FILE | sort | uniq -c | sort -rn | head -10
echo ""
echo "最常访问的URL:"
awk '{print $7}' $LOG_FILE | sort | uniq -c | sort -rn | head -10
7.5 综合示例
7.5.1 日志分析工具
bash
#!/bin/bash
# 日志分析工具
if [ $# -eq 0 ]; then
echo "用法: $0 <日志文件>"
exit 1
fi
LOG_FILE=$1
if [ ! -f "$LOG_FILE" ]; then
echo "错误: 文件不存在"
exit 1
fi
echo "============================================="
echo " 日志分析报告"
echo "============================================="
echo ""
# 1. 基本统计
echo "【基本统计】"
echo "总记录数: $(wc -l < $LOG_FILE)"
echo "文件大小: $(du -h $LOG_FILE | awk '{print $1}')"
echo ""
# 2. 时间范围
echo "【时间范围】"
echo "开始时间: $(head -1 $LOG_FILE | awk '{print $4}')"
echo "结束时间: $(tail -1 $LOG_FILE | awk '{print $4}')"
echo ""
# 3. HTTP状态码统计
echo "【HTTP状态码统计】"
grep -oE " [0-9]{3} " "$LOG_FILE" | \
sort | \
uniq -c | \
sort -rn | \
awk '{
status=$2; count=$1
if (status==200) desc="OK"
else if (status==301) desc="重定向"
else if (status==302) desc="临时重定向"
else if (status==404) desc="未找到"
else if (status==500) desc="服务器错误"
else desc="其他"
printf " %s %s: %d次\n", status, desc, count
}'
echo ""
# 4. 访问最多的IP
echo "【访问最多的IP Top 10】"
awk '{print $1}' "$LOG_FILE" | \
sort | \
uniq -c | \
sort -rn | \
head -10 | \
awk '{printf " %s: %d次\n", $2, $1}'
echo ""
# 5. 最常访问的页面
echo "【最常访问的页面 Top 10】"
awk '{print $7}' "$LOG_FILE" | \
sort | \
uniq -c | \
sort -rn | \
head -10 | \
awk '{printf " %s: %d次\n", $2, $1}'
echo ""
# 6. 错误分析
echo "【错误分析】"
ERROR_COUNT=$(grep -c " 500 " "$LOG_FILE")
404_COUNT=$(grep -c " 404 " "$LOG_FILE")
echo " 500错误: $ERROR_COUNT次"
echo " 404错误: $404_COUNT次"
echo ""
# 7. 按小时统计访问量
echo "【按小时统计访问量】"
awk '{print $4}' "$LOG_FILE" | \
sed 's/:/ /' | \
awk '{print $2}' | \
sort | \
uniq -c | \
sort -n | \
awk '{printf " %02d:00 - %02d:00: %d次\n", $1, $1+1, $2}'
echo ""
echo "============================================="
echo " 分析完成"
echo "============================================="
7.5.2 CSV数据处理工具
bash
#!/bin/bash
# CSV数据处理工具
# 示例CSV文件格式: 姓名,年龄,城市,销售额
# 文件名: sales.csv
CSV_FILE="sales.csv"
echo "=== CSV数据分析 ==="
echo ""
# 1. 显示数据
echo "【原始数据】"
cat "$CSV_FILE"
echo ""
# 2. 计算总销售额
echo "【总销售额】"
awk -F, 'NR>1 {sum+=$4} END {print "总销售额: " sum}' "$CSV_FILE"
echo ""
# 3. 按城市统计
echo "【按城市统计】"
awk -F, 'NR>1 {city[$3]++; sales[$3]+=$4}
END {
printf "%-10s %-10s %-10s\n", "城市", "人数", "销售额"
printf "%s\n", "----------------"
for (c in city) {
printf "%-10s %-10d %-10.2f\n", c, city[c], sales[c]
}
}' "$CSV_FILE"
echo ""
# 4. 销售额Top 5
echo "【销售额Top 5】"
awk -F, 'NR>1 {arr[$1]=$4}
END {
n=asort(arr, sorted)
for (i=n; i>n-5 && i>0; i--) {
for (name in arr) {
if (arr[name]==sorted[i]) {
printf " %s: %.2f\n", name, arr[name]
delete arr[name]
break
}
}
}
}' "$CSV_FILE"
echo ""
# 5. 统计分析
echo "【统计分析】"
awk -F, 'NR>1 {
count++
sum+=$4
if (count==1) {min=$4; max=$4}
if ($4<min) min=$4
if ($4>max) max=$4
}
END {
printf " 记录数: %d\n", count
printf " 平均值: %.2f\n", sum/count
printf " 最大值: %.2f\n", max
printf " 最小值: %.2f\n", min
}' "$CSV_FILE"
echo ""
# 6. 按年龄分组统计
echo "【按年龄分组统计】"
awk -F, 'NR>1 {
if ($2<20) group="20岁以下"
else if ($2<30) group="20-29岁"
else if ($2<40) group="30-39岁"
else group="40岁以上"
count[group]++
sales[group]+=$4
}
END {
for (g in count) {
printf " %s: %d人, 销售额%.2f\n", g, count[g], sales[g]
}
}' "$CSV_FILE"
7.6 本章小结
本章详细介绍了Shell编程中最重要的三个文本处理工具------"文本三剑客":
-
grep/egrep:
- 基本用法和常用选项
- 基本正则表达式(BRE)
- 扩展正则表达式(ERE)
- 实战示例和管道组合
-
sed:
- 基本语法和常用选项
- 替换命令(s命令)
- 删除命令(d命令)
- 插入命令(i/a命令)
- 多命令组合
-
awk:
- 基本语法和内置变量
- 模式匹配
- BEGIN和END块
- 条件语句、循环和数组
- 内置函数和自定义函数
-
组合使用:
- 管道组合多个工具
- 实际应用案例
- 日志分析工具
- CSV数据处理工具
掌握这三个工具的使用,将大大提升你处理文本数据的能力,是编写高效Shell脚本的必备技能。
更多内容,欢迎访问南徽玉的个人博客