未记录事实核验 · 状态:现行内容

记录仅说明核验时间与维护状态,不保证适用于所有环境;使用前请留意正文中的版本、适用条件与风险提示。

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}'

下一步


info 前置知识


掌握检查

  • [ ] 能区分三者的职责: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 #文本处理 #数据处理 #日志分析