Tools

Linux 文本处理三剑客进阶实战:grep、sed、awk 从入门到精通

✎ -- 字 🕐 -- 分钟
字号

Linux 文本处理三剑客进阶实战:grep、sed、awk 从入门到精通

为什么你需要精通这三把利器

在 Linux 世界里,有一组被无数运维和开发人员奉为经典的文本处理工具——grep 搜索、sed 替换、awk 编程。它们就像瑞士军刀的三个刀片,各自擅长的领域互补而不重叠:

工具核心定位最佳场景一句话理解
grep文本搜索与过滤查找、过滤、统计匹配行"我帮你找"
sed流编辑器批量替换、行级编辑、文本变换"我帮你改"
awk模式驱动编程语言字段提取、报表生成、结构化数据处理"我帮你算"

很多开发者只会基础用法(grep xxx filesed 's/old/new/'awk '{print $1}'),遇到稍复杂的需求就写 Python 脚本——其实 90% 的日常文本处理任务,这三把利器组合起来就够了,而且比 Python 脚本快 10 倍以上。本文从正则表达式精通到实战案例,帮你彻底掌握它们。

一、grep:文本搜索的瑞士军刀

1.1 基础与常用选项速查

grep 的核心功能是在文本流中搜索匹配模式的行。以下是日常高频使用的选项:

# 基础搜索
grep "error" app.log                # 在日志中查找包含 error 的行

# 常用选项组合
grep -i "error" app.log             # 忽略大小写
grep -n "error" app.log             # 显示行号
grep -c "error" app.log             # 只输出匹配行数(计数)
grep -v "debug" app.log             # 反向过滤:排除包含 debug 的行
grep -r "TODO" src/                 # 递归搜索目录
grep -l "password" *.conf           # 只输出包含匹配的文件名
grep -w "fail" app.log              # 全词匹配(fail 不匹配 failure)
grep -A3 "exception" app.log        # 匹配行 + 后 3 行(After)
grep -B2 "exception" app.log        # 匹配行 + 前 2 行(Before)
grep -C2 "exception" app.log        # 匹配行 + 前/后各 2 行(Context)
grep -E "err|fail|warn" app.log     # 扩展正则(相当于 egrep)
grep -P "\d{4}-\d{2}-\d{2}" log     # Perl 正则(支持 \d 等简写)

1.2 正则表达式精通:从 BRE 到 ERE 到 PCRE

grep 支持三种正则引擎,理解它们的差异是写出精准匹配的前提:

引擎触发选项特点典型差异
BRE(基础正则)默认+/?/|/() 需转义\+ 表示"一个或多个"
ERE(扩展正则)-E+/?/|/() 直接用+ 表示"一个或多个"
PCRE(Perl 正则)-P支持 \d/\w、 lookahead 等\d+ 匹配连续数字

实战推荐:日常使用优先选 -E(ERE),语法最直观;需要 \d 等简写或零宽断言时用 -P

# ERE 实战:匹配 HTTP 状态码 4xx 或 5xx
grep -E "HTTP/[0-9.]+ [45][0-9]{2}" access.log

# PCRE 实战:提取 IPv4 地址
grep -Po "\b(\d{1,3}\.){3}\d{1,3}\b" access.log
# -o 只输出匹配部分(而非整行),配合 PCRE 精准提取

# 零宽断言:提取 JSON 字段值(lookbehind + lookahead)
grep -Po '(?<="api_key": ")[^"]+' config.json

1.3 高级用法:多文件、上下文与二进制

# 多模式 OR 搜索(两种写法等效)
grep -E "error|warn|fatal" app.log
grep -e "error" -e "warn" -e "fatal" app.log

# 从标准输入搜索(管道组合)
cat access.log | grep -E "5[0-9]{2}" | grep -v "500"

# 排除二进制文件 + 递归搜索
grep -rI "secret" /etc/     # -I 忽略二进制文件

# 统计每个文件中匹配行数 + 排序
grep -rc "TODO" src/ | sort -t: -k2 -rn | head -10

# fgrep(固定字符串搜索,不做正则解析,更快)
fgrep "exact_string_no_regex" bigfile.log

1.4 常见陷阱

