1. 先搞清楚uniq到底在做什么用Linux的人迟早都会遇到uniq但很多人第一次用它的经历都不太愉快——明明是想把重复行去掉结果文件一模一样一行都没少。这不是uniq没干活而是它天生就是个“挑食”的工具uniq只处理相邻的重复行。换句话说如果相同的行没有挨在一起它就当没看见。这一点是理解uniq所有行为的地基。打个比方uniq就像一个只认前后邻居的保安。你在队伍里连续排了三个同一个人他能帮你揪出来但如果同一个人分别站在队头、队中、队尾他就完全无感了。所以“配合sort使用”几乎是uniq的标配姿势sort先把相同内容行排到一起uniq再逐个压掉重复。在处理日志去重、统计访问来源、清洗配置文件这类场景时uniq sort的组合能解决80%以上的文本去重需求。而且它跟awk、sed、cut这些命令搭配起来能玩出很多花活。下面我直接把常用参数和实操案例展开讲每一段都能直接在你的终端里跑。2. 七个必会参数逐个拆解2.1 -c让每个重复项都带上“计数”uniq -c是统计类需求中使用频率最高的参数。它的作用是在每行前面加一个出现次数格式上默认是右对齐的先是空格然后是数字再接一个空格最后是原来的行内容。$ cat test.txt apple banana apple apple cherry $ sort test.txt | uniq -c 3 apple 1 banana 1 cherry注意我在这里先执行了sort因为原始文件里两个apple中间隔着banana直接跑uniq -c只会统计到第一行的apple和第二行连着的apple第三行单独的apple会漏掉。这是uniq新手最容易踩的坑。-c的实际价值不只是告诉你每个值出现几次。在日志分析里它往往是流水线的第一站。比如你拿到一堆访问日志想知道哪些IP最活跃先把IP字段抽出来排序再用uniq -c计数最后按数字倒序排一下直接就能看到排名。$ awk {print $1} access.log | sort | uniq -c | sort -rn | head -10这条命令我几乎每周都用。sort -rn里的-r是倒序-n是按数值而不是字典序排。如果不加-n10会排在2前面因为字典序是“10” “2”。这个细节很多人栽过。2.2 -d 和 -D只看重复项还是连组看默认情况下uniq把重复行只保留一行。但有些时候你偏偏想知道哪些行是重复的或者想看重复行到底长什么样。uniq -d表示只输出重复的行每组重复只输出一行uniq -D表示把所有的重复行都输出一组也不折叠。配合之前的test.txt$ sort test.txt | uniq -d apple $ sort test.txt | uniq -D apple apple apple这里的区别很实用。比如你怀疑配置文件里某个参数被重复声明了-d能快速定位到哪个参数重复了但如果你想进一步分析重复的上下文或者要验证重复项是不是内容完全一致-D把所有重复行都铺开更直观。还有一个值得记的变形uniq -cd。这个组合可以把每个重复项的出现次数也带出来适合做“哪些值重复出现超过N次”的筛选。比如找出现超过2次的重复IP$ awk {print $1} access.log | sort | uniq -cd | awk $1 2 {print}这里第一段sort | uniq -cd先统计每个IP次数第二段awk按数字过滤。两层管道一接问题就解决了。2.3 -u把“只出现一次的行”捞出来-u和-d正好相反它只输出那些在文件中没有重复的行。这个参数在找“孤立项”时很好用。比如你有一份设备序列号清单想知道哪些序列号只在列表里出现一次可能是登记异常或漏号直接排序后加-u$ sort serials.txt | uniq -u-u配合其他命令还能做集合运算。比如有两个文件A和B想找在A中但不在B中的行可以先合并排序再处理。当然专门的comm命令做集合对比更强但sort fileA.txt fileB.txt | uniq -u在行内容不重复的前提下也能起到排查差异的作用胜在命令短、直觉好记。2.4 -i忽略大小写去重日志、配置、尤其是手工录入的数据里大小写不统一是常态。FOX、Fox、fox在uniq看来是三行不同的内容但业务层面它们可能是同一个值。uniq -i会让uniq做比较时忽略大小写差异。注意它影响的是“是否认为两行重复”输出内容仍然保留第一次出现的那一行的原始大小写形态。$ cat mixed.txt Error ERROR error Warning $ sort mixed.txt | uniq -i Error Warning这里有个实际经验-i和sort的配合有个先后讲究。sort默认按ASCII排序大写字母排在小写前面所以Error、ERROR、error会被排成ERROR、Error、error。此时uniq -i会把这三行都归为重复输出时保留第一行也就是全大写的ERROR。如果你想要保留小写形式得先考虑好排序规则或者用sort -f把大小写忽略后再排这样输出的保留形态会不一样。2.5 -w和-s精准控制比较范围有这样一种场景每一行的前一部分是编号/时间戳后一部分才是业务内容。你希望忽略前几位字符只比较后面的内容或者只比较前N个字符。-s N表示跳过前N个字符开始比较-w N表示只比较前N个字符。两个参数可以组合控制比较的范围是一个“滑动窗口”。举个例子有三行日志2024-01-01 ERROR: disk full 2024-01-02 ERROR: disk full 2024-01-03 WARN: cpu load high如果直接用uniq三行互不相同。但我想按“错误类型”去重也就是说想忽略开头的日期部分拿第12个字符之后的内容做比较。首行是10位日期加一个空格要跳过前11个字符$ uniq -s 11 messages.log 2024-01-01 ERROR: disk full 2024-01-03 WARN: cpu load high第二行的ERROR: disk full因为和第一行除日期外完全相同被判定为重复行直接压掉了。反向场景只需要比较每行前固定长度的前缀。比如一批数据行前缀是状态码后面跟详细描述我想按状态码去重$ uniq -w 3 status.log如果前缀包含空格或字段结构复杂直接用-f参数会更优雅见下一节。2.6 -f跳过N个字段-f N表示在比较时跳过前N个字段。这里的“字段”默认由空白字符空格或Tab分隔跟awk默认的字段分隔方式一致。典型场景是处理“用户名 邮箱”这种两列数据。比如passwd文件里每行是用户名:x:UID:GID:描述:家目录:Shell你只想比较UID那一列就能写出类似这样的命令$ sort -t: -k3,3n passwd | uniq -t: -f2不对这里要纠正。uniq本身不支持-t指定分隔符它只认固定的“空白字符作为字段分隔”。所以如果文件不是用空白分隔的就没办法直接用-f得先用tr或awk转换。-f最顺手的使用场景是处理空格分隔的文本。比如一份记录文件每行是日期 用户名 操作想按“用户名”去重即同一用户只保留一条操作记录$ sort -k2,2 data.txt | uniq -f1-f1表示跳过第一个字段日期从第二个字段用户名开始比较。这里注意sort也按第二个字段排序保证同一用户的记录相邻否则uniq照样“看不见”。2.7 参数组合-c -d -i -s一起上实际工作中很少只用一个参数组合起来才能解决真实问题。举一个常见的混合场景分析一批大小写混乱、带有重复前缀的统计文本。$ cat raw.log 2024-05-01 Error: timeout 2024-05-01 ERROR: timeout 2024-05-02 error: timeout 2024-05-03 warn: retry $ sort -f raw.log | uniq -cid -s 11 3 error: timeout我解释一下这条命令的执行过程sort -f先忽略大小写排序uniq -c要求带计数-i表示比较时忽略大小写-d表示只显示重复项-s 11跳过每行前11个字符。最终输出把三个“不同日期但同样错误信息”的行合并成一组并给出次数3。这样既忽略了前置日期又忽略了大小写还只保留了重复内容一行命令就把多维度的处理都做完了。3. 四个真实场景实操复盘3.1 场景一网站访问日志IP热度统计从Nginx或Apache日志里统计访问最多的IP是uniq最经典的应用。日志大概长这样192.168.1.10 - - [01/May/2024:12:00:01 0000] GET /index.html HTTP/1.1 200 512 192.168.1.20 - - [01/May/2024:12:00:03 0000] GET /about.html HTTP/1.1 200 340 192.168.1.10 - - [01/May/2024:12:00:05 0000] GET /images/logo.png HTTP/1.1 304 0完整统计命令是$ awk {print $1} access.log | sort | uniq -c | sort -rn | head -20四段管道各干一件事awk抽IP列sort把相同IP排成相邻uniq -c给每个IP计数sort -rn按次数从高到低排head只取前20名。输出的样例是1023 192.168.1.10 876 203.0.113.5 512 198.51.100.7这里有个性能细节值得说当access.log非常大时管道里的中间数据都会经过排序内存占用和磁盘IO都不小。这时有一个替代方案直接用awk做统计不排序速度反而更快$ awk {count[$1]} END {for (ip in count) print count[ip], ip} access.log | sort -rn | head -20awk用关联数组做计数不需要排序相同行省掉了一大块开销。但代价是如果IP种类极多比如百万级awk也会占用大量内存。对于一般规模的日志两种方案都够用我个人的习惯是日志文件在几百MB以内用sort uniq超过这个量级就改用awk。3.2 场景二两份列表求交集差集假设你有两个文件一个是本周活跃用户ID一个是上月活跃用户ID都是每行一个ID。想找出“本月新出现的用户”即在本月列表中但不在上月列表中的。一个直观做法$ cat this_week.txt last_month.txt | sort | uniq -u等等这有个逻辑问题。求差集不能简单地用uniq -u因为如果两个文件里都有同一个ID它会出现两次uniq -u会直接把它丢弃因为有重复但我们要找的是“只在本月不在上月”的ID正确做法是把两个文件打标区分。正确的姿势是用comm或者给文件加标记$ awk NRFNR {seen[$1]1; next} !seen[$1] last_month.txt this_week.txt这条awk命令干的事是读第一个文件last_month.txt时记录所有ID到seen数组读第二个文件this_week.txt时如果某行ID没出现在seen里就输出。这是真正意义上的差集。那uniq在这种场景里还有用吗有。如果只是想看“两个文件合并后哪些元素不重复”sort file1 file2 | uniq -u是合理的。它表达的是集合里孤零零出现一次的元素。这个语义在“找两边都没配对的数据”时很有用。做数据校验时我经常拿它快速扫一眼合并结果里的异常孤立值。3.3 场景三配置项重复声明检查曾经处理过一份服务配置文件几千行里面有可能存在重复的配置项声明。排查方法是把配置项名称每行第一个字段抽出来排序再统计重复项。$ awk {print $1} app.conf | sort | uniq -d如果发现输出里有配置项名称再用grep -n定位每个重复声明的具体行号$ grep -n ^timeout app.conf这种“先用uniq找异常再用grep定位”的模式排查文本问题特别顺手。uniq -d在这里相当于一个探测器只报异常不报正常输出量小人眼扫起来不累。3.4 场景四脚本里的标志物去重写Shell脚本时经常会遇到一段文本里出现了某个标志多次需要合并成一次。比如把多个来源的IPv4地址合并后去重生成防火墙白名单$ cat ip_list_*.txt | sort -u whitelist.txt等一下这里我用了sort -u而不是sort | uniq。原因在于sort -u在排序的同时直接去重一步到位效率更高而且语义更清晰——“排完顺便去个重”。那uniq的定位在哪uniq的优势在于“我已经有了一段排好序的数据只想做轻量去重”或者需要计数、保留重复组等sort -u做不到的操作。脚本里比较经典的uniq用法是处理“连续重复标记”。比如一个上报系统会连续输出多行相同的状态信息脚本里要去掉连续重复只保留变化点就用$ uniq monitor.log这里故意不排序因为我们要的恰恰是“相邻去重”这个语义保留时间线的变化不把历史记录全部打乱。这说明uniq即使不配合sort也有自己的用武之地关键看你想表达什么逻辑。4. 进阶玩法uniq与兄弟命令的协同4.1 “两步走”和“一步到位”sort -u vs sort | uniq很多初学者会问sort -u不是更简单吗为什么还要分开写sort | uniq答案是sort -u只解决“去重”一个需求而uniq有计数、保留重复组、忽略大小写、跳过字段等一系列附加能力。当需求从“去重”变成“去重并统计次数”时sort | uniq -c是唯一的选择。sort -u做不到计数。另外还有一个性能细节。sort -u在排序过程中去重内存消耗通常比sort | uniq略低一点因为它在合并阶段就直接丢弃重复项减少了最终输出和后续管道的负载。从执行结果看两者完全一样但如果你在管道中间还有其他处理分开写反而更灵活。4.2 去掉uniq -c输出的前导空格uniq -c输出的计数默认右对齐前面带了若干空格。这在人眼阅读时很整齐但如果你想把它喂给其他程序或者转成CSV前面的空格就成了干扰。一个常见的清理手法是用sed或awk$ sort test.txt | uniq -c | awk {$1$1; print}awk {$1$1; print}的作用是强制awk重算整行把字段之间的多个空格压缩成单个空格并且去掉行首空格。输出变成3 apple 1 banana 1 cherry如果你想把计数和内容调换位置比如变成内容,计数的CSV格式可以这样$ sort test.txt | uniq -c | awk {print $2, $1} | tr , apple,3 banana,1 cherry,14.3 和cut结合按字段抽完再统计有一个场景日志文件是空格分隔的你想统计第二列里的值分布。比如$ cut -d -f2 access.log | sort | uniq -c | sort -rncut先把字段抽出来后续流程和之前一样。这个组合在“字段名称已知、结构固定”的文件里非常顺手比awk省一点心智负担。值得注意的是如果文件字段之间是多个连续空格cut -d 可能会抽出空字段。这种时候用awk按默认空白分隔反而更稳。我个人的经验是结构规整的空格分隔文件用cut字段分隔不规整的用awk尽量别纠结。4.4 处理Windows换行符的坑从Windows传过来的文本文件每行结尾是\r\n而不是\n。用uniq去重时因为每一行末尾都粘着\r看似内容相同的两行可能实际上是apple\r和apple\r——这种就算相同了。但更多时候是内容里有不同比如一行是apple\r另一行是apple肉眼一样uniq判定为不同行。这不是uniq的问题是数据源的编码问题。处理办法是先把\r去掉$ sed s/\r$// windows.txt | sort | uniq -c每次从同事那边拿到Windows生成的日志我都会习惯性先清理换行符。否则统计结果可能莫名其妙多出许多“看似重复实际不重复”的数据。4.5 用uniq辅助做“批次边界检测”假设你的程序每输出1000行日志会打一个特殊标记行---BATCH---你想确认标记之间是否重新计数。把标记行也拉出去重看是否有意外重复的批次标记$ grep BATCH app.log | uniq -d如果有输出说明标记行重复了可能是程序逻辑出了问题。这个用法利用的正是uniq“相邻重复判断”的特性不需要排序直接沿着时间线检查“连续出现两次相同标记”的异常。这类场景在监控脚本里能省不少事。5. 常见问题与排查实录在终端里跑uniq踩过的坑我整理成了速查表基本覆盖我日常遇到的大部分问题现象原因解决办法uniq执行后文件一行没变相同行不相邻uniq只处理连续重复先sort再uniq或改用sort -u计数结果右对齐空格太多uniq -c默认输出格式如此用awk {$1$1; print}清理空格重复行没被识别行尾有\rWindows换行符先执行sed s/\r$//再处理大小写不同的行被当不同行uniq默认区分大小写加-i参数想去掉前几位时间戳再判断日期/前缀干扰了比较用-s N跳过前N个字符字段分隔符不是空格uniq -f只认空白字符先用tr或awk转换分隔符去重后顺序全变了排序步骤导致确认需求需保留顺序时改用awk维护数组大文件统计卡顿sort排序开销大改成awk直接用关联数组计数只看到重复项里的一行-d和-D语义不同需要全部行时改用-D中文内容识别异常locale排序环境影响设置LC_ALLC或LC_ALLzh_CN.UTF-8后重试这里重点说一下中文排序环境的坑。当文本包含中文时sort的排序结果受locale环境影响很大。在某些locale下中文并不会按你预期的“拼音顺序”或“Unicode顺序”排列而是可能按字节序排。这本身不影响uniq判断“是否相同”但会影响哪些相同的行能被排到一起。如果中文数据去重结果不对劲先把LC_ALL设置成一致的环境$ LC_ALLC sort data.txt | uniq -c设置为Clocale后排序依据是字节序结果确定性强不容易受当前系统环境干扰。这个方法在和其他同事协作、复现问题时尤其重要。还有一个常见误操作是直接把uniq的结果重定向回原文件$ sort test.txt | uniq -c test.txt # 危险因为Shell的重定向会在命令执行前就清空原文件等管道里的命令真正开始运行时文件内容已经没了。正确做法是先输出到临时文件再覆盖原文件$ sort test.txt | uniq -c tmp.txt mv tmp.txt test.txt这个坑我在刚学Shell时踩过弄丢过一份还没备份的数据从那以后凡是要覆盖原文件我都坚持“先写临时文件再mv”的流程。多说一句sort -o test.txt test.txt这种写法是安全的因为sort -o对输入输出同一文件做了特殊处理但uniq没有对应的-o参数所以别指望它。6. 用过的都说好留下几句实在话uniq这个命令表面上简单但它真正考验的是你对“相邻”这两个字的理解。所有诡异表现只要你记住它的工作前提是“只处理连续出现的重复行”就都能解释通了。使用uniq超过半年后我养成了一个习惯凡是涉及文本去重的任务先问自己三个问题——数据是否有序比较时要忽略哪些维度输出时要保留哪些字段把这三个问题想清楚命令基本一次就能写对。最常用的组合里sort | uniq -c是绝对的榜首几乎任何“统计出现次数”的需求都能套这个模板。其次就是sort | uniq -d定位重复项时输出干净利落。真正让我离不开uniq的反而不是这些常规用法而是它在管道里那种“轻量、专注、只做一件事”的气质。处理文本时一个命令只解决一个问题管道再长也清晰可控。最后分享一个我从某个维护老系统的同事那里学到的技巧把sort -u写成Shell函数加一个交互确认防止误覆盖数据文件。工具本身是死的但使用习惯是活的防御性地处理数据文件什么时候都不吃亏。