文本变换与提取
Linux 运维基础 · 第七章 目标:掌握 cut/sort/uniq/sed/awk,能从日志和命令输出中精准提取数据
1. 概述#
上一章讲了"找",这一章讲"提取和变换"——找到之后怎么把有用的信息抠出来、排序、统计、格式化。这是运维"数据管道"的核心环节,也是面试高频考点。
工具链:cut(切列)→ sort(排序)→ uniq(去重统计)→ sed(替换/删除)→ awk(终极瑞士军刀)。
2. cut — 按列切割#
2.1 按分隔符切割#
bash
# -d 指定分隔符,-f 选字段(从 1 开始)
cut -d: -f1 /etc/passwd # 用户名(第 1 列)
cut -d: -f1,7 /etc/passwd # 用户名 + Shell(第 1 和 7 列)
cut -d: -f1-3 /etc/passwd # 第 1 到第 3 列
# 空格分隔
echo "192.168.1.1 8080 tcp" | cut -d' ' -f1 # 192.168.1.12.2 按字符位置切割#
bash
# -c 按字符位置
echo "2025-07-01 10:30:15" | cut -c1-10 # 2025-07-01
echo "ERROR:Connection timeout" | cut -c1-5 # ERROR2.3 典型用法#
bash
# 列出所有用户名和 Shell
cut -d: -f1,7 /etc/passwd
# 从 ps 输出中只提取 PID 和命令
ps aux | cut -c1-20,66-
# 提取 Nginx 日志中的 IP
cut -d' ' -f1 /var/log/nginx/access.log3. sort + uniq — 排序和去重#
3.1 sort — 排序#
bash
# 基础排序
sort file.txt # 字典序排序
# 数字排序
sort -n file.txt # 按数值排序(不写 -n 的话 "10" < "2")
sort -nr file.txt # 倒序数字排序
sort -rn file.txt # 等价写法
# 其他常用参数
sort -u # 排序并去重(等效 sort | uniq)
sort -k2 # 按第 2 列排序
sort -t: -k3 -n /etc/passwd # 按冒号分隔的第 3 列(UID)数字排序
sort -t' ' -k2nr # 按空格分隔的第 2 列数字倒序
sort -h file.txt # 人类可读数字排序(4K < 2M < 1G,配合 du -h)坑:locale 会悄悄改变排序结果。 现代系统默认是 UTF-8 locale,sort 的字典序会忽略大小写差异、标点,甚至把 a 和 A 排在一起,导致 sort | uniq 去重不干净、脚本行为在不同机器上不一致。
bash
# 现象:UTF-8 locale 下大小写混排、标点被忽略
sort file.txt
# 要“按字节排序”(纯 ASCII 码顺序,可预测、更快):
LC_ALL=C sort file.txt
# 运维脚本、比对 diff、做精确去重时,强烈建议前面加 LC_ALL=C3.2 uniq — 去重 / 统计#
bash
# 注意:uniq 只去除**连续**的重复行,所以通常先 sort
sort file.txt | uniq # 去重
sort file.txt | uniq -c # 去重 + 统计每行出现次数
sort file.txt | uniq -d # 只显示重复的行
sort file.txt | uniq -u # 只显示不重复的行3.3 经典组合#
bash
# 访问量 Top10 IP
cat access.log | awk '{print $1}' | sort | uniq -c | sort -rn | head -10
# └ 提取IP └ 排序 └ 统计计数 └ 按计数倒序 └ 取前10
# 每个 IP 访问了多少个不同 URI
cat access.log | awk '{print $1, $7}' | sort -u | awk '{print $1}' | sort | uniq -c | sort -rn4. sed — 流编辑器#
sed 是 非交互式 文本编辑工具,按行读、按规则处理、输出。可替换、删除、插入、提取某几行。
4.1 替换:s/old/new/#
bash
# 基本替换(每行第一次出现)
sed 's/old/new/' file.txt
echo "hello world" | sed 's/world/linux/' # hello linux
# 全局替换(所有出现)
sed 's/old/new/g' file.txt
# 不同分隔符(当 old/new 里有 / 时换分隔符)
sed 's|/usr/local|/opt|' file.txt # 用 | 代替 /
sed 's#http://#https://#' file.txt # 用 # 更清晰
# 只替换第 N 次匹配
sed 's/old/new/2' file.txt # 替换每行第 2 次出现
# 指定行范围替换
sed '2,5s/old/new/' file.txt # 只替换第 2-5 行
sed '/pattern/s/old/new/' file.txt # 只替换匹配 pattern 的行
# 直接修改文件(加 -i 参数)
sed -i 's/old/new/g' file.txt # 原地修改
sed -i.bak 's/old/new/g' file.txt # 修改前备份为 file.txt.bak4.2 删除行:d#
bash
sed '3d' file.txt # 删除第 3 行
sed '3,5d' file.txt # 删除第 3-5 行
sed '/pattern/d' file.txt # 删除匹配的行
sed '/^$/d' file.txt # 删除空行
sed '/^#/d' file.txt # 删除 # 开头(注释)的行4.3 提取行范围:n,m p#
bash
# p 是打印,通常配合 -n(静默模式)使用
sed -n '5p' file.txt # 只打印第 5 行
sed -n '10,20p' file.txt # 打印第 10-20 行
sed -n '/start/,/end/p' file.txt # 打印从 start 到 end 之间的所有行4.4 插入和追加#
bash
sed '3a\new line' file.txt # 在第 3 行后追加一行
sed '3i\new line' file.txt # 在第 3 行前插入一行4.5 典型运维场景#
bash
# 修改 Nginx 配置:把 HTTP 重定向到 HTTPS
sed -i 's|listen 80;|listen 80;\n return 301 https://$host$request_uri;|' nginx.conf
# 修改配置文件:切换数据库地址
sed -i 's/db_host=.*/db_host=10.0.1.100/' config.ini
# 清理日志:去掉 DEBUG 行
sed -i '/DEBUG/d' app.log
# 去掉行首尾空白
sed 's/^[ \t]*//;s/[ \t]*$//' file.txt
# 删除所有 # 注释行和空行(看有效配置)
sed '/^#/d;/^$/d' /etc/ssh/sshd_config5. awk — 强大的文本处理语言#
awk 是运维排障的终极利器。按行读取、按列处理、支持条件过滤和计算。
5.1 基础语法#
bash
awk '条件 { 动作 }' file
# 条件可选,默认所有行
# 动作用 {} 包裹,默认 {print $0}(打印整行)5.2 字段引用#
| 写法 | 含义 |
|---|---|
$0 | 整行 |
$1 | 第 1 列(默认空格分隔) |
$2 | 第 2 列 |
$NF | 最后一列 |
$(NF-1) | 倒数第 2 列 |
NR | 当前行号 |
NF | 当前行的列数 |
5.3 常用场景#
bash
# 提取指定列
awk '{print $1, $3}' access.log # 第 1 和第 3 列
# 按条件过滤
awk '$3 > 100 {print $0}' data.txt # 第 3 列大于 100 的行
# 按分隔符解析
awk -F: '{print $1, $3}' /etc/passwd # -F 指定分隔符为 :
# 按行号过滤
awk 'NR>=5 && NR<=10' file.txt # 只处理 5-10 行
# 按正则过滤
awk '/ERROR/ {print $0}' app.log # 包含 ERROR 的行
awk '$1 ~ /^192\./ {print $0}' access.log # 第 1 列匹配正则
# 程序块(BEGIN 和 END)
awk 'BEGIN {print "开始统计"} {sum+=$3} END {print "合计:", sum}' data.txt5.4 运维实战#
实战 1:列出所有普通用户(UID >= 1000)#
bash
awk -F: '{if($3>=1000) print $1}' /etc/passwd
# 简写:
awk -F: '$3>=1000 {print $1}' /etc/passwd实战 2:Top10 访问 IP#
bash
cat /var/log/nginx/access.log | awk '{print $1}' | sort | uniq -c | sort -rn | head -10
# ^ IP 在第一列实战 3:统计日志中每种 HTTP 状态码的数量#
bash
cat /var/log/nginx/access.log | awk '{print $9}' | sort | uniq -c | sort -rn
# 200 184723
# 304 12345
# 404 567
# 500 23实战 4:找非 Running 的 K8s Pod#
bash
kubectl get pods -A | awk '$4!="Running" {print $1, $2, $4}'
# NAMESPACE NAME STATUS实战 5:计算访问日志总流量#
bash
# 假设第 10 列是响应大小(字节)
awk '{sum += $10} END {print "Total:", sum/1024/1024, "MB"}' access.log实战 6:按时间段统计请求数#
bash
# 时间格式:[01/Jul/2025:10:30:15 +0800]
awk '{split($4, a, ":"); hour=a[2]; count[hour]++}
END {for(h in count) print h":00", count[h]}' access.log | sort实战 7:条件即模式 + 三元 if-else#
bash
# 技巧 1:把条件直接写在动作块前面,等价于 if,更简洁
awk -F: '{if($3>=1000) print $1}' /etc/passwd # 啰嗦写法
awk -F: '$3>=1000 {print $1}' /etc/passwd # 等价简写(条件即模式)
# 技巧 2:真正的 if-else 用三元表达式,一行给每行打标签
awk -F: '{print $1, ($3>=1000 ? "普通用户" : "系统用户")}' /etc/passwd6. 工具组合实战#
构造运维分析管道#
bash
# 场景:Nginx access.log,统计每个 IP 的最常访问 URI
awk '{a[$1 SUBSEP $7]++} END { # 用 "IP\0URI" 作为 key 计数
for(k in a) { split(k, p, SUBSEP); printf "%s %s %d\n", p[1], p[2], a[k] }
}' access.log | sort -k1,1 -k3rn # 按 IP + 访问次数排序坑:
a[$1][$7]这种“真·多维数组”是 gawk 4.0+ 的扩展。 Debian/Ubuntu 默认awk是 mawk,不支持多维数组,会直接报语法错误。跨发行版可移植的写法是上面这种a[$1 SUBSEP $7](用内置分隔符SUBSEP把多个字段拼成一个 key),或干脆确认装了 gawk(Rocky/RHEL 默认就是 gawk)再用a[i][j]。
管道设计的思考方式#
- 提取 —
awk或cut把需要的列拿出来 - 预处理 —
sort为下一步做准备 - 聚合 —
uniq -c或awk做统计 - 排序展示 —
sort -rn | head取 TopN
7. 小结#
| 工具 | 核心功能 | 记忆口诀 |
|---|---|---|
cut | 按列切 | -d 分隔符 -f 列号 |
sort | 排序 | -n 数字 -r 倒序 -k 按列;精确去重加 LC_ALL=C |
uniq | 去重统计 | 只去相邻重复,必须先 sort;uniq -c 计数 |
sed | 流编辑 | s/old/new/g 替换,d 删除,-i 原地修改 |
awk | 终极处理 | $1 第一列,$NF 最后一列,条件过滤 + 统计计算 |
| 管道组合 | 数据流水线 | 提取 → 排序 → 聚合 → 展示 |