DEBUG.WIKI
V.PS:让项目靠近用户,亚太、欧洲、北美 KVM VPS
IPPeak:从目标地区访问网页,住宅代理、SEO 监测、广告验证
ZgoCloud:多地区部署,按需选节点;日本、美国、香港、德国

uniq -u 仍留下重复行,或把重复内容整组删掉

uniq 只比较相邻重复组,-u 仅保留单行组。根据排序去重、只取全局出现一次或保留首次顺序的目标选择命令,避免误删记录。

对文本执行 uniq -u 后,同样的内容仍出现两次;换一份输入,重复出现的内容又完全消失。这两个现象都可能来自选项语义:uniq 比较相邻的重复组,-u 只输出组内恰好一行的组。

下文适用于 GNU uniq、sort 和 GNU awk,按默认的换行记录、整行内容处理普通文本。没有使用忽略大小写、跳过字段、数值排序或 NUL 分隔选项;添加这些选项会改变“相同”的判断。

用一份小输入区分两种误解

例如输入文件 sample.txt 是:

alpha
alpha
beta
alpha
gamma

执行 uniq -u sample.txt 时,开头两个连续的 alpha 构成重复组,被整组排除;后面的 alpha 前后都不同,是另一个单行组,会被输出。因此结果应为 beta、alpha、gamma 三行。

不加 -u 时,默认行为是每个相邻重复组输出一行,结果应为 alpha、beta、alpha、gamma。其中 alpha 仍有两行,因为它们属于两个不相邻的组。

所以,去掉 -u 可以保留重复组的一份内容,但不能让 uniq 跨越整个未排序文件寻找重复项。

每种内容保留一份,且允许改变顺序

先确认输出文件不存在,并且不是输入文件的硬链接或符号链接。下面把结果写入一个独立文件:

LC_ALL=C sort -u -- ./sample.txt > ./sample.distinct.txt

对示例输入,结果应是 alpha、beta、gamma。sort 把相同内容聚在一起,-u 再保留一份。LC_ALL=C 固定这里的比较规则,避免环境 locale 参与排序;它也意味着输出采用该规则下的排序,不保留原来的首次出现顺序。

不要顺手加入 -n、-k 或 -f。它们可能把不同整行按数值、部分字段或忽略大小写的规则视为等价,这与逐行内容去重不是同一个要求。

只要在整个文件里出现一次的内容

如果业务要排除所有重复内容,连其中一份也不要,才使用排序后接 uniq -u:

LC_ALL=C sort -- ./sample.txt |
  LC_ALL=C uniq -u > ./sample.singletons.txt

示例里的 alpha 共出现三次,这次会全部排除,只留下 beta 和 gamma。两条命令分别设置 LC_ALL=C;只给管道左侧命令设置环境变量,不会自动给右侧也设置。

需要了解频次时,可先看排序后的计数:

LC_ALL=C sort -- ./sample.txt | LC_ALL=C uniq -c

这里应分别看到 alpha 为 3、beta 为 1、gamma 为 1。核对频次能发现“重复内容保留一份”和“仅保留出现一次”是否选反。

保留第一次出现的顺序

如果记录顺序有意义,可以让 awk 记住已经遇到的整行:

LC_ALL=C gawk '!($0 in seen) { seen[$0] = 1; print }' \
  ./sample.txt > ./sample.first-seen.txt

$0 是当前整行,seen 用整行作为键。第一次遇到时打印并登记,随后相同内容跳过;不会对行排序。示例结果同样是 alpha、beta、gamma,但如果输入先出现 gamma,输出也会先保留它。

这条规则的内存随不同内容的数量增长。对大量不同记录,应先评估内存;不能因为 uniq 处理相邻组很省内存,就认为这个保序方案也有同样的资源需求。示例针对文本行,不适合直接处理多行 CSV 记录或二进制数据。

用记录含义验收,保留原输入

先在小副本里放入相邻重复、间隔重复和只出现一次的记录,核对输出内容与顺序。正式输入如果存在行尾空格、制表符或 CRLF 与 LF 混用,看起来相同的行可能并不相同;是否规范化这些字符需要业务依据,不要在去重时自动删除它们。

所有示例输出都应使用未占用的新文件。不能写成 sort input > input,也不能把错误输出立即覆盖原始记录。检查命令退出状态、磁盘空间和输出内容后,再通过原有流程替换正式文件;管道用于自动流程时,还应分别捕获阶段失败,避免只检查最后一个进程。

回退依赖保留的原文件。去重结果已经丢失重复次数,排序结果也丢失原顺序,不能从结果文件可靠地还原它们。