Shell 文本处理四大金刚:cut/sed/awk/sort 从入门到精通实战指南

AgenticCoding·十二月创作之星挑战赛 10w+人浏览 490人参与

一、开篇:为什么你必须掌握这四个工具?

1.1 Shell 文本处理的「刚需场景」

无论你是运维工程师(分析 Nginx/MySQL 日志)、开发工程师(处理配置文件 / 接口返回)、数据分析师(清洗 CSV/TSV 数据)、还是AI 开发者(预处理大模型训练语料),Shell 文本处理都是绕不开的核心技能

1.2 四大工具的「定位与分工」

这四个工具被称为 Shell 文本处理的「四大金刚」,它们的核心定位如下:

工具核心角色本质适用场景
cut字段切割机从文本中提取指定字段 / 列结构化文本(CSV/TSV/ 日志)的字段提取
sed流编辑器逐行修改文本内容批量替换、删除、插入、格式化文本
awk文本分析师 / 报告生成器按行分析文本并生成结构化报告统计、计算、条件过滤、字段重组
sort数据排序器对文本进行排序 / 去重 / 合并排序日志、去重重复记录、合并数据集

1.3 学习前置要求

  • 已掌握 Shell 基础命令(ls/cat/echo/grep
  • 了解正则表达式基础(后续会针对工具补充)
  • 可在 Linux/macOS 系统或 WSL2 中实践(所有示例均在 CentOS 7.9 中验证)

二、基础铺垫:Shell 文本处理的前置知识

2.1 文本格式定义

我们处理的文本通常是结构化 / 半结构化的,常见格式有:

  1. 分隔符文本:用固定字符分隔字段(如 CSV 用逗号,TSV 用 Tab,/etc/passwd 用冒号)
  2. 日志文本:用空格 / Tab / 引号分隔字段(如 Nginx 的 access.log、Python 的日志)
  3. 自由格式文本:无固定分隔符(如纯文本文件、大模型训练语料)

2.2 正则表达式快速复习

  • 基础正则(BRE):支持^(行首)、$(行尾)、.(任意单个字符)、*(0 或多个前一个字符)、[](字符集)、[^](反向字符集)
  • 扩展正则(ERE):在 BRE 基础上增加+(1 或多个前一个字符)、?(0 或 1 个前一个字符)、|(或)、()(分组)

三、cut:字段切割机 —— 精准提取文本片段

3.1 核心语法

cut OPTION... [FILE]...

核心选项

  • -d DELIM:指定单个字符的分隔符(默认是 Tab)
  • -f FIELDS:指定提取的字段(多个字段用分隔,范围用-,如1,3提取第 1 和 3 字段,2-5提取第 2 到 5 字段)
  • -c CHARS:按字符位置提取(而非字段,如1-10提取前 10 个字符)
  • -s:仅显示包含分隔符的行(跳过无分隔符的行)

3.2 基础用法示例

3.2.1 结构化文本字段提取(/etc/passwd)
# 提取/etc/passwd的第1字段(用户名)和第7字段(登录Shell)
cut -d: -f1,7 /etc/passwd
# 输出示例:
# root:/bin/bash
# bin:/sbin/nologin
# daemon:/sbin/nologin

# 提取第1到3字段
cut -d: -f1-3 /etc/passwd
3.2.2 字符位置提取(固定长度文本)
# 提取字符串的前5个字符
echo "Hello World" | cut -c1-5
# 输出:Hello

# 提取第6到结尾的字符
echo "Hello World" | cut -c6-
# 输出: World
3.2.3 跳过无分隔符的行
# 假设有混合格式的文本:
echo -e "user:password:email\njohn:123456:john@example.com\ninvalid line" > test.txt

# 仅显示包含分隔符的行
cut -d: -s -f1,2 test.txt
# 输出:user:password、john:123456(跳过invalid line)

3.3 进阶用法:与其他工具结合

# 提取Nginx access.log的IP和请求路径
# 假设日志格式:192.168.1.1 - - [Time] "GET /index.html HTTP/1.1" 200 612
cat access.log | cut -d'"' -f2 | cut -d' ' -f1,2
# 输出:GET /index.html

# 提取大模型训练语料的正文(假设语料是id|title|content格式)
cat llm_corpus.txt | cut -d'|' -f3 > llm_train_content.txt

3.4 cut局限性与替代方案

  • 只能使用单个字符作为分隔符(不能用正则或连续空格)
  • 替代方案:awk支持多字符分隔符和正则分隔符

四、sed:流编辑器 —— 批量修改文本的瑞士军刀

sed(Stream Editor)是逐行处理文本的工具,核心原理是「模式匹配→动作执行」,支持的动作包括:打印、删除、替换、插入、追加等。

4.1 核心语法

sed OPTION... 'SCRIPT' [FILE]...

核心选项

  • -n:仅打印匹配的行(默认会打印所有行)
  • -i:直接修改原文件(危险!建议先备份,如-i.bak
  • -e:执行多个脚本(支持多命令串联)
  • -E/-r:使用扩展正则(默认是基础正则)

4.2 核心概念:模式空间与保持空间

这是理解sed进阶用法的关键

  • 模式空间(Pattern Space)sed的临时工作区,默认每次读取一行文本到模式空间进行处理
  • 保持空间(Hold Space):用于暂存模式空间的内容,默认是空的,可通过命令(h/H/g/G/x)与模式空间交互

4.3 常用命令与示例

4.3.1 打印行(p命令)
# 打印第5行
sed -n '5p' /etc/passwd

# 打印第3到10行
sed -n '3,10p' /etc/passwd

# 打印包含"root"的行
sed -n '/root/p' /etc/passwd

# 打印从包含"root"的行到第10行的内容
sed -n '/root/,10p' /etc/passwd
4.3.2 删除行(d命令)
# 删除第5行
sed '5d' /etc/passwd

# 删除第3到10行
sed '3,10d' /etc/passwd

# 删除包含"nologin"的行
sed '/nologin/d' /etc/passwd

# 删除空行(基础正则:^$匹配空行)
sed '/^$/d' test.txt

# LLM关联:删除大模型语料中的空行和重复换行
sed '/^$/d' llm_corpus.txt | sed 's/\n\n/\n/g'
4.3.3 替换文本(s命令,核心用法!)

语法sed 's/源文本/目标文本/FLAGS'FLAGS

  • g:全局替换(默认仅替换每行的第一个匹配)
  • i:忽略大小写(仅部分版本支持)
  • n:替换第 n 个匹配(如2替换每行的第二个匹配)
# 将"a"替换为"b"(仅替换每行第一个)
echo "aaa bbb aaa" | sed 's/a/b/'
# 输出:baa bbb aaa

# 全局替换
echo "aaa bbb aaa" | sed 's/a/b/g'
# 输出:bbb bbb bbb

# 替换第2个匹配
echo "aaa bbb aaa" | sed 's/a/b/2'
# 输出:aba bbb aaa

# 使用正则替换HTML标签(LLM语料清洗)
echo "<p>Hello World</p>" | sed -E 's/<[^>]*>//g'
# 输出:Hello World

# 替换Nginx日志时间格式(将[10/Oct/2024:13:55:36]转换为2024-10-10 13:55:36)
sed 's/\[//;s/\]/''/;s/\/Oct\//-10-/;s/\/2024/:2024 /' access.log
4.3.4 插入 / 追加行(i/a命令)
# 在第5行**之前**插入"Insert Line"
sed '5i Insert Line' /etc/passwd

# 在第5行**之后**追加"Append Line"
sed '5a Append Line' /etc/passwd

# 在包含"root"的行之前插入注释
sed '/root/i # This is root user' /etc/passwd
4.3.5 模式空间与保持空间交互(进阶)
# 将偶数行合并到奇数行后
echo -e "Line 1\nLine 2\nLine 3\nLine 4" | sed 'N;s/\n/ /'
# 输出:Line 1 Line 2、Line 3 Line 4

# 反转文件内容(类似tac命令,用保持空间实现)
sed -n '1!G;h;$p' test.txt

4.4 LLM 关联应用:大模型语料清洗

# 1. 删除语料中的HTML标签
sed -E 's/<[^>]*>//g' llm_corpus.html > llm_corpus.txt

# 2. 统一换行符为Linux格式
sed 's/\r//g' llm_corpus.txt > llm_corpus_linux.txt

# 3. 替换语料中的敏感词
sed 's/敏感词/替换词/g' llm_corpus.txt > llm_corpus_clean.txt

# 4. 删除语料中的重复行(结合sort)
sed '/^$/d' llm_corpus.txt | sort | uniq > llm_corpus_deduplicated.txt

五、awk:文本分析师 ——Shell 中的 "迷你 Python"

awk最强大的 Shell 文本处理工具,支持变量、条件、循环、数组、函数等,可实现复杂的统计、计算、报告生成功能。

5.1 核心语法

awk OPTION... 'BEGIN{INIT} PATTERN{ACTION} END{FINAL}' [FILE]...

核心组件

  • BEGIN{}:处理文本之前执行的初始化操作(如定义变量、打印表头)
  • PATTERN{}:模式匹配的动作(逐行处理文本)
  • END{}:处理完所有文本后执行的收尾操作(如打印统计结果)

5.2 内置变量(核心!)

变量含义
$0当前处理的整行文本
$1~$NF当前行的第 1 到第 N 个字段(NF是当前行的字段总数)
NR当前处理的行号
FNR当前文件的行号(多文件处理时用)
FS字段分隔符(默认是空格 / Tab,可通过-F选项或FS=":"修改)
OFS输出字段分隔符(默认是空格,可修改为,等)

5.3 基础用法示例

5.3.1 字段提取与重组
# 提取/etc/passwd的第1和7字段,用" -> "分隔
awk -F: '{print $1 " -> " $7}' /etc/passwd
# 输出:root -> /bin/bash

# 打印当前行的字段总数和行号
awk -F: '{print "Line " NR ": " NF " fields"}' /etc/passwd

# LLM关联:提取大模型语料的标题和正文,重组为Prompt格式
awk -F'|' '{print "请分析以下文本:\n标题:" $2 "\n正文:" $3 "\n---"}' llm_corpus.txt
5.3.2 条件过滤
# 打印/etc/passwd中字段数等于7的行(合法的系统用户行)
awk -F: 'NF==7{print}' /etc/passwd

# 打印Nginx日志中状态码为404的行
awk '$9==404{print}' access.log

# 打印系统用户中UID大于1000的行
awk -F: '$3>1000{print}' /etc/passwd
5.3.3 统计计算(BEGIN/END 块)
# 计算/etc/passwd的行数
awk 'END{print NR}' /etc/passwd

# 计算Nginx日志中所有请求的响应时间总和(假设第10字段是响应时间)
awk '{sum += $10} END{print "Total Response Time: " sum "s"}' access.log

# 计算大模型语料中不同类别的数量(假设第4字段是类别)
awk -F'|' '{count[$4]++} END{for(cat in count) print cat ": " count[cat] " items"}' llm_corpus.txt
# 输出示例:
# 科技: 120
# 娱乐: 80
# 教育: 50

5.4 进阶用法:数组与函数

5.4.1 数组应用:统计词频
# 统计文本中每个单词的出现次数
cat text.txt | tr -s ' ' '\n' | awk '{count[$1]++} END{for(word in count) print word ": " count[word]}'

# LLM关联:统计大模型训练语料中"AI"的出现次数
awk '{count += gsub(/AI/,"AI")} END{print "AI出现次数:" count}' llm_corpus.txt
5.4.2 内置函数:字符串 / 数学函数
# 字符串函数:length()计算长度,toupper()转换为大写
awk '{print toupper($0) " (Length: " length($0) ")"}' text.txt

# 数学函数:sqrt()开平方,int()取整
awk '{print "Square Root of " $1 ": " sqrt($1)}' num.txt
5.4.3 自定义函数
# 定义一个计算平均数的函数
awk '
function avg(a,b) {
    return (a+b)/2
}
BEGIN{print "Avg of 10 and 20: " avg(10,20)}
'
# 输出:Avg of 10 and 20: 15

六、sort:数据排序器 —— 文本排序 / 去重 / 合并的核心工具

6.1 核心语法

sort OPTION... [FILE]...

核心选项

  • -n数值排序(默认是字典序,如 "10" 会排在 "2" 前面)
  • -r降序排序(默认是升序)
  • -k FIELD:按指定字段排序(如-k2,2按第 2 字段排序)
  • -t DELIM:指定分隔符(默认是空格 / Tab)
  • -u去重(仅保留排序后的唯一行)
  • -m合并已排序的文件(不重新排序)
  • -o FILE:将结果输出到指定文件(代替管道)

6.2 基础用法示例

6.2.1 基本排序
# 字典序升序(默认)
echo -e "b\nc\na" | sort
# 输出:a、b、c

# 数值降序
echo -e "5\n2\n9\n1" | sort -n -r
# 输出:9、5、2、1

# 去重排序
echo -e "a\nb\na\nc" | sort -u
# 输出:a、b、c
6.2.2 按字段排序
# 按第2字段数值降序排序(分隔符是空格)
echo -e "a 3\nc 1\nb 5" | sort -k2,2 -n -r
# 输出:b 5、a 3、c 1

# 按/etc/passwd的第3字段(UID)升序排序
sort -t: -k3,3 -n /etc/passwd

# LLM关联:按大模型生成的候选答案分数降序排序(假设是"答案|分数"格式)
sort -t'|' -k2,2 -n -r llm_candidates.txt
6.2.3 合并与去重
# 合并两个已排序的文件
sort -m sorted1.txt sorted2.txt > merged.txt

# 统计Nginx日志中最活跃的10个IP
cat access.log | cut -d' ' -f1 | sort | uniq -c | sort -n -r | head -10
# 解释:
# 1. cut提取IP
# 2. sort排序IP
# 3. uniq -c统计每个IP的出现次数
# 4. sort -n -r按次数降序排序
# 5. head -10取前10个

七、综合实战:四大工具协同处理真实场景

7.1 场景 1:Nginx 访问日志分析

目标:分析 Nginx 的 access.log,生成「IP 请求次数 Top10」「状态码分布」「平均响应时间」的报告

7.1.1 准备日志格式

假设 access.log 的格式为:

192.168.1.1 - - [10/Oct/2024:13:55:36 +0800] "GET /index.html HTTP/1.1" 200 612 "-" "Mozilla/5.0" 0.05
192.168.1.2 - - [10/Oct/2024:13:55:37 +0800] "GET /about.html HTTP/1.1" 404 512 "-" "Mozilla/5.0" 0.12

(最后一个字段是响应时间,单位:秒)

7.1.2 实战命令
#!/bin/bash
# Nginx日志分析脚本

echo "=== Nginx Access Log Analysis Report ==="
echo "Date: $(date)"
echo "====================================="

# 1. IP请求次数Top10
echo -e "\n1. Top 10 Active IP Addresses:"
cat access.log | cut -d' ' -f1 | sort | uniq -c | sort -n -r | head -10 | awk '{printf "%-15s %-5d\n", $2, $1}'

# 2. HTTP状态码分布
echo -e "\n2. HTTP Status Code Distribution:"
cat access.log | cut -d' ' -f9 | sort | uniq -c | sort -n -r | awk '{printf "%-3d %-5d\n", $2, $1}'

# 3. 平均响应时间
echo -e "\n3. Average Response Time:"
cat access.log | awk '{sum += $NF; count++} END{printf "%.3fs\n", sum/count}'

# 4. 404请求的URL
echo -e "\n4. 404 Not Found URLs:"
cat access.log | grep " 404 " | cut -d'"' -f2 | cut -d' ' -f2 | sort -u

echo -e "\n====================================="
echo "Analysis Completed!"
7.1.3 运行结果
=== Nginx Access Log Analysis Report ===
Date: Thu Oct 10 14:23:45 CST 2024
=====================================

1. Top 10 Active IP Addresses:
192.168.1.1       125  
192.168.1.3       89   
192.168.1.2       56   
...

2. HTTP Status Code Distribution:
200  1234
404  56
301  23
...

3. Average Response Time:
0.087s

4. 404 Not Found URLs:
/about.html
/contact.html
...

7.2 场景 2:大模型训练语料预处理

目标:将爬取的电商评论语料(CSV 格式)预处理为大模型训练可用的格式

7.2.1 原语料格式
id,username,comment,score,timestamp
1,john,"Great product! I love it.",5,2024-10-01
2,jane,"Bad quality, broke in 2 days.",1,2024-10-02
3,bob,"Good but expensive.",4,2024-10-03
7.2.2 预处理目标
  • 仅保留评论内容和评分
  • 清洗评论中的特殊字符
  • 按评分降序排序
  • 去重重复评论
7.2.3 实战命令
#!/bin/bash
# LLM训练语料预处理脚本

# 1. 提取评论和评分字段,跳过表头
cut -d',' -f3,4 comments.csv | tail -n +2 > tmp_corpus.txt

# 2. 清洗评论中的引号和特殊字符
sed -e 's/"//g' -e 's/\r//g' -e 's/[^a-zA-Z0-9 ,。!?]//g' tmp_corpus.txt > tmp_corpus_clean.txt

# 3. 按评分降序排序(用awk的第2字段)
awk -F',' '{print $0}' tmp_corpus_clean.txt | sort -t',' -k2,2 -n -r > tmp_corpus_sorted.txt

# 4. 去重重复评论
uniq tmp_corpus_sorted.txt > llm_train_corpus.txt

# 5. 统计预处理结果
echo "=== LLM Corpus Preprocessing Report ==="
echo "Original Lines: $(wc -l < comments.csv)"
echo "Preprocessed Lines: $(wc -l < llm_train_corpus.txt)"
echo "Average Comment Length: $(awk -F',' '{sum += length($1)} END{printf "%d\n", sum/NR}' llm_train_corpus.txt)"
echo "====================================="

# 清理临时文件
rm tmp_*.txt
7.2.4 预处理后结果
"Great product! I love it.",5
"Good but expensive.",4
"Bad quality, broke in 2 days.",1

八、进阶技巧与最佳实践

8.1 性能优化

  1. 优先使用轻量级工具cutsortsedawk,如能用 cut 解决的问题不要用 awk
  2. 减少管道次数:合并命令减少 IO 操作,如cat file | cut | sort → sort file | cut
  3. 避免临时文件:用管道直接传递结果,如无法避免用mktemp生成临时文件
  4. 大文件处理:用sed-n选项减少输出,awkBEGIN块初始化变量减少重复计算

8.2 常见坑点

  1. cut的分隔符限制:只能用单个字符,不能用正则或连续空格
  2. sed的正则模式:默认是基础正则,扩展正则需要用-E/-r
  3. awk的字段分隔符:默认会合并连续的空格 / Tab,如需保留用FS=" +"-F"[[:space:]]+"
  4. sort的默认排序:是字典序,数值排序必须用-n,如 "100" 会排在 "2" 前面

8.3 最佳实践

  1. 注释清晰:给复杂命令添加注释,便于维护
  2. 先测试再执行:对大文件或原文件修改,先在小样本上测试
  3. 备份原文件:用sed -i.bak修改文件时自动生成备份
  4. 查看手册:用man cut/man sed/man awk/man sort查看完整文档
  5. 写成脚本:将常用的处理逻辑写成 Shell 脚本,便于复用

九、结尾总结:工具选型与学习路径

9.1 工具选型建议

需求首选工具
简单字段提取cut
批量替换 / 删除文本sed
复杂统计 / 计算 / 报告awk
排序 / 去重 / 合并sort

9.2 学习路径

  1. 基础阶段:掌握cut的字段提取、sed的替换 / 删除、sort的基本排序
  2. 进阶阶段:掌握sed的模式空间 / 保持空间、awk的变量 / 数组 / 函数
  3. 实战阶段:结合真实场景(日志分析、语料预处理),用四个工具协同处理
  4. 高级阶段:学习perl/python的文本处理库,弥补 Shell 工具的局限性
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值