陷阱 1:BRE 下 | 不是"或",() 不是分组——需要 \|\(。初学者最常踩坑。

陷阱 2grep "pattern" *.log 如果没有 .log 文件,grep 会报错。用 grep -r "pattern" . --include="*.log" 更安全。

陷阱 3-P 不是所有系统都支持(macOS 的 grep 不支持 PCRE,需 brew install grep 安装 GNU grep)。

二、sed:流编辑器的替换艺术

2.1 核心概念:地址 + 命令

sed 的工作模型是逐行读取 → 匹配地址 → 执行命令 → 输出。"地址"决定哪些行被处理,"命令"决定如何处理:

# 地址语法速查
sed '3d' file             # 删除第 3 行
sed '1,5d' file           # 删除第 1-5 行
sed '/^#/d' file          # 删除以 # 开头的行(注释)
sed '/error/,/warn/d' log # 删除从 error 到 warn 之间的所有行

# 命令速查
sed 's/old/new/' file     # 替换每行第一个 old
sed 's/old/new/g' file    # 替换每行所有 old(g = global)
sed 's/old/new/2' file    # 只替换每行第 2 个匹配
sed 's/old/new/i' file    # 忽略大小写替换
sed 'y/abc/xyz/' file     # 字符映射(a→x, b→y, c→z)

2.2 替换实战:从简单到进阶

# 基础:修改配置文件端口
sed -i 's/Port 22/Port 2222/' /etc/ssh/sshd_config
# -i 直接修改文件(原地编辑),不加 -i 只输出到 stdout

# 安全原地编辑:自动备份
sed -i.bak 's/old/new/g' config.yaml
# 生成 config.yaml.bak 备份文件

# 引用匹配组(& 和 \1)
sed 's/[0-9]\+/(&)/g' file          # 用 & 引用整个匹配:123 → (123)
sed -E 's/(\w+)@(\w+)/\1 at \2/g' emails.txt
# \1 引用第 1 组,\2 引用第 2 组
# bob@example → bob at example

# 删除行首行尾空白
sed 's/^ \+//' file       # 删除行首空格
sed 's/ \+$//' file       # 删除行尾空格
sed 's/^ \+//;s/ \+$//'   # 组合:同时删首尾空格

# 多命令用分号连接或 -e 选项
sed -e 's/foo/bar/' -e 's/baz/qux/' file

2.3 高级技巧:多行模式与 Hold Space

sed 有两个缓冲区:Pattern Space(当前处理的行)和 Hold Space(临时存储区)。掌握它们的交换命令(h/H/g/G/x),就能处理跨行操作:

# 合并用 \ 续行的配置项(多行变单行)
sed ':a;N;/\\\n$/ba;s/\\\n//g' nginx.conf
# :a 设置标签,N 读取下一行到 Pattern Space
# 如果续行符号 \ 在行尾则跳回标签 a 继续读
# 最后把 \n 替换掉

# 反转文件所有行(类似 tac)
sed '1!G;h;$!d' file
# 1!G:非首行时把 Hold Space 追加到 Pattern Space
# h:把 Pattern Space 存到 Hold Space
# $!d:非末行时删除 Pattern Space(不输出)
# 到最后一行时自然输出:效果就是逆序

# 删除空行但保留一个(压缩连续空行)
sed '/^$/d' file                     # 删除所有空行
sed '/^$/{N;/^\n$/d}' file          # 只删除连续空行,保留单个空行

2.4 常见陷阱

陷阱 1sed -i 在 macOS 和 Linux 行为不同。macOS 需要 sed -i '' 's/old/new/' file(空字符串备份后缀),Linux 用 sed -i 's/old/new/' file。跨平台脚本建议用 sed -i.bak

陷阱 2:替换含路径分隔符 / 的内容时,可以用其他分隔符:sed 's|/old/path|/new/path|g'

陷阱 3-i 会原地修改文件,没有撤销机制。务必先测试不加 -i 的版本,确认无误再加 -i

三、awk:模式驱动的编程语言

3.1 核心模型:BEGIN + 模式-动作 + END

awk 不是简单的"打印字段"工具——它是完整的编程语言,有变量、条件、循环、函数、数组。其执行模型:

awk '
BEGIN { 初始化代码 }           # 执行一次,在读取任何行之前
/模式/ { 动作代码 }            # 每行匹配模式时执行
{ 默认动作代码 }               # 无模式则每行都执行
END { 收尾代码 }               # 执行一次,在所有行处理完毕后
' file

3.2 字段处理实战

# 基础字段操作(默认空格/tab 分隔)
awk '{print $1, $3}' data.txt        # 打印第 1、3 列
awk '{print NR, $0}' data.txt        # NR=行号,$0=整行
awk '{print NF}' data.txt            # NF=当前行字段数

# 自定义分隔符
awk -F',' '{print $1, $2}' csv.csv   # 逗号分隔
awk -F':' '{print $1}' /etc/passwd   # 冒号分隔
awk -F'\t' '{print $1}' tsv.tsv      # tab 分隔

# 多分隔符(正则分隔符)
awk -F'[,;|]' '{print $1}' mixed.txt  # 逗号、分号、竖线都当分隔符

# 重组字段输出
awk -F',' '{print "Name:" $1, "Age:" $2, "City:" $3}' users.csv

3.3 条件过滤与计算

# 条件过滤
awk '$3 > 100' data.txt              # 第 3 列 > 100 的行
awk '$1 == "ERROR"' app.log          # 第 1 列等于 ERROR
awk '/5[0-9]{2}/' access.log         # 行中包含 5xx 状态码

# 列求和与统计
awk '{sum += $3} END {print "Total:", sum}' sales.csv
awk '{sum += $3; count++} END {print "Avg:", sum/count}' sales.csv

# 按条件分组统计
awk -F',' '$3 == "Beijing" {sum += $2} END {print "Beijing total:", sum}' data.csv

# 计数与频次统计
awk '{count[$1]++} END {for(k in count) print k, count[k]}' access.log
# 按第 1 列(如 IP)分组计数,输出 IP + 访问次数

3.4 内置函数与字符串处理

# 字符串函数
awk '{print length($0)}' file          # 每行长度
awk '{print toupper($1)}' file         # 转大写
awk '{print substr($1, 1, 3)}' file    # 截取子串
awk '{print index($0, "error")}' log   # 查找子串位置
awk -F':' '{gsub(/^[ \t]+/, "", $1); print $1}' file  # 去除字段首空格

# 数学函数
awk '{print sqrt($2), int($2), log($2)}' data.txt

# 格式化输出(printf)
awk -F',' '{printf "%-20s %8d %10.2f\n", $1, $2, $3}' report.csv
# %-20s 左对齐 20 字符宽,%8d 右对齐 8 位整数,%10.2f 10 字符 2 位小数

3.5 高级实战:报表生成与日志分析

# ========== 案例 1:Nginx 日志分析 ==========

# 统计各 HTTP 状态码出现次数 + 占比
awk '{status[$9]++} END {
  total = 0;
  for (s in status) total += status[s];
  for (s in status) printf "%s: %d (%.1f%%)\n", s, status[s], status[s]*100/total;
}' access.log

