未记录事实核验 · 状态:现行内容
记录仅说明核验时间与维护状态,不保证适用于所有环境;使用前请留意正文中的版本、适用条件与风险提示。
warning 适用边界与维护说明
本文适用于Linux命令行下的grep、sed、awk基础练习,部分选项和转义写法按GNU工具习惯使用,不保证其他实现完全兼容。按逗号拆列、去引号和分隔符转换仅适用于无引号包裹逗号、嵌入换行等复杂字段的简化数据;去引号还须确认不会改变字段边界或含义。统计示例假定字段齐全、数值有效,日志须符合示例列布局,响应时间排序另需第7列;四处平均值计算已有无数据保护,不存在旧说明所称的空输入除零问题。sed时间段示例按起止匹配行选取范围,不是完整时间区间过滤,缺少结束匹配时会延伸至文件末尾。ETL输出仅是SQL形状的文本拼接,未限定数据库方言且转义不完整,不可直接入库,实际写入应采用匹配目标数据库的参数化方式。sed -i会修改原文件,应使用副本或备份。本次仅作正文结构审阅,未执行命令或进行外部事实、链接核验。
文本处理三剑客 - grep/sed/awk 数据处理实战
abstract 本文概要
- grep:在文件中搜索匹配行
- sed:流式编辑,替换和转换文本
- awk:按列处理结构化数据(数据处理的利器)
- 重点:数据处理场景(日志分析、CSV 处理、数据清洗)
三剑客各司其职
| 工具 | 擅长 | 一句话描述 |
|---|---|---|
grep |
搜索 | 找出匹配条件的行 |
sed |
替换/转换 | 对每一行进行文本替换 |
awk |
列处理 | 按列提取、计算、汇总数据 |
tip 记忆口诀
- grep:给我找 → 过滤行
- sed:流式替换 → 改文本
- awk:按列算 → 处理结构化数据
grep - 搜索神器
grep(Global Regular Expression Print),在文件中搜索匹配指定模式的行,并输出这些行。
基本用法
# 搜索包含关键词的行
grep "ERROR" app.log
# 搜索多个文件
grep "ERROR" *.log
# 递归搜索目录下所有文件
grep -r "ERROR" /var/log/app/
# 大小写不敏感搜索
grep -i "error" app.log
# 显示行号
grep -n "ERROR" app.log
# 显示匹配行的上下文(-C 同时显示前后各 N 行)
grep -C 3 "NullPointerException" app.log
# 显示匹配行及其前 N 行(-B before)
grep -B 2 "ERROR" app.log
# 显示匹配行及其后 N 行(-A after)
grep -A 2 "ERROR" app.log
反向搜索(不含关键词的行)
# 显示不包含 DEBUG 的行(过滤掉噪音)
grep -v "DEBUG" app.log
# 过滤掉注释行和空行(配置文件查看常用)
grep -v "^#" config.properties | grep -v "^$"
统计匹配数
# 只显示匹配行数
grep -c "ERROR" app.log
# 显示包含匹配的文件名(只显示文件名,不显示内容)
grep -l "ERROR" *.log
# 显示不包含匹配的文件名
grep -L "ERROR" *.log
正则表达式
# ^ 匹配行开头
grep "^2026-03-18" access.log # 找 3 月 18 日的日志
# $ 匹配行结尾
grep "500$" access.log # 找状态码 500 结尾的行
# . 匹配任意单个字符
grep "user.id" data.csv
# .* 匹配任意字符串
grep "ERROR.*NullPointer" app.log # ERROR 后面跟着 NullPointer
# | 表示"或"(需要 -E 参数启用扩展正则)
grep -E "ERROR|WARN|FATAL" app.log
# + 匹配前面字符一次或多次
grep -E "[0-9]+" data.csv
# 匹配 IP 地址(简化版)
grep -E "[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}" access.log
tip 数据工作中的 grep 场景
# 场景 1:快速定位 Spark 任务错误
grep -i "exception\|error\|failed" spark-app.log | head -50
# 场景 2:过滤出特定用户的访问日志
grep "user_id=12345" access.log
# 场景 3:统计 404 和 500 错误数量
grep -c " 404 " access.log
grep -c " 500 " access.log
# 场景 4:查看XML配置文件(仅过滤以#开头的行和空行,不会去除XML注释)
grep -v "^#" /etc/hadoop/core-site.xml | grep -v "^$"
# 场景 5:在所有 Python 脚本中找某个函数
grep -rn "def load_data" /home/alice/scripts/
sed - 流式编辑器
sed(Stream Editor),对文本流进行逐行处理,最常用的功能是替换。
基本替换
# 语法:sed 's/原内容/新内容/' 文件
# s 表示 substitute(替换)
# 替换每行第一个匹配(不修改原文件,结果输出到终端)
sed 's/foo/bar/' data.txt
# 替换每行所有匹配(加 g 参数,global)
sed 's/foo/bar/g' data.txt
# 修改原文件(-i 参数,in-place)
sed -i 's/foo/bar/g' data.txt
# 修改原文件,同时保留备份(-i.bak 参数)
sed -i.bak 's/foo/bar/g' data.txt
# 原文件改好了,同时生成 data.txt.bak 备份
# 大小写不敏感替换(加 I 参数)
sed 's/error/ERROR/gI' data.txt
数据清洗常用操作
# 删除空行
sed '/^$/d' data.csv
# 删除以 # 开头的注释行
sed '/^#/d' config.txt
# 删除行首的空格和 Tab
sed 's/^[[:space:]]*//' data.txt
# 删除行尾的空格和 Tab
sed 's/[[:space:]]*$//' data.txt
# 删除行首和行尾的空白字符(常用于数据清洗)
sed 's/^[[:space:]]*//;s/[[:space:]]*$//' data.txt
# 删除第 N 行(比如删除 CSV 的表头)
sed '1d' data.csv
# 只保留 1-10 行(提取前 10 行数据)
sed -n '1,10p' data.csv
# 只打印匹配的行(等同于 grep)
sed -n '/ERROR/p' app.log
格式转换
# 把 Windows 的换行符(\r\n)转成 Linux 的(\n)
sed 's/\r//' windows_file.csv > linux_file.csv
# 把 CSV 的逗号分隔换成 Tab 分隔(简单情况,字段内有逗号时不适用)
sed 's/,/\t/g' data.csv > data.tsv
# 给每行加前缀
sed 's/^/PREFIX: /' data.txt
# 给每行加后缀
sed 's/$/ END/' data.txt
# 在第 5 行后面插入一行内容
sed '5a\新插入的内容' data.txt
# 替换第 3 行的内容
sed '3c\新的第三行内容' data.txt
note sed 的实际场景
# 场景 1:批量修改配置文件中的 IP 地址
sed -i.bak 's/192\.168\.1\.100/10.0.0.100/g' /etc/hadoop/core-site.xml
# 场景 2:简化逗号分隔文本清洗 - 仅在确认引号不是CSV结构、删除不会改变字段边界或含义时使用
sed 's/"//g' raw_data.csv > clean_data.csv
# 场景 3:演示sed地址范围:从匹配09点的行起,到随后首个匹配10点的行止(含结束行);缺结束匹配时到文件末尾。这不是完整时间区间过滤。
sed -n '/2026-03-18 09:/,/2026-03-18 10:/p' app.log
# 场景 4:把多个空格压缩成一个
sed 's/ */ /g' data.txt
awk - 数据处理利器
awk 是功能最强大的文本处理工具,特别适合处理按列分隔的结构化数据(CSV、日志、TSV 等)。
note awk 的名字
awk 来自三位作者的姓氏首字母:Aho、Weinberger、Kernighan。
awk 基本结构
# 语法:awk '模式 { 动作 }' 文件
# 对文件每一行,先判断是否匹配模式,匹配则执行动作
# 最简单的 awk:输出每一行(和 cat 等效)
awk '{ print }' data.csv
# 输出第 1 列($1 表示第一个字段)
awk '{ print $1 }' data.csv
# 默认分隔符是空白字符(空格或 Tab)
# 指定分隔符用 -F 参数
awk -F',' '{ print $1 }' data.csv # CSV(逗号分隔)
awk -F'\t' '{ print $1 }' data.tsv # TSV(Tab 分隔)
awk 内置变量
# $0 :整行内容
# $1 :第 1 列
# $2 :第 2 列
# $NF :最后一列(NF = Number of Fields)
# $( NF-1 ):倒数第 2 列
# NR :当前行号(Number of Records)
# NF :当前行的列数
# FS :字段分隔符(Field Separator,默认空白)
# OFS :输出分隔符(Output Field Separator)
# 示例
awk -F',' '{ print NR, $1, $NF }' data.csv # 行号、第1列、最后一列
awk -F',' '{ print NF }' data.csv # 每行有多少列
常用模式
# 跳过第 1 行(表头)
awk -F',' 'NR > 1 { print $2 }' data.csv
# 只处理第 5 行
awk 'NR == 5 { print }' data.csv
# 处理第 2 到第 10 行
awk 'NR >= 2 && NR <= 10 { print }' data.csv
# 过滤:第 3 列等于某个值
awk -F',' '$3 == "北京" { print }' users.csv
# 过滤:第 5 列大于 1000
awk -F',' '$5 > 1000 { print }' orders.csv
# 包含关键词(正则匹配)
awk '/ERROR/ { print }' app.log
awk -F',' '$2 ~ /上海|北京/ { print }' data.csv # 第 2 列包含上海或北京
列计算与汇总
# 计算每行第 3 和第 4 列之和
awk -F',' '{ print $1, $3 + $4 }' data.csv
# 求某一列的总和
awk -F',' '{ sum += $5 } END { print "总金额:", sum }' orders.csv
# 求某一列的平均值
awk -F',' 'NR > 1 { sum += $5; count++ } END { if (count > 0) print "平均金额:", sum/count; else print "无数据,无法计算平均金额" }' orders.csv
# 同时求最大值、最小值、总和
awk -F',' 'NR > 1 {
sum += $5
if (count == 0 || $5 > max) max = $5
if (min == "" || $5 < min) min = $5
count++
} END {
print "总和:", sum
print "最大值:", max
print "最小值:", min
if (count > 0) print "平均值:", sum/count
else print "无数据,无法计算平均值"
}' orders.csv
格式化输出
# printf 格式化输出(类似 C 语言)
awk -F',' '{ printf "用户: %-10s 金额: %8.2f\n", $1, $5 }' orders.csv
# 修改分隔符输出(将 CSV 输出为 Tab 分隔)
awk -F',' '{ OFS="\t"; print $1, $2, $3 }' data.csv
# 在特定列之间插入内容
awk -F',' '{ print $1 " - " $2 " (" $3 ")" }' data.csv
BEGIN 和 END 块
# BEGIN 在处理文件前执行,END 在处理完成后执行
awk -F',' '
BEGIN {
print "开始处理数据"
print "============================"
}
NR > 1 {
sum += $5
count++
}
END {
print "============================"
print "处理完成"
print "总记录数:", count
print "总金额:", sum
if (count > 0) printf "平均金额: %.2f\n", sum/count
else print "无数据,无法计算平均金额"
}' orders.csv
三剑客组合实战
实战 1:分析 Web 访问日志
# 日志格式:IP 时间 请求方法 URL 状态码 响应大小
# 例:192.168.1.1 2026-03-18T09:30:00 GET /api/user 200 1024
# 1. 统计各状态码出现次数
awk '{ print $5 }' access.log | sort | uniq -c | sort -rn
# 2. 找出访问量最多的 TOP 10 IP
awk '{ print $1 }' access.log | sort | uniq -c | sort -rn | head -10
# 3. 统计某个时间段的错误请求
grep "2026-03-18T09:" access.log | awk '$5 >= 400 { print $1, $4, $5 }'
# 4. 计算平均响应大小
awk '{ sum += $6; count++ } END { if (count > 0) printf "平均响应大小: %.2f bytes\n", sum/count; else print "无数据,无法计算平均响应大小" }' access.log
# 5. 找出响应时间最慢的 20 个请求(假设第 7 列是响应时间毫秒)
awk '{ print $7, $0 }' access.log | sort -rn | head -20 | awk '{ $1=""; print }'
实战 2:处理 CSV 数据文件
# 假设 orders.csv 格式:
# order_id,user_id,city,product,amount,status
# 1001,U001,北京,iPhone,5999,paid
# 1002,U002,上海,MacBook,12999,paid
# 1. 统计各城市的订单金额总和
awk -F',' 'NR > 1 { city_sum[$3] += $5 } END { for (city in city_sum) print city, city_sum[city] }' orders.csv | sort -k2 -rn
# 2. 筛选金额大于 5000 且状态为 paid 的订单
awk -F',' 'NR > 1 && $5 > 5000 && $6 == "paid" { print }' orders.csv
# 3. 给每行添加一列(税后金额 = 金额 * 1.13)
awk -F',' 'NR == 1 { print $0 ",tax_amount" }
NR > 1 { printf "%s,%.2f\n", $0, $5 * 1.13 }' orders.csv
# 4. 删除重复的 user_id,只保留每个用户金额最大的订单
awk -F',' 'NR > 1 { if (!($2 in max) || $5 > max[$2]) { max[$2]=$5; line[$2]=$0 } }
END { for (uid in line) print line[uid] }' orders.csv
# 5. 找出下单次数最多的用户 TOP 5
awk -F',' 'NR > 1 { count[$2]++ }
END { for (uid in count) print uid, count[uid] }' orders.csv | sort -k2 -rn | head -5
实战 3:ETL 数据转换
# 场景:将日志拼接为SQL形状的文本,仅作格式转换演示;未限定数据库方言且转义不完整,输出不可直接导入数据库
# 输入:error_log.txt
# 格式:2026-03-18 09:30:00 ERROR UserService 用户登录失败 user_id=12345
awk '
{
# 提取字段
datetime = $1 " " $2
level = $3
module = $4
# 后面的内容合并为消息
msg = ""
for (i=5; i<=NF; i++) msg = msg " " $i
# 生成 INSERT SQL
gsub(/"/, "\\\"", msg) # 转义引号
printf "INSERT INTO error_log (datetime, level, module, message) VALUES (\"%s\", \"%s\", \"%s\", \"%s\");\n",
datetime, level, module, msg
}' error_log.txt > import.sql
常用命令组合速查
# 1. 统计文件行数(排除空行)
grep -v "^$" data.csv | wc -l
# 2. 去重并统计(uniq 是三剑客的好伙伴)
awk -F',' '{ print $3 }' data.csv | sort | uniq -c | sort -rn
# 3. 提取第 N 行到第 M 行
sed -n '10,20p' data.csv
# 4. 提取第 N 列(空格分隔)
awk '{ print $3 }' data.txt
# 5. 替换并输出特定列
awk -F',' '{ gsub(/old/, "new", $2); print }' OFS=',' data.csv
# 6. 多文件合并后去重
cat file1.csv file2.csv | sort | uniq > merged_unique.csv
# 7. 实时过滤日志中的错误
tail -f app.log | grep --line-buffered "ERROR\|WARN"
快速参考
| 工具 | 核心用途 | 典型命令 |
|---|---|---|
grep |
搜索匹配行 | grep -i "error" *.log |
grep -v |
过滤掉匹配行 | grep -v "DEBUG" |
grep -c |
统计匹配行数 | grep -c "404" access.log |
sed 's/a/b/g' |
全文替换 | sed -i 's/old/new/g' file |
sed '/^$/d' |
删除空行 | sed '/^$/d' config.txt |
awk '{print $2}' |
提取第 2 列 | awk -F',' '{print $2}' |
awk '{sum+=$5}' |
列求和 | awk '{sum+=$5}END{print sum}' |
下一步
- 07-管道与重定向 - 把三剑客和其他命令串联起来,威力倍增
- 08-Shell脚本基础 - 把三剑客的命令写进脚本,实现自动化
- 14-实战场景 - 更多完整的日志分析和数据处理案例
掌握检查
- [ ] 能区分三者的职责:grep 是过滤行,sed 是替换/删除文本,awk 是按列处理结构化数据
- [ ] 能用
grep -i "error" *.log在多个日志文件中不区分大小写地搜索错误 - [ ] 能用
sed -i 's/旧值/新值/g' file.csv对文件做全局替换(-i直接修改原文件) - [ ] 能用
awk -F',' '{print $1,$3}' data.csv提取 CSV 的第 1 列和第 3 列 - [ ] 能用
awk -F',' '{sum+=$5}END{print sum}' orders.csv对第 5 列数值求总和
实践练习
基础题:有一个日志文件,包含 INFO、WARN、ERROR 三种级别的日志。分别统计三种级别各有多少行(提示:grep -c);然后提取所有 ERROR 行,输出到 errors.log。
进阶题:有一个逗号分隔的 CSV 文件 orders.csv(字段:订单号,用户ID,城市,商品,金额,状态),要求:(1) 用 awk 统计每个城市的订单数量和总金额;(2) 用 sed 把状态字段中的 paid 替换为 completed;(3) 过滤出金额大于 500 的订单保存到新文件。
下一步
#Linux #grep #sed #awk #文本处理 #数据处理 #日志分析