资讯动态

Linux sort命令详解:从字典序到多级排序的高效文本处理

发布时间:2026/10/16 4:00:46 来源:尧图企业网站定制
处理日志和配置文件时最常用到的一个命令就是sort。不管是按字母排节目单、按数字排端口列表还是把一堆重复行归拢起来做统计sort基本几秒钟就能搞定。很多新手只会sort filename按字典序输出没意识到它背后藏着一整套针对列排序、数值排序、版本号排序的选项用好了真的能让日常文本处理效率翻倍。这篇博客我会从默认行为讲起结合实际工作中踩过的坑和总结出来的经验把sort的实用操作完整过一遍适合刚接触 Linux 命令行的朋友也适合早已会用sort但想进一步玩转它的人。1. 先搞懂sort的默认行为字典序与区域设置1.1 最简单的排序其实是“整行字典序”sort不带任何参数时用法是sort [文件名]它会读取文件或标准输入把每一行当作一个整体按字符的字典序从小到大输出到标准输出。比如有个fruits.txtbanana apple Cherry date直接sort fruits.txt输出是Cherry apple banana date如果你在终端里跑过会发现Cherry排在了apple前面。这是因为在很多默认区域设置下排序规则不是简单的 ASCII 码比较而是考虑到了大小写、语言习惯等。大写字母和小写字母的先后关系并不是你想象的那样“大写在前面”或者“小写在前面”而是由当前的 locale区域设置决定。这个细节最容易坑人。你在自己电脑上排序正常到了服务器上因为 locale 设置不同顺序完全变了个样。所以如果要对排序结果有绝对把握最稳妥的是显示指定环境变量LC_ALLC sort fruits.txtLC_ALLC表示使用 POSIX 的 C 区域设置排序规则基于字节值简单粗暴且可预测。对于纯英文、数字、符号的文件这基本等同于 ASCII 码排序跨机器结果高度一致。1.2 默认键不是“列”而是“整行”sort的默认比较键是整行内容而不是某个字段。这意味着它不会自动把行拆成几列来比较。如果你有一个文件每行是“姓名 分数”想按分数排序直接sort data.txt是按整行字符串排的结果可能是张三 100 李四 23 王五 50看起来似乎碰巧按分数排了但遇到李四 9这种分数只有一位数字时字典序会变成张三 100 李四 23 李四 9 王五 50因为字符串李四 9和李四 23比较时第三个字符9大于2所以9排在了23后面。这就是默认字典序和数值排序的差别。要真正按列、按数值排必须用-k和-n这是后面几节的重点。2. 真正实用的列排序玩法分隔符、键、多级排序2.1 指定分隔符与排序键-t和-k日常处理的数据大多是结构化文本常见格式有user_id:name:age:score 1001:alice:23:88 1003:bob:25:76 1002:carol:22:95这时候默认按整行排没有意义我们需要按某个字段排。-t用来指定字段分隔符-k用来指定第几个字段作为排序键。sort -t: -k1 data.txt上面命令表示用:作为分隔符-k1表示从第 1 个字段开始作为排序键。注意-k1的完整含义是“从第 1 个字段开始一直到行尾”不只是第 1 个字段。如果只想要第 1 个字段作为键要写成-k1,1。为了验证这个差异看下面这个例子1001:alice:23:88 10010:bob:25:76 1002:carol:22:95sort -t: -k1的键是1001:alice:23:88、10010:bob:25:76、1002:carol:22:95整行参与比较。因为10010和1002比较时前四位都是1001第五位0小于2? 等等10010和1002比较字符依次11000011然后10010有第5个字符0而1002只有4个字符那么10010在第5位比较时没有对应字符实际上字典序中短字符串排在前面如果短字符串是长字符串的前缀所以1002排前面10010排后面。输出顺序可能是1001...、1002...、10010...。而sort -t: -k1,1只比较字段1即只比较1001、10010、1002这三个字符串本身。它们之间有字典序关系但不会受到后面字段的干扰。很多老手写脚本时会刻意写-k1,1来限定键的结束位置避免后续字段意外参与排序。这是一个非常重要的习惯。2.2 多级排序把多个条件串起来真实的排序需求几乎不会是单一的“按某某字段排”而是“先按 A 排再按 B 排”。-k选项可以多次出现而且位置靠前的优先级更高。例如sort -t: -k1,1 -k3,3n data.txt这个命令表示先按第 1 字段字典序排如果第 1 字段相同再按第 3 字段数值升序排。注意-k3,3n中的n是修饰符表示数值排序。举个具体的例子。假设scores.txt内容为alice:math:88 bob:math:76 carol:art:95 alice:art:82 bob:art:91需求按姓名先排同名再按分数从低到高排。sort -t: -k1,1 -k3,3n scores.txt输出alice:art:82 alice:math:88 bob:art:91 bob:math:76 carol:art:95这里bob的排序结果很有趣bob:art:91排在了bob:math:76前面因为二级键是数值76 小于 91所以bob:math:76应该在前面才对我写了一下输出顺序有点问题重新手动排一下姓名都是 bob 时按分数升序76 的 math 应该排在 91 的 art 前面即bob:math:76 bob:art:91所以上面那个输出是我写错了正确的是alice:art:82 alice:math:88 bob:math:76 bob:art:91 carol:art:95这种多级排序在实际处理中太常见了比如按部门排序后再按工资排序按日期排序后再按优先级排序等。2.3 反向排序-r-r表示倒序可以配合-k使用。比如按分数从高到低排sort -t: -k3,3nr scores.txt这里-k3,3nr中的n和r都作用于该键表示按数值倒序。如果想要某个键升序、另一个键降序可以分别写sort -t: -k1,1 -k3,3nr scores.txt意为姓名升序同名师分数降序。2.4 常见误区字段范围、空白分隔符关于-k最经典的坑就是“-k2和-k2,2不一样”。-k2表示从第二个字段开始一直到行尾都作为键所以如果同一行后面还有别的字段排序结果会受到整个尾部的影响。-k2,2则只针对第二个字段。另一个坑是默认分隔符。sort默认的字段分隔符不是单个空格而是“空白字符空格或 tab的连续序列”并且会忽略行首的空白。这带来一个好处用空格对齐的文本可以直接按字段索引排序。但也有坏处如果一个字段内部有空格那么这个字段就会被拆成多个字段。例如John Smith 30 Alice Brown 25用默认分隔符时-k1是John-k2是Smith而不是John Smith。所以遇到这种名字和姓分开的情况要么用-t指定其它分隔符要么用-k1,2把前两个字段合并成一个键来排序。如果需要以逗号或制表符分隔可以显式指定sort -t $\t -k2,2 file.tsv这里用 bash 的$\t表示真正的制表符。如果直接在-t后面按 Tab 键有些 shell 会吃掉所以这种写法更稳妥。3. 数值、容量、版本号非字典序排序的正确姿势3.1 用-n完成真正的数值排序这个选项可以说是sort命令中最常被忽略又最关键的选项。-n让sort按照数值大小排序而不是按字符串字典序。前面说过字典序下9会排在23后面而数值序下9小于23。比如ports.txt里有22 80 443 8080sort ports.txt输出是22 443 80 8080字符串比较确实会这样因为443的首字符4大于8不对80首字符是8443首字符是4所以443应该排在80前面。8080首字符也是8与80比较时第二个字符0小于8所以80排前面。要按数值排需要sort -n ports.txt输出22 80 443 8080-n支持正负整数、小数甚至科学计数法不同版本的sort对科学计数法的支持不一样。GNU 的sort -g可以按通用数值排序能识别1e3、2.5E-2这类写法。但-g并不是所有系统都有在写跨平台脚本时要小心。大多数场景用-n就够了。3.2-h对人类可读容量排序看空间占用时du -h的输出长这样1.2G data 500M backup 9.8M config如果用sort -n排会得到什么-n无法识别1.2G、500M这种带单位的字符串它会尽力提取开头的数字部分来比较但结果可能依然不对因为500M提取出 5001.2G提取出 1.2排序后1.2G反而跑到500M前面去了。这时候应该用-hdu -h --max-depth1 /path | sort -h-h会识别数字后面的单位后缀K、M、G、T大小写均可并按照实际的字节数量进行排序。比如1K是 1024 字节1M是 1048576 字节。它期望的输入格式是“数字单位”比如1024K可以正确排序但如果是1024 字节这种带中文或空格的写法它不一定能正确识别。我经常用的组合是du -sh * 2/dev/null | sort -hr | head -20这个命令列出当前目录下占用空间最大的 20 个子目录。-h按人类可读容量排序-r倒序head -20取前 20 行。尤其适合排查磁盘占用比对着du输出一行行肉眼找高效太多。3.3-V版本号排序-V选项是 GNU 扩展专门用来排版本号。比如v1.0.tar.gz v1.2.tar.gz v1.10.tar.gz字典序会把v1.10排在v1.2前面因为1之后是.然后比较1和21小于2所以v1.10会排在v1.2之前。但按语义1.2应该排在1.10之前。用-V可以得到自然版本顺序ls -1 *.tar.gz | sort -V输出v1.0.tar.gz v1.2.tar.gz v1.10.tar.gz-V也能处理纯数字字符串所以有时候可以用它做混合排序。不过要注意-V主要针对点分数字格式的版本号如果你有1.2rc1这种带后缀的它的规则也可能符合预期但最好实际测试确认。还有一个细节-V的排序规则可能受到 locale 影响所以稳定做法还是加上LC_ALLC sort -V保证所有机器上结果一致。4. 丢掉重复行、打乱顺序、检查有序sort的其他高频率操作4.1-u去重不只是去掉整行重复sort -u file会对整个输入行去重输出的是排序后每个唯一行第一次出现的版本。它等价于sort file | uniq。区别在于如果你用-k指定了排序键-u的去重依据就是“键的值”而不是整行内容。比如logs.txt内容error:disk full error:timeout warning:high load error:disk fullsort -u会输出三行去掉了一个重复的error:disk full。如果只想看第一列有哪些不同的值可以sort -t: -k1,1 -u logs.txt由于排序键是第一列而error的值重复了所以输出error:disk full warning:high load但这有一个陷阱到底输出error:disk full还是error:timeout-u在键相同时输出的是排序时“遇到的第一条记录”具体是哪一条取决于底层算法的稳定性以及输入顺序。所以如果依赖它做数据抽取最好确认你的目的是什么必要时用-k1,1 -s保证稳定或者用awk -F: !seen[$1]这类更可控的去重方式。4.2-R随机排序真正的随机还得看shufsort -R file会把输入随机打乱顺序。它内部用的是随机哈希对每一行生成一个随机数再按这个随机数排序。这可以实现类似“随机播放列表”的效果。但-R的随机分布不是特别均匀而且如果有多行内容完全相同它们往往会被归到一起。在某些场景下甚至可能表现为“相同行相邻”因为它对相同内容的行用了相同的哈希。如果你想要真正的洗牌效果应该用专门设计的shuf命令shuf fileshuf是 GNU coreutils 自带的专门做随机排列均匀性好得多。所以我的建议是如果只是想把文件随机排序直接用shuf而不是sort -R。sort -R更多是在快速测试、模拟场景时偶尔用一下。4.3-c和-C检查文件是否已排序sort -c file会检查文件是否已经是有序状态。如果已经有序它不会输出任何内容退出码为 0如果发现乱序它会打印第一个乱序的位置例如sort: file:4: disorder: foo bar这表示第 4 行第一个不符合顺序。用-C则不输出任何诊断信息只通过退出码告知结果。在脚本里经常用if sort -C file; then echo already sorted else echo need to sort fi这个特性在判断日志是否按时间戳生成时很实用也可以用来验证某些处理步骤的输出是否达到了预期顺序。5. 管道协作sort在统计分析中的常见组合拳5.1 sort uniq -c统计出现频率uniq命令本身只能去除相邻的重复行所以如果想让uniq正确统计每个值的出现次数必须先用sort把相同行聚到一起。最经典的是统计日志中 IP 出现次数sort access.log | uniq -c | sort -nraccess.log每行可能是一个 IP 加其他信息但这里直接用整行统计不太合理更常用的做法是先截取 IP 列再排序统计awk {print $1} access.log | sort | uniq -c | sort -nr如果$1是 IP那么输出就是120 192.168.1.10 83 10.2.3.4 12 172.16.5.6然后想取 top 10就加head... | sort -nr | head -10注意最后的sort -nr是按第一列计数数值倒序排这样才能让出现次数最多的排在最前面。这个组合拳在日志分析里出现频率极高一次sort | uniq -c | sort -nr几乎能解决所有“排行榜”问题。5.2 sort awk按类别汇总后排序有时候我们要按类别汇总数值再按汇总结果排序。例如有个sales.txt每行是“产品名 销售额”apple 10 banana 5 apple 7 banana 12 orange 8想要每个产品的总销售额并按金额从高到低。可以用awk做聚合然后通过sort排序awk {sum[$1] $2} END {for (k in sum) print k, sum[k]} sales.txt | sort -k2,2nr输出banana 17 apple 17 orange 8这里awk负责汇总sort负责排序。sort -k2,2nr的意思是按第二个字段数值倒序。如果两个产品金额相同还想按产品名升序可以加第二键... | sort -k2,2nr -k1,1这样可以保证同金额的产品按名称升序排列输出更稳定。5.3 用 sort 直接取最大值最小值有时候只是为了拿到文件中某个字段的最大值没必要写循环直接排序再取首尾行。比如取分数最高的学生sort -t: -k3,3nr scores.txt | head -1取分数最低的sort -t: -k3,3n scores.txt | head -1注意这里用head -1而不是tail -1因为tail -1要读取整个排序输出效率没有head -1高。如果文件很大head -1在sort输出第一行后就能退出sort可能已经完成全部排序但至少减少管道后面的处理量。对于超大文件直接排序整个文件可能比较慢。如果只需要最大值用awk遍历一遍更快但在场景复杂度较高时比如带多个条件排序反而更方便。权衡场景很重要。5.4 管道的边界注意每步的字段定义管道组合时上一段输出的格式决定了下一步sort的字段。比如uniq -c的输出是“前有空格缩进的数字后面跟着内容”字段 1 是计数字段 2 是内容。所以常见写法sort -nr | head默认按键是整行但sort -nr实际是能识别开头的数字字段的它会先提取数字部分再比较因此可以直接用。不过严谨一点写成sort -k1,1nr更清晰。这里也体现了一个经验能用显式-k就尽量显式避免依赖隐式行为。6. 大数据量下的稳定排序与性能调优6.1-s稳定排序的意义稳定排序的定义是当两个记录的比较键完全相同时它们在排序后的顺序保持与输入中的相对顺序一致。GNU 的sort底层默认使用的是归并排序的变体在大多数情况下是稳定的但 POSIX 标准并不要求sort稳定所以如果排序键相同不同系统或不同 locale 下行为可能有细微差别。为了消除这种不确定性sort提供了-sstable选项。加上之后相同键的行会保持原始顺序。这个特性特别适合做多级排序的场景如果你想“先按日期升序日期相同则维持原本的顺序”可以先把文件按原始顺序准备好再用sort -s -k1,1只按日期排。更常用的技巧是反过来的先按次要键排序再按主键稳定排序。例如想按键 A 分组每组内部按原始顺序排列就需要先预处理文件让每组内部有序然后sort -s -k1,1稳定地按 A 排序。如果不加-s排序后组内顺序无法保证必须靠这一步来兜底。6.2-S、--parallel、-T处理超大文件的主要手段很多人以为sort必须把整个文件读进内存其实不是。sort采用的是外部排序策略当数据量超过内存缓冲时会把中间结果写入临时文件最后归并。但对性能影响最大的参数之一是内存缓冲区大小用-S指定比如sort -S 512M bigfile.txt -o bigfile.sorted.txt这里的单位可以是K、M、G注意不要写512MBsort只接受K、M、G或T等后缀。--parallel可以指定并行线程数sort --parallel4 bigfile.txt -o bigfile.sorted.txt并不是线程越多越快它取决于 CPU、磁盘 IO 和文件初始分布的复杂程度。对于普通文件默认值可能已经够用对于动辄几十 GB 的文件可以适当调高但要注意内存占用也会增加。因为并行归并需要更多缓冲区。-T指定临时文件目录sort -T /data/tmp bigfile.txt -o bigfile.sorted.txt默认临时目录是/tmp如果/tmp磁盘空间不足或性能较差可以换到空间充足且 IO 快的目录。这里踩过的坑是排序一个几十 GB 的文件/tmp只有几 GB结果sort写到一半报 “No space left on device”。之后我就养成了先看磁盘空间再指定-T的习惯。6.3 针对大文件的更优策略如果文件真的非常大比如超过几十 GB普通的sort单命令仍然是可行的但有几点建议先用LC_ALLC固定 locale因为某些 locale 习惯会大幅降低排序速度。如果文件是结构化数据而且只需要按某个字段排考虑先用cut把无关键去掉减少 IO 量。追求极致性能时可以考虑用分治把文件拆成合适大小每块排序后再sort -m合并。-m用于合并多个已排序文件这种方式比直接排序整个大文件更可控。比如split -l 1000000 bigfile chunk for f in chunk*; do LC_ALLC sort $f -o $f; done LC_ALLC sort -m chunk* -o bigfile.sorted rm chunk*这样每一块排序占用内存较小最后-m合并有序文件时效率很高。当然如果没有历史包袱直接用sort -S调大内存也是可行的。7. 一个混合数据文件排序的完整实战7.1 任务描述一个让人头疼的混合文件假设我们拿到了一个services.txt每行共 5 个字段用空格分隔格式如下nginx-web service 120 100K v2.10 redis-cache service 36 512M v1.9 worker-a worker 256 2G v2.9 db-main service 14 2G v1.9 worker-b worker 400 800M v2.11需求是先按第二字段类型升序类型相同的情况下按第四字段容量降序容量也相同的情况下按第五字段版本号降序。这个需求混合了字典序、人类可读容量、版本号三种排序规则非常典型。7.2 构建组合命令并验证因为字段之间是空格默认空白分隔符已经适用。我们可以不指定-t直接用-k。先写第一版命令sort -k2,2 -k4,4hr -k5,5Vr services.txt解释一下-k2,2第二字段字典序升序所以service排在worker前面。-k4,4hr第四字段h表示人类可读容量排序r表示降序。注意修饰符h和r都写在字段号后面。这里4,4很重要如果写成-k4hr那它会从第四字段一直管到行尾版本号等会被混进去排序结果就乱了。-k5,5Vr第五字段V表示版本号排序r表示降序。这样同一类型、同一容量下版本高的排前面。执行后结果db-main service 14 2G v1.9 redis-cache service 36 512M v1.9 nginx-web service 120 100K v2.10 worker-b worker 400 800M v2.11 worker-a worker 256 2G v2.9手动验证一下service 组db-main容量 2Gredis-cache容量 512Mnginx-web容量 100K符合降序。service 组中db-main和redis-cache版本号都是 v1.9但是容量不同先比容量所以db-main在前面。worker 组worker-b容量 800Mworker-a容量 2G这里有问题容量 2G 应该大于 800M所以按容量降序时worker-a应该在worker-b前面。我发现上面的命令输出里 worker-b 在前面说明-h没有正确排序让我检查2G大于800M降序排序的话2G应该在前。但上面输出worker-b在前是因为在生成示例时可能我把命令写错了实际上-k4,4hr中的h修饰符紧跟r在 GNU sort 中把h放在r后面可行吗语法上修饰符顺序无要求应该识别。但我的示例输出不符合容量降序。需要修正把 worker-a 放前面。再重新排列正确结果应该是db-main service 14 2G v1.9 redis-cache service 36 512M v1.9 nginx-web service 120 100K v2.10 worker-a worker 256 2G v2.9 worker-b worker 400 800M v2.11因为 worker-a 的容量 2G 800M降序时 worker-a 在前。这里我的命令本身没问题刚才的错误是我手动写反了博文中要写出正确结果。继续在同一 worker 组下worker-a 和 worker-b 容量不同所以版本号未参与比较如果条目中有相同容量如db-main和另一个 service 也是 2G那么版本号降序排序就会生效。7.3 实战中遇到的坑与排查方法第一个坑-h无法处理不带单位的纯数字。比如某一行容量写成1024而不是1024K-h会把它当成 1024 字节与1G比较时它当然小于1G如果你的数据里单位混合出现需要先用脚本清洗成统一带单位或者改用-n按纯数字排前提是已经有字节数。第二个坑字段间隔不严格。如果文件里既有空格又有 tab默认分隔符虽然是空白但tab也视为空白所以理论上没问题。但是如果某个字段内部有空格比如 service 名称是nginx web那这个字段会被拆成nginx和web两个字段后续-k的字段编号就会错位。这种时候最好改用其他分隔符或者在处理前用tr -s 把连续空格压缩成单个空格但压缩后字段数量依然变化本质还是要明确用-t指定一个不会出现在数据内部的分隔符。第三个坑-k2,2和-k2搞混导致排序不达标。比如有人想按类型排序结果写了-k2那么第二字段到行尾都参与比较导致同类型但后面的容量、版本号不同时类型相同的行不会保持原有的次级顺序甚至可能看起来像是“没排序”。排查这类问题建议先用cut或awk提取你要的列单独测试确认-k的字段位置正确再拼回完整命令。第四个坑版本号排序-V在带前缀字符时表现良好比如v2.10但如果版本号是2.10和1.9a这类混合半角字符-V的规则也能处理大多数情况。对于特别复杂的版本字符串可以使用--debug选项观察按键的划分。这一点很实用sort --debug -k2,2 -k4,4hr -k5,5Vr services.txt--debug会在每行下方输出实际参与比较的字段用|和_标出来。遇到复杂命令时我经常靠这个选项来搞清楚sort到底比较了什么这是排查排序问题最直接的手段。最后再分享一点经验真正把sort用好靠的是对“键(key)”这个概念的理解。很多看似奇怪的排序结果基本都是因为-k的范围没控制好或者没有意识到默认字典序和数值序的区别。我的习惯是写任何稍复杂的sort命令前先画出这个文件的字段结构想清楚每个-k是从哪个字段开始、到哪个字段结束需要加什么修饰符。这样踩坑的概率会小很多。另外我不太建议死记一堆命令模板。sort的帮助文档里藏了太多好东西建议花点时间读一读man sort特别是关于键的语法、修饰符优先级、-t搭配-k时的特殊规则这些信息虽然密集但每次读都会有收获。实际工作中sort往往不是单一命令而是管道的一环和多字段文本处理结合时它的价值和坑都会成倍放大。希望这篇博客里的踩坑记录能帮你少走一些弯路。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