
无论你是运维工程师(分析 Nginx/MySQL 日志)、开发工程师(处理配置文件 / 接口返回)、数据分析师(清洗 CSV/TSV 数据)、还是AI 开发者(预处理大模型训练语料),Shell 文本处理都是绕不开的核心技能。
这四个工具被称为 Shell 文本处理的「四大金刚」,它们的核心定位如下:
工具 | 核心角色 | 本质 | 适用场景 |
|---|---|---|---|
cut | 字段切割机 | 从文本中提取指定字段 / 列 | 结构化文本(CSV/TSV/ 日志)的字段提取 |
sed | 流编辑器 | 逐行修改文本内容 | 批量替换、删除、插入、格式化文本 |
awk | 文本分析师 / 报告生成器 | 按行分析文本并生成结构化报告 | 统计、计算、条件过滤、字段重组 |
sort | 数据排序器 | 对文本进行排序 / 去重 / 合并 | 排序日志、去重重复记录、合并数据集 |
ls/cat/echo/grep)我们处理的文本通常是结构化 / 半结构化的,常见格式有:
^(行首)、$(行尾)、.(任意单个字符)、*(0 或多个前一个字符)、[](字符集)、[^](反向字符集)+(1 或多个前一个字符)、?(0 或 1 个前一个字符)、|(或)、()(分组)cut:字段切割机 —— 精准提取文本片段cut OPTION... [FILE]...核心选项:
-d DELIM:指定单个字符的分隔符(默认是 Tab)-f FIELDS:指定提取的字段(多个字段用,分隔,范围用-,如1,3提取第 1 和 3 字段,2-5提取第 2 到 5 字段)-c CHARS:按字符位置提取(而非字段,如1-10提取前 10 个字符)-s:仅显示包含分隔符的行(跳过无分隔符的行)# 提取/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# 提取字符串的前5个字符
echo "Hello World" | cut -c1-5
# 输出:Hello
# 提取第6到结尾的字符
echo "Hello World" | cut -c6-
# 输出: World# 假设有混合格式的文本:
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)# 提取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.txtcut的局限性与替代方案awk支持多字符分隔符和正则分隔符sed:流编辑器 —— 批量修改文本的瑞士军刀sed(Stream Editor)是逐行处理文本的工具,核心原理是「模式匹配→动作执行」,支持的动作包括:打印、删除、替换、插入、追加等。
sed OPTION... 'SCRIPT' [FILE]...核心选项:
-n:仅打印匹配的行(默认会打印所有行)-i:直接修改原文件(危险!建议先备份,如-i.bak)-e:执行多个脚本(支持多命令串联)-E/-r:使用扩展正则(默认是基础正则)这是理解sed进阶用法的关键:
sed的临时工作区,默认每次读取一行文本到模式空间进行处理h/H/g/G/x)与模式空间交互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/passwdd命令)# 删除第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'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.logi/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# 将偶数行合并到奇数行后
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# 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.txtawk:文本分析师 ——Shell 中的 "迷你 Python"awk是最强大的 Shell 文本处理工具,支持变量、条件、循环、数组、函数等,可实现复杂的统计、计算、报告生成功能。
awk OPTION... 'BEGIN{INIT} PATTERN{ACTION} END{FINAL}' [FILE]...核心组件:
BEGIN{}:处理文本之前执行的初始化操作(如定义变量、打印表头)PATTERN{}:模式匹配的动作(逐行处理文本)END{}:处理完所有文本后执行的收尾操作(如打印统计结果)变量 | 含义 |
|---|---|
$0 | 当前处理的整行文本 |
$1~$NF | 当前行的第 1 到第 N 个字段(NF是当前行的字段总数) |
NR | 当前处理的行号 |
FNR | 当前文件的行号(多文件处理时用) |
FS | 字段分隔符(默认是空格 / Tab,可通过-F选项或FS=":"修改) |
OFS | 输出字段分隔符(默认是空格,可修改为,等) |
# 提取/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# 打印/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# 计算/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# 统计文本中每个单词的出现次数
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# 字符串函数:length()计算长度,toupper()转换为大写
awk '{print toupper($0) " (Length: " length($0) ")"}' text.txt
# 数学函数:sqrt()开平方,int()取整
awk '{print "Square Root of " $1 ": " sqrt($1)}' num.txt# 定义一个计算平均数的函数
awk '
function avg(a,b) {
return (a+b)/2
}
BEGIN{print "Avg of 10 and 20: " avg(10,20)}
'
# 输出:Avg of 10 and 20: 15sort:数据排序器 —— 文本排序 / 去重 / 合并的核心工具sort OPTION... [FILE]...核心选项:
-n:数值排序(默认是字典序,如 "10" 会排在 "2" 前面)-r:降序排序(默认是升序)-k FIELD:按指定字段排序(如-k2,2按第 2 字段排序)-t DELIM:指定分隔符(默认是空格 / Tab)-u:去重(仅保留排序后的唯一行)-m:合并已排序的文件(不重新排序)-o FILE:将结果输出到指定文件(代替管道)# 字典序升序(默认)
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# 按第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# 合并两个已排序的文件
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个目标:分析 Nginx 的 access.log,生成「IP 请求次数 Top10」「状态码分布」「平均响应时间」的报告
假设 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(最后一个字段是响应时间,单位:秒)
#!/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!"=== 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
...目标:将爬取的电商评论语料(CSV 格式)预处理为大模型训练可用的格式
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#!/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"Great product! I love it.",5
"Good but expensive.",4
"Bad quality, broke in 2 days.",1cut> sort> sed> awk,如能用 cut 解决的问题不要用 awkcat file | cut | sort → sort file | cutmktemp生成临时文件sed的-n选项减少输出,awk的BEGIN块初始化变量减少重复计算cut的分隔符限制:只能用单个字符,不能用正则或连续空格sed的正则模式:默认是基础正则,扩展正则需要用-E/-rawk的字段分隔符:默认会合并连续的空格 / Tab,如需保留用FS=" +"或-F"[[:space:]]+"sort的默认排序:是字典序,数值排序必须用-n,如 "100" 会排在 "2" 前面sed -i.bak修改文件时自动生成备份man cut/man sed/man awk/man sort查看完整文档需求 | 首选工具 |
|---|---|
简单字段提取 | cut |
批量替换 / 删除文本 | sed |
复杂统计 / 计算 / 报告 | awk |
排序 / 去重 / 合并 | sort |
cut的字段提取、sed的替换 / 删除、sort的基本排序sed的模式空间 / 保持空间、awk的变量 / 数组 / 函数perl/python的文本处理库,弥补 Shell 工具的局限性