# 提取 TOP 10 访问 IP
awk '{ip[$1]++} END {
  for (k in ip) print ip[k], k;
}' access.log | sort -rn | head -10

# ========== 案例 2:CSV 报表生成 ==========

# 生成带合计行的销售报表
awk -F',' '
BEGIN {
  printf "%-15s %10s %10s\n", "Region", "Sales", "Orders";
  printf "%-15s %10s %10s\n", "------", "------", "-------";
}
{
  region = $1; sales = $2; orders = $3;
  total_sales += sales; total_orders += orders;
  printf "%-15s %10.0f %10d\n", region, sales, orders;
}
END {
  printf "%-15s %10s %10s\n", "------", "------", "-------";
  printf "%-15s %10.0f %10d\n", "TOTAL", total_sales, total_orders;
}
' sales.csv

3.6 awk 数组与多维统计

# 二维统计:按 IP + 状态码交叉统计
awk '{matrix[$1][$9]++} END {
  for (ip in matrix) {
    for (status in matrix[ip]) {
      print ip, status, matrix[ip][status];
    }
  }
}' access.log

# 利用 SUBSEP 模拟多维数组(传统 awk 不支持 [][] 语法)
awk '{key = $1 SUBSEP $9; count[key]++} END {
  for (k in count) {
    split(k, parts, SUBSEP);
    print parts[1], parts[2], count[k];
  }
}' access.log

四、三剑客组合实战:管道串联的威力

真正的战斗力在于组合。下面是几个高频组合案例:

4.1 日志分析全链路

# 提取 5xx 错误 → 统计 IP → TOP 10 → 生成报告
grep -E "HTTP/[0-9.]+ [5][0-9]{2}" access.log \
  | awk '{print $1}' \
  | sort \
  | uniq -c \
  | sort -rn \
  | head -10

# 提取错误日志中的时间分布(按小时统计)
grep "ERROR" app.log \
  | awk '{print substr($1, 1, 2)}' \
  | sort \
  | uniq -c \
  | awk '{printf "%02d:00 - %d errors\n", $2, $1}'

# 过滤 + 替换 + 格式化
grep "exception" app.log \
  | sed 's/exception/EXCEPTION/g' \
  | awk '{printf "[%s] %s\n", $1, $0}'

4.2 配置文件批量处理

# 删除所有注释行 + 空行,输出干净配置
sed '/^#/d; /^$/d' nginx.conf

# 替换所有 IP 为新 IP + 验证
sed -i 's/192\.168\.1\.100/10\.0\.0\.50/g' *.conf
grep -r "10.0.0.50" . --include="*.conf"

# 提取所有 key=value 对,输出为 CSV
awk -F'=' '/^[^#]/ && NF==2 {
  gsub(/^[ \t]+|[ \t]+$/, "", $1);
  gsub(/^[ \t]+|[ \t]+$/, "", $2);
  print $1 "," $2;
}' config.ini

4.3 数据清洗与转换

# JSON 日志 → CSV(简易提取)
grep "api_call" events.json \
  | sed 's/.*"user":"\([^"]*\)".*"path":"\([^"]*\)".*"time":\([0-9]*\).*/\1,\2,\3/' \
  | awk -F',' 'BEGIN{print "user,path,time"} {print}' > events.csv

# TSV → JSON(awk 生成)
awk -F'\t' 'NR>1 {
  printf "{\"name\":\"%s\",\"age\":%d,\"city\":\"%s\"}", $1, $2, $3;
  if(NR<FNR) printf ",";
  printf "\n";
}' data.tsv > data.json

五、性能优化与选型决策

5.1 性能对比实测

在 100 万行日志文件上实测各工具处理速度:

操作命令耗时备注
简单搜索grep "error" big.log0.3sgrep 最快,专精搜索
正则搜索grep -E "err|fail" big.log0.5sERE 略慢于固定字符串
行级替换sed 's/old/new/g' big.log1.2ssed 单行替换极快
字段提取awk '{print $1,$3}' big.log1.8sawk 需解析字段,稍慢
分组统计awk '{c[$1]++} END{...}'3.5s涉及哈希表,更慢但仍秒级
Python 等效python script.py15s+启动+解释开销,慢 10 倍+

5.2 选型决策流程

面对文本处理需求,如何快速决定用哪个工具?

# 决策流程(伪代码)
if 只需要查找/过滤:
    → grep(最快最简单)
elif 只需要替换/删除行:
    → sed(流编辑,一行搞定)
elif 需要字段操作/计算/报表:
    → awk(编程语言,灵活强大)
elif 需要搜索+替换+计算:
    → grep | sed | awk 管道组合
elif 数据量大(GB级)或逻辑极复杂:
    → 考虑专用工具(ripgrep、mlr、jq)

5.3 现代替代品推荐

经典三剑客之外,一些场景有更优选择:

工具替代品优势
grepripgrep (rg)递归搜索速度快 5-10 倍,自动忽略 .git
grep -Prg原生支持 PCRE,无需 GNU grep
sedsd语法更直觉,内存安全,默认原地编辑有备份
awkMiller (mlr)CSV/TSV/JSON 处理更专业,支持多格式转换
awkjqJSON 处理专精(已有专题文章)

建议:日常仍然优先学三剑客(所有 Linux 都预装),特殊场景用替代品。

六、最佳实践 Checklist

# ✓ grep 最佳实践
1. 优先用 -E (ERE) 而非默认 BRE,语法更直觉
2. 大文件搜索用 -F (固定字符串) 或 fgrep,更快
3. 递归搜索用 rg 或 grep -rI --include="*.ext"
4. 提取匹配内容用 -Po (PCRE + 只输出匹配)
5. 组合过滤用管道:grep ... | grep -v ...

# ✓ sed 最佳实践
1. 先不加 -i 测试,确认后再加 -i.bak(带备份)
2. macOS 用 -i '',Linux 用 -i,跨平台用 -i.bak
3. 含 / 的替换用 | 或 # 当分隔符
4. 多行操作用 N/H/G,先在简单文件上验证逻辑
5. 复杂 sed 脚本写进文件用 -f 调用

# ✓ awk 最佳实践
1. BEGIN 块放初始化、FS 设置、表头
2. END 块放汇总输出、统计结果
3. 大文件统计用 awk 而非 sort|uniq(避免排序开销)
4. printf 比 print 更可控(对齐、精度)
5. 复杂 awk 程序写进文件用 -f 调用

七、常见陷阱汇总

陷阱描述解法
grep BRE 语法| 不是"或",( 不是分组统一用 -E-P
sed -i 跨平台macOS 和 Linux 参数不同-i.bak 统一
sed 无撤销-i 原地修改不可逆先不加 -i 测试,或用 -i.bak
awk 分隔符默认默认连续空格视为一个分隔符-F 明确指定
awk 数字比较"100" 是字符串,"100" > "99" 结果错误强制数值:$2+0 > 99
管道性能多次管道各启动一个进程尽量在 awk 内完成多步逻辑
编码问题处理 UTF-8 中文字符可能乱码设置 LC_ALL=C 或用 rg

结语

grep、sed、awk 三剑客是 Linux 文本处理的基石。它们看似简单,实则各有一个完整的能力体系:grep 的正则精通让你精准搜索,sed 的流编辑让你批量变换,awk 的编程模型让你计算统计。日常 90% 的文本处理任务——日志分析、数据清洗、配置编辑、报表生成——都能用三剑客组合搞定,而且比 Python 脚本快得多。

记住核心原则:找用 grep、改用 sed、算用 awk。精通它们,你的 Linux 命令行战斗力会跃升一个台阶。