首页/系统/Linux 运维命令速查/场景:去重同时保留原始顺序
文本处理77 条

场景:去重同时保留原始顺序

sort -u 会丢顺序;要「按首次出现顺序去重」用 awk 的 seen 数组,URL 清单、订单号去重场景常用。

Linux 运维命令速查系统更新于 2026-10-13

保序去重是什么场景

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 就能完成:先计数再在首次出现时打印,避免把文件读两遍。

易错点

  • 直接用 sort -u 代替保序去重,结果顺序被打乱,依赖「最早记录优先」的下游逻辑会得出错误结论;顺序有语义时必须用 awk 保序写法。
  • 去重数组按唯一行数占内存,上亿行清单直接去重可能在内存紧张的机器上触发 OOM;先切分文件分批处理。
  • 不做大小写归一化时,同一项的不同大小写写法被当成两条,域名、邮箱类数据尤其明显;去重前先统一口径。
  • Windows 生成的文件带行尾回车,去重后重复项仍然存在,看起来「去重成功」实则无效;处理前先统一换行符。

数据来源与参考

  • man 1 awk(GNU gawk 手册)

相关工具

​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