保序去重是什么场景
sort -u 会重排,uniq 只能去掉相邻重复行,而「按第一次出现的顺序去重」是另一个真实需求:处理 URL 清单、订单号、访问日志里的访客列表时,顺序本身就是信息,比如要保留最早出现的那条记录。标准解法是 awk 的 seen 数组配合 !seen[$0]++ 这个惯用法,再按需要换成忽略大小写、按指定字段去重等变体。它一行命令就能完成,比排序去重再恢复顺序省事得多。
基本信息
| 适用平台 | GNU/Linux、macOS(awk 为标准工具,gawk 与 mawk 均支持) |
|---|---|
| 核心写法 | awk '!seen[$0]++' |
| 常见变体 | tolower 忽略大小写,$1 按字段去重 |
| 内存特征 | 占用取决于去重后的唯一行数,与输入行数无关 |
| 对比 | sort -u 会重排,uniq 必须先去重排序 |
| 超大文件 | 先按前缀切分再分批去重,或改走排序去重路线 |
典型使用场景
- 清洗抓取结果里的 URL 列表,去重同时保留抓取顺序。
- 去重订单号或请求 ID,并保留最早出现的那一条位置。
- 按首次出现顺序统计日志中的客户端 IP,还原访问路径。
- 生成配置列表时按原顺序去重,避免配置顺序被排序打乱。
- 忽略大小写去重邮箱地址,同时保留第一次出现时的原始写法。
常用写法
常用写法
awk '!seen[$0]++' url.txt > url.uniq.txt # 按首次出现顺序去重
awk '!seen[tolower($0)]++' mail.txt > mail.uniq # 忽略大小写去重
awk -F, '!seen[$1]++' orders.csv > orders.uniq # 按第一个字段去重
wc -l url.txt url.uniq.txt # 对比去重前后行数
awk '!seen[$0]++' url.txt | head -5 # 抽样确认顺序未被改动
awk '!seen[$0]++{print NR": "$0}' url.txt # 带原行号输出,便于定位
LC_ALL=C sort -u url.txt > set.txt # 集合口径核对用
comm -3 set.txt <(LC_ALL=C sort url.uniq.txt) # 两条路线结果应完全一致写法与语义对照
| 写法 | 作用 | 使用要点 |
|---|---|---|
| !seen[$0]++ | 整行保序去重 | 首次出现时计数为 0,取反为真即打印 |
| tolower($0) | 忽略大小写 | 需要输出原始大小写时不要覆盖整行 |
| $1 或 $2 | 按字段去重 | 先用 -F 指定分隔符,处理 CSV 常用 |
| seen[$0] 计数 | 统计出现次数 | 去掉取反即得到每行出现次数 |
| sort -u | 排序去重 | 集合结果相同但顺序丢失 |
| uniq | 相邻去重 | 必须排序后再用,单独用会漏非相邻重复 |
内存占用取决于去重后的唯一行数,而不是输入总行数:几百万唯一行会占用可观内存,超大文件可先按哈希前缀切分成多个小文件,各自去重后再合并。
行的不可见差异会让相同内容漏去重:行尾多了回车、行首行尾有空格、制表符与空格混用都会造成额外条目。去重前用 cat -A 抽样看一眼,必要时先统一清理。
需要同时输出「每行出现次数」和「保序清单」时,一个 awk 就能完成:先计数再在首次出现时打印,避免把文件读两遍。