02-文本处理三剑客:grep、sed、awk
Linux文本处理三剑客:grep查找、sed编辑、awk分析。掌握这三个工具可解决90%的文本处理需求。
grep - 文本搜索
grep(Global Regular Expression Print)用于搜索文本,支持正则表达式。
基本用法
# 基本搜索
grep "pattern" file.txt
# 忽略大小写
grep -i "pattern" file.txt
# 显示行号
grep -n "pattern" file.txt
# 反向匹配(不包含)
grep -v "pattern" file.txt
# 递归搜索目录
grep -r "pattern" /path/to/dir
# 只显示文件名
grep -l "pattern" *.txt
# 显示匹配行的上下文
grep -C 3 "pattern" file.txt # 前后各3行
grep -A 2 "pattern" file.txt # 后2行
grep -B 2 "pattern" file.txt # 前2行
# 统计匹配行数
grep -c "pattern" file.txt
# 精确匹配整行
grep -x "exact line" file.txt
# 匹配整个单词
grep -w "word" file.txt
正则表达式
# 基本正则表达式(BRE)
grep "^start" file.txt # 行首
grep "end$" file.txt # 行尾
grep "^$" file.txt # 空行
grep "." file.txt # 任意字符
grep "a*" file.txt # 0个或多个a
grep "[abc]" file.txt # a、b或c
grep "[^abc]" file.txt # 非a、b、c
grep "[0-9]" file.txt # 数字
grep "[a-z]" file.txt # 小写字母
# 扩展正则表达式(ERE,-E)
grep -E "a+" file.txt # 1个或多个a
grep -E "a?" file.txt # 0个或1个a
grep -E "a{3}" file.txt # 恰好3个a
grep -E "a{2,5}" file.txt # 2-5个a
grep -E "a{2,}" file.txt # 至少2个a
grep -E "ab|cd" file.txt # ab或cd
grep -E "(ab)+" file.txt # 1个或多个ab
# Perl正则(-P,功能最强)
grep -P "\d+" file.txt # 数字
grep -P "\w+" file.txt # 字母数字下划线
grep -P "\s+" file.txt # 空白字符
grep -P "(?<=@)\w+" file.txt # 正向后查找
实战案例
# 查找IP地址
grep -E "\b([0-9]{1,3}\.){3}[0-9]{1,3}\b" file.txt
# 查找邮箱
grep -E "\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b" file.txt
# 查找URL
grep -E "https?://[^\s]+" file.txt
# 查找ERROR日志
grep -i "error" /var/log/syslog
# 排除注释行和空行
grep -v "^#" file.txt | grep -v "^$"
# 多模式搜索
grep -e "pattern1" -e "pattern2" file.txt
grep -E "pattern1|pattern2" file.txt
# 从文件读取模式
grep -f patterns.txt file.txt
# 统计代码行(排除空行和注释)
grep -v "^$" file.c | grep -v "^\s*//" | wc -l
# 查找进程
ps aux | grep nginx | grep -v grep
# 彩色高亮(默认)
grep --color=auto "pattern" file.txt
grep变体
# egrep = grep -E(扩展正则)
egrep "a+" file.txt
# fgrep = grep -F(固定字符串,不解析正则)
fgrep "a*b" file.txt # 搜索字面量 "a*b"
# zgrep(搜索压缩文件)
zgrep "pattern" file.gz
sed - 流编辑器
sed(Stream Editor)逐行处理文本,支持查找、替换、删除、插入。
基本语法
sed [options] 'command' file
# -n:静默模式(只打印处理的行)
# -e:多个编辑命令
# -i:原地编辑(修改文件)
# -r/-E:扩展正则
替换(s命令)
# 基本替换(只替换每行第一个)
sed 's/old/new/' file.txt
# 全局替换(每行所有匹配)
sed 's/old/new/g' file.txt
# 替换第N个匹配
sed 's/old/new/2' file.txt # 第2个
# 忽略大小写
sed 's/old/new/gi' file.txt
# 替换特定行
sed '3s/old/new/' file.txt # 第3行
sed '2,5s/old/new/' file.txt # 2-5行
sed '2,$s/old/new/' file.txt # 2到末尾
sed '/pattern/s/old/new/' file.txt # 匹配pattern的行
# 原地修改文件
sed -i 's/old/new/g' file.txt
# 备份后修改
sed -i.bak 's/old/new/g' file.txt
# 分隔符替换(避免转义/)
sed 's|/usr/bin|/usr/local/bin|g' file.txt
sed 's#old#new#g' file.txt
# 使用正则
sed -E 's/[0-9]+/NUMBER/g' file.txt
# 引用匹配内容(&)
sed 's/[0-9]\+/"&"/g' file.txt # 123 → "123"
# 引用分组(\1, \2...)
sed -E 's/([a-z]+)@([a-z]+)/\2@\1/' file.txt # user@host → host@user
删除(d命令)
# 删除特定行
sed '3d' file.txt # 第3行
sed '2,5d' file.txt # 2-5行
sed '2,$d' file.txt # 2到末尾
sed '$d' file.txt # 最后一行
# 删除匹配行
sed '/pattern/d' file.txt
# 删除空行
sed '/^$/d' file.txt
# 删除注释行
sed '/^#/d' file.txt
# 删除空行和注释
sed '/^$/d; /^#/d' file.txt
插入和追加
# a:在后面追加
sed '2a\new line' file.txt # 第2行后
sed '/pattern/a\new line' file.txt # 匹配行后
# i:在前面插入
sed '2i\new line' file.txt # 第2行前
sed '/pattern/i\new line' file.txt # 匹配行前
# c:替换整行
sed '2c\new line' file.txt
sed '/pattern/c\new line' file.txt
打印(p命令)
# 打印特定行(-n静默模式)
sed -n '3p' file.txt # 第3行
sed -n '2,5p' file.txt # 2-5行
sed -n '/pattern/p' file.txt # 匹配行
sed -n '$p' file.txt # 最后一行
# 打印并删除
sed -n '/pattern/p; /pattern/d' file.txt
多命令和分支
# -e 多命令
sed -e 's/old/new/g' -e '/pattern/d' file.txt
# ; 分隔
sed 's/old/new/g; /pattern/d' file.txt
# 脚本文件
sed -f script.sed file.txt
# script.sed 内容:
# s/old/new/g
# /pattern/d
实战案例
# 配置文件修改
sed -i 's/^#Port 22/Port 2222/' /etc/ssh/sshd_config
# 添加配置
sed -i '/\[mysqld\]/a\max_connections=1000' /etc/my.cnf
# 替换变量
sed "s/@@VERSION@@/$VERSION/g" template.txt > output.txt
# 删除行尾空格
sed -i 's/[[:space:]]*$//' file.txt
# 删除Windows换行符
sed -i 's/\r$//' file.txt
# 在每行前加行号
sed = file.txt | sed 'N;s/\n/\t/'
# 大小写转换
echo "Hello" | sed 'y/a-z/A-Z/' # HELLO
# 提取IP地址
ifconfig | sed -n '/inet /p' | sed 's/.*inet \([0-9.]*\).*/\1/'
# 批量重命名
for f in *.txt; do
mv "$f" "$(echo $f | sed 's/\.txt$/.bak/')"
done
# HTML转义
sed 's/&/\&/g; s/</\</g; s/>/\>/g' file.html
sed高级技巧
# 保持空间(h、H、g、G、x)
# h:复制模式空间到保持空间
# g:复制保持空间到模式空间
# x:交换
# 倒序输出(tac的实现)
sed '1!G;h;$!d' file.txt
# 删除重复行(uniq的实现)
sed '$!N; /^\(.*\)\n\1$/!P; D'
# 每两行合并
sed 'N;s/\n/ /' file.txt
awk - 文本分析
awk是强大的文本分析工具,支持变量、数组、函数,类似小型编程语言。
基本语法
awk 'pattern {action}' file
# 内置变量
$0 # 整行
$1-$NF # 第1到最后一列
NF # 列数(字段数)
NR # 行号(总)
FNR # 行号(当前文件)
FS # 输入分隔符(默认空格)
OFS # 输出分隔符(默认空格)
RS # 行分隔符(默认换行)
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 $1 "\t" $3}' file.txt # 制表符分隔
awk '{print $NF}' file.txt # 最后一列
awk '{print $(NF-1)}' file.txt # 倒数第2列
# 打印行号
awk '{print NR, $0}' file.txt
# 自定义分隔符
awk -F: '{print $1}' /etc/passwd # 冒号分隔
awk -F'[,:]' '{print $1}' file.txt # 多个分隔符
# 输出分隔符
awk 'BEGIN{OFS=","} {print $1, $2}' file.txt
# 条件打印
awk '$3 > 100' file.txt # 第3列>100
awk '/pattern/' file.txt # 包含pattern
awk '!/pattern/' file.txt # 不包含
awk 'NR==5' file.txt # 第5行
awk 'NR>=2 && NR<=5' file.txt # 2-5行
模式匹配
# 正则匹配
awk '/^[0-9]/' file.txt # 数字开头
awk '/error/i' file.txt # 忽略大小写
awk '$1 ~ /^a/' file.txt # 第1列以a开头
awk '$1 !~ /^a/' file.txt # 第1列不以a开头
# 范围匹配
awk '/start/,/end/' file.txt # start到end之间
# 逻辑运算
awk '$1 == "root"' /etc/passwd
awk '$3 > 100 && $4 < 200' file.txt
awk '$1 == "Alice" || $1 == "Bob"' file.txt
BEGIN和END
# BEGIN:处理前执行
awk 'BEGIN {print "Name\tAge"} {print}' file.txt
# END:处理后执行
awk '{sum += $1} END {print "Total:", sum}' file.txt
# 组合使用
awk 'BEGIN {print "Start"} {print} END {print "End"}' file.txt
运算和变量
# 算术运算
awk '{print $1 + $2}' file.txt
awk '{print $1 * 2}' file.txt
awk '{sum += $1} END {print sum}' file.txt
# 变量
awk '{count++} END {print count}' file.txt
awk 'BEGIN {total=0} {total += $1} END {print total/NR}' file.txt
# 数组
awk '{arr[$1]++} END {for (i in arr) print i, arr[i]}' file.txt
# 统计
awk '{sum += $1; sumsq += $1*$1}
END {print "Avg:", sum/NR, "StdDev:", sqrt(sumsq/NR - (sum/NR)^2)}' file.txt
格式化输出
# printf
awk '{printf "%s\t%d\n", $1, $2}' file.txt
awk '{printf "%-10s %5d\n", $1, $2}' file.txt # 左对齐、右对齐
awk '{printf "%.2f\n", $1}' file.txt # 保留2位小数
条件和循环
# if语句
awk '{if ($3 > 100) print $1}' file.txt
awk '{if ($1 == "Alice") print $0; else print "Not Alice"}' 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
函数
# 字符串函数
length($1) # 长度
substr($1, 1, 3) # 截取(从1开始,长度3)
index($1, "a") # 查找位置
split($0, arr, ",") # 分割
sub(/old/, "new") # 替换第一个
gsub(/old/, "new") # 替换所有
tolower($1) # 转小写
toupper($1) # 转大写
# 数学函数
int($1) # 取整
sqrt($1) # 平方根
rand() # 随机数[0,1)
srand() # 设置随机种子
# 示例
awk '{print tolower($1)}' file.txt
awk '{print length($1)}' file.txt
实战案例
# 统计文件大小
ls -l | awk '{sum += $5} END {print "Total:", sum/1024/1024, "MB"}'
# 统计日志
awk '{status[$9]++} END {for (s in status) print s, status[s]}' access.log
# 提取第N列
awk -F: '{print $1}' /etc/passwd
# 去重统计
awk '{arr[$1]++} END {for (i in arr) print i, arr[i]}' file.txt
# 计算平均值
awk '{sum += $1} END {print sum/NR}' file.txt
# Top N
awk '{print $1}' file.txt | sort -rn | head -10
# 合并列
awk '{print $1 $2 $3}' file.txt
# 条件求和
awk '$2 == "error" {count++; sum += $3} END {print count, sum}' log.txt
# 多文件处理
awk '{print FILENAME, $0}' file1.txt file2.txt
# 处理CSV
awk -F, '{print $1, $3}' data.csv
# JSON提取(简单)
echo '{"name":"Alice","age":25}' | awk -F'"' '{print $4}'
# 生成SQL
awk '{printf "INSERT INTO users VALUES (\"%s\", %d);\n", $1, $2}' data.txt
# 日志分析:统计状态码
awk '{print $9}' access.log | sort | uniq -c | sort -rn
# 计算第95百分位
awk '{arr[NR]=$1} END {
asort(arr);
idx = int(NR * 0.95);
print "P95:", arr[idx]
}' data.txt
# 过滤时间范围
awk '$1 >= "2023-01-01" && $1 <= "2023-12-31"' log.txt
# 格式化表格
awk 'BEGIN {printf "%-10s %-10s %10s\n", "Name", "Age", "Score"}
{printf "%-10s %-10d %10.2f\n", $1, $2, $3}' data.txt
awk脚本文件
# script.awk
BEGIN {
FS = ","
OFS = "\t"
print "Name\tScore"
}
{
if ($2 >= 60) {
pass++
total += $2
}
}
END {
print "Pass:", pass
print "Average:", total/pass
}
# 执行
awk -f script.awk data.csv
三剑客组合使用
管道组合
# 统计错误日志
grep "ERROR" app.log | awk '{print $1}' | sort | uniq -c
# 提取IP并统计
grep "Failed password" /var/log/auth.log | \
awk '{print $11}' | \
sort | uniq -c | sort -rn
# 日志分析
cat access.log | \
grep "404" | \
awk '{print $7}' | \
sort | uniq -c | \
sort -rn | \
head -10
# 替换后过滤
sed 's/old/new/g' file.txt | grep "new" | awk '{print $1}'
# 多步处理
cat data.txt | \
grep -v "^#" | \ # 删除注释
grep -v "^$" | \ # 删除空行
sed 's/ */ /g' | \ # 多空格转单空格
awk '{print $1, $3}' | \ # 提取列
sort -k2 -n # 按第2列排序
实战脚本
#!/bin/bash
# 分析Nginx访问日志
LOG_FILE="/var/log/nginx/access.log"
echo "=== Top 10 IP ==="
awk '{print $1}' $LOG_FILE | sort | uniq -c | sort -rn | head -10
echo ""
echo "=== Top 10 URL ==="
awk '{print $7}' $LOG_FILE | sort | uniq -c | sort -rn | head -10
echo ""
echo "=== Status Code Statistics ==="
awk '{print $9}' $LOG_FILE | sort | uniq -c | sort -rn
echo ""
echo "=== 4xx and 5xx Errors ==="
awk '$9 ~ /^[45]/ {print $0}' $LOG_FILE | wc -l
echo ""
echo "=== Traffic by Hour ==="
awk '{print substr($4, 14, 2)}' $LOG_FILE | sort | uniq -c
性能对比
任务 |
grep |
sed |
awk |
说明 |
|---|---|---|---|---|
简单搜索 |
⭐⭐⭐ |
⭐ |
⭐ |
grep最快 |
替换 |
✗ |
⭐⭐⭐ |
⭐⭐ |
sed专用 |
列提取 |
✗ |
⭐ |
⭐⭐⭐ |
awk最强 |
统计计算 |
✗ |
✗ |
⭐⭐⭐ |
awk专用 |
复杂逻辑 |
✗ |
⭐ |
⭐⭐⭐ |
awk最灵活 |
最佳实践
选择工具
# 仅搜索:grep
grep "error" app.log
# 简单替换:sed
sed 's/old/new/g' file.txt
# 列处理/统计:awk
awk '{sum += $3} END {print sum}' data.txt
# 复杂处理:awk脚本
awk -f complex.awk data.txt
性能优化
# grep: 使用固定字符串(-F)
grep -F "literal string" file.txt # 比正则快
# sed: 减少正则复杂度
sed 's/[0-9]\+/NUM/g' # 比 's/[0-9][0-9]*/NUM/g' 快
# awk: 提前退出
awk '{if (NR > 1000) exit} {print}' huge.txt
# 避免不必要的管道
awk '/pattern/ {print $1}' file.txt # 优于 grep pattern | awk '{print $1}'
可读性
# 复杂命令分行
grep "ERROR" app.log | \
awk '{print $1, $5}' | \
sort | \
uniq -c
# 注释
awk '
# 统计每个状态码的出现次数
{status[$9]++}
# 输出统计结果
END {for (s in status) print s, status[s]}
' access.log
核心: grep查、sed改、awk析,熟练掌握三剑客是Linux运维的必备技能。