从基础到高阶详解Linux Shell文件去重的完整指南
在日常开发和运维工作中,我们经常会遇到文件内容重复的场景——比如日志合并后存在大量重复条目、数据采集时出现冗余记录、文本处理后残留重复行等。手动去重效率低下且容易出错,而 Linux Shell 提供了多种高效的去重工具和技巧,能够轻松应对不同场景下的文件去重需求。
本文将从「基础去重」到「高阶实战」,系统讲解 Linux Shell 中文件去重的核心方法,包括 sort+uniq 组合、awk 高级去重、sed 精准去重等,同时深入原理分析和性能对比,帮助你根据实际场景选择最优方案。
一、基础去重:sort + uniq 组合(最常用)
1.1 核心原理
uniq 是 Linux 内置的去重命令,但其仅能去除相邻的重复行(底层原理:逐行读取文件,与上一行内容对比,相同则跳过,不同则输出并更新“上一行缓存”)。因此,直接使用 uniq 无法处理非相邻重复行,必须先通过 sort 命令将文件内容排序,使重复行相邻,再用 uniq 去重。
1.2 基本用法
场景 1:简单去重(保留首次出现的行)
# 语法:sort 文件名 | uniq sort data.txt | uniq
示例:data.txt 原始内容
apple banana apple orange banana
执行结果(去重后并排序)
apple banana orange
场景 2:统计重复次数(去重并显示计数)
sort data.txt | uniq -c
输出结果(前缀为重复次数)
2 apple
2 banana
1 orange场景 3:只保留重复行(筛选重复数据)
sort data.txt | uniq -d
输出结果(仅显示出现次数 ≥2 的行)
apple banana
场景 4:只保留不重复行(筛选唯一数据)
sort data.txt | uniq -u
输出结果(仅显示出现次数 =1 的行)
orange
1.3 关键参数详解
| 参数 | 作用 | 示例 |
|---|---|---|
-c | 显示每行的重复次数 | `sort data.txt |
-d | 仅显示重复出现的行(次数 ≥2) | `sort data.txt |
-u | 仅显示不重复的行(次数 =1) | `sort data.txt |
-i | 忽略大小写差异(如 Apple 和 apple 视为重复) | `sort data.txt |
-f N | 忽略前 N 个字段的差异(字段以空格分隔) | `sort data.txt |
-s N | 忽略前 N 个字符的差异 | `sort data.txt |
1.4 注意事项
- 必须先排序:
uniq仅对比相邻行,未排序的文件直接使用uniq会导致去重不彻底(如上述示例中直接uniq data.txt无法去除非相邻的apple和banana)。 - 空白行处理:
uniq会将空白行视为相同内容,如需保留空白行可结合grep过滤:sort data.txt | grep -v '^$' | uniq(先去除空白行再去重)。 - 性能考量:
sort命令的时间复杂度为 O(n log n),对于超大文件(GB 级),需结合后续高阶方法优化。
二、高阶去重:awk 命令(灵活处理复杂场景)
sort+uniq 适合简单的全行去重,但在实际工作中,我们常遇到「部分字段去重」「保留最后一次出现的行」「按条件去重」等复杂需求,此时 awk 命令的灵活性优势凸显。
2.1 核心原理
awk 基于「关联数组」(键值对)实现去重:将需要去重的字段作为数组的键,遍历文件时判断键是否存在——存在则跳过(或覆盖),不存在则存储并输出。无需提前排序,支持自定义去重逻辑。
2.2 典型场景实战
场景 1:部分字段去重(保留首次出现的行)
假设 user.txt 内容如下(第一列为用户 ID,第二列为用户名),需按用户 ID 去重(相同 ID 仅保留首次出现的行):
101 Alice 102 Bob 101 Charlie 103 Dave 102 Eve
命令:
awk '!a[$1]++' user.txt
输出结果(按 ID 去重,保留首次出现的行):
101 Alice 102 Bob 103 Dave
原理解析:
$1表示第一列(用户 ID),作为关联数组a的键。a[$1]++表示数组值自增(初始为 0,首次访问后变为 1)。!a[$1]表示当数组值为 0 时(首次出现),条件为真,执行默认操作(输出当前行)。
场景 2:部分字段去重(保留最后一次出现的行)
延续上述场景,需按用户 ID 去重,保留最后一次出现的行:
命令:
awk '{a[$1]=$0} END{for(k in a) print a[k]}' user.txt输出结果:
101 Charlie 102 Eve 103 Dave
原理解析:
- 遍历文件时,用数组
a存储每个 ID 对应的最新行(重复 ID 会覆盖之前的值)。 END块在文件遍历结束后执行,遍历数组并输出所有键对应的行(顺序不固定,如需排序可追加| sort)。
场景 3:全行去重(无需排序,保留首次出现)
与 sort+uniq 功能一致,但无需排序,效率更高(时间复杂度 O(n)):
awk '!a[$0]++' data.txt
输出结果(保留首次出现的行,顺序与原文件一致):
apple banana orange
场景 4:按条件去重(如过滤特定字符后去重)
假设 log.txt 内容如下(需按日志时间戳去重,忽略后面的详细信息):
2024-05-01 10:00:00 [INFO] 用户登录 2024-05-01 10:00:00 [ERROR] 数据库连接失败 2024-05-01 10:01:00 [INFO] 数据查询成功
命令(按前 19 个字符(时间戳)去重,保留首次出现的行):
awk '!a[substr($0,1,19)]++' log.txt
输出结果:
2024-05-01 10:00:00 [INFO] 用户登录 2024-05-01 10:01:00 [INFO] 数据查询成功
原理:substr($0,1,19) 截取每行前 19 个字符(时间戳部分)作为数组键,实现按时间戳去重。
2.3 awk 去重的优势
- 无需排序:直接遍历文件一次即可完成去重,效率高于
sort+uniq(尤其对于超大文件)。 - 灵活定制:支持按任意字段、字符长度、正则表达式等条件去重,满足复杂场景需求。
- 保留原始顺序:默认保留首次出现的行的顺序,而
sort+uniq会改变行的顺序(除非后续再排序回原始顺序,操作繁琐)。
三、精准去重:sed 命令(适合小规模文件)
sed 是流式文本编辑器,通过正则表达式匹配和替换实现去重,适合小规模文件或简单的去重场景(性能略逊于 awk,但语法简洁)。
3.1 核心原理
利用 sed 的「保持空间(hold space)」和「模式空间(pattern space)」,将已处理的行存储在保持空间中,遍历每行时与保持空间中的内容对比,若存在则删除,不存在则添加到保持空间并输出。
3.2 基本用法
场景:全行去重(保留首次出现的行,顺序不变)
sed -n ':a; /^\n*$/!{H;g;s/\n//g;/:$/!s/$/:/;s/:[^:]*\(\1\)/:\1/;t b;g}; p; :b; s/:$//;h;$p' data.txt简化版本(适合无特殊字符的文件):
sed '$!N; /^\(.*\)\n\1$/!P; D' data.txt
示例:data.txt 原始内容
apple banana apple orange banana
输出结果(保留首次出现的行,顺序不变):
apple banana orange
3.3 注意事项
sed去重语法较复杂,尤其对于部分字段去重场景,不如awk灵活。- 适合小规模文件(几 MB 以内),大规模文件建议使用
awk或sort+uniq。
四、性能对比与场景选择
4.1 三种方法性能对比(基于 1GB 文本文件,1000 万行数据)
| 方法 | 时间复杂度 | 执行时间 | 优势场景 |
|---|---|---|---|
sort+uniq | O(n log n) | 约 30 秒 | 需排序、统计重复次数、简单全行去重 |
awk | O(n) | 约 10 秒 | 无需排序、部分字段去重、保留原始顺序 |
sed | O(n²) | 约 120 秒 | 小规模文件、简单全行去重 |
4.2 场景选择建议
- 简单全行去重且需排序:优先使用
sort+uniq(语法简洁,易上手)。 - 复杂场景(部分字段、保留最后一次出现、无需排序):优先使用
awk(灵活高效)。 - 小规模文件、临时快速去重:可使用
sed(无需记忆复杂语法,适合应急)。 - 超大文件(10GB+):推荐
awk或sort --parallel=4 uniq(sort加--parallel参数开启多线程加速)。
五、实战案例:日志文件去重与分析
假设我们有一个 Nginx 访问日志 access.log,内容如下(部分行):
192.168.1.1 - - [01/May/2024:10:00:00 +0800] "GET /index.html HTTP/1.1" 200 1024
192.168.1.2 - - [01/May/2024:10:00:01 +0800] "POST /login HTTP/1.1" 401 512
192.168.1.1 - - [01/May/2024:10:00:02 +0800] "GET /index.html HTTP/1.1" 200 1024
192.168.1.3 - - [01/May/2024:10:00:03 +0800] "GET /api/data HTTP/1.1" 200 2048
192.168.1.1 - - [01/May/2024:10:00:04 +0800] "GET /index.html HTTP/1.1" 200 1024
需求 1:按 IP 去重,统计每个 IP 的访问次数
awk '{print $1}' access.log | sort | uniq -c | sort -nr输出结果(按访问次数降序排列):
3 192.168.1.1
1 192.168.1.2
1 192.168.1.3
需求 2:按 URL 去重,保留每个 URL 最后一次访问的日志
awk '{url=$7; a[url]=$0} END{for(k in a) print a[k]}' access.log | sort -k4输出结果(按时间戳排序,保留每个 URL 最后一次访问记录):
192.168.1.2 - - [01/May/2024:10:00:01 +0800] "POST /login HTTP/1.1" 401 512 192.168.1.1 - - [01/May/2024:10:00:04 +0800] "GET /index.html HTTP/1.1" 200 1024 192.168.1.3 - - [01/May/2024:10:00:03 +0800] "GET /api/data HTTP/1.1" 200 2048
需求 3:去除重复的成功访问日志(状态码 200),保留首次出现的行
awk '$9==200 && !a[$1$7]++' access.log
输出结果(按 IP+URL 去重,仅保留首次成功访问的日志):
192.168.1.1 - - [01/May/2024:10:00:00 +0800] "GET /index.html HTTP/1.1" 200 1024 192.168.1.3 - - [01/May/2024:10:00:03 +0800] "GET /api/data HTTP/1.1" 200 2048
六、总结
Linux Shell 提供了多种文件去重方案,核心可分为三类:
sort+uniq:基础通用,适合简单全行去重、统计重复次数,需排序支持。awk:高阶灵活,适合复杂场景(部分字段、保留特定行、无需排序),效率高。sed:简洁轻便,适合小规模文件和临时去重需求。
到此这篇关于从基础到高阶详解Linux Shell文件去重的完整指南的文章就介绍到这了,更多相关Shell文件去重内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
Linux Bash 提示符的一些骚操作(自定义 Bash 提示符)
这篇文章主要介绍了Linux Bash 提示符的一些骚操作,一些能让你自定义 Bash 提示符的黑科技,需要的朋友可以参考下2017-07-07


最新评论