侧边栏壁纸
博主头像
一笑痕

人生若只如初见,
是可喜亦或者是可悲?

  • 累计撰写 104 篇文章
  • 累计收到 7 条评论

sort -k2 和 -k2,2 排出两种顺序,差别在并列之后

2026-9-23 / 0 评论 / 7 阅读

sort -k2 和 -k2,2 排出两种顺序,差别在并列之后

一份四条记录的导出,第二列是金额,第三列是件数。同一个文件、同一条命令,只是把键的范围从 -k2 改成 -k2,2,输出就换了一种排法。

== 输入(第 2 列是金额,第 3 列是件数)==
C3003 30 12 beijing
B2001 30 2 shanghai
D4004 100 7 chengdu
A1002 30 9 shenzhen
== sort -k2 ==
D4004 100 7 chengdu
C3003 30 12 beijing
B2001 30 2 shanghai
A1002 30 9 shenzhen

== sort -k2,2 ==
D4004 100 7 chengdu
A1002 30 9 shenzhen
B2001 30 2 shanghai
C3003 30 12 beijing

D4004 那一行两种写法都排第一,因为 100 在字典序里小于 30,这一点和键的范围无关。剩下的三行金额都是 30,顺序完全不同,问题全出在这里。

键的范围是唯一的分歧点

-k2 只给了起始字段,没有给结束字段。按这个写法,键从第 2 列开始一直延伸到行尾,所以三行金额相等时,比较会继续往右走,用第 3 列的数字决定先后:12 排在 2 前面,9 排在最后。数字当字符串比,"12" < "2" 是成立的。

-k2,2 把键钉在第 2 列。三行金额相等,键就完全相等,此时 sort 会拿整行做一次兜底比较,这是默认行为,不是 -k2,2 额外要求的。整行比下来,A1002B2001C3003 按首字母排成 A、B、C。

兜底比较与稳定排序

想关掉整行兜底,加 -s

== sort -k2,2 -s ==
D4004 100 7 chengdu
C3003 30 12 beijing
B2001 30 2 shanghai
A1002 30 9 shenzhen

== sort -k2,2n ==
A1002 30 9 shenzhen
B2001 30 2 shanghai
C3003 30 12 beijing
D4004 100 7 chengdu

== sort -k2n ==
A1002 30 9 shenzhen
B2001 30 2 shanghai
C3003 30 12 beijing
D4004 100 7 chengdu

-k2,2 -s 的输出是 D、C、B、A,后三行正好是输入顺序。键相等时保留原顺序,这就是稳定排序。不用 -s 的版本给的是 A、B、C,也就是让整行内容参与比较。

两种做法没有谁更正确,区别在于结果能不能复现。输入顺序变了,-k2,2 -s 的输出跟着变,-k2,2 的输出不变。导出的表格如果要在两次执行之间对得上,键里必须把所有决定顺序的因素写清楚,否则第二次导出的行序可能是另一套。

换了位置的是哪两条

把两种写法输出到文件里 diff 一下,一共两行挪了位置。

== 只用第二列排(-k2,2)与 -k2 的结果差在哪 ==
  2,3d1
  < C3003 30 12 beijing
  < B2001 30 2 shanghai
  4a3,4
  > B2001 30 2 shanghai
  > C3003 30 12 beijing
  两种写法的行序完全相同的行数: 2 / 4

四条记录里两条位置不变、两条互换,比例看着不高。把这个文件放大到几万行,金额重复的行通常占多数,出现差异的比例会高得多。分页接口按这种排序取第 3 页,两次请求之间上游插入了一条新记录,页码对应的数据就可能整体错位。

数字列一定要带 n

第二列是金额,属于数字。字典序的 -k2,2 把 100 排在 30 前面,-k2,2n 才按数值比,30 在 100 前面。注意 -k2,2n-k2n 在这个数据集上结果一样,那是因为第 2 列没有重复到需要看第 3 列的数值。金额里出现并列时,两者还是会分开,理由和前面一样:-k2n 的键延伸到行尾。

== 换成数字键(金额是数字时 -k2,2 会按字典序把 100 排到 30 前面)==
  字典序 -k2,2 :  D4004 100 7 chengdu A1002 30 9 shenzhen B2001 30 2 shanghai C3003 30 12 beijing
  数值序 -k2,2n:  A1002 30 9 shenzhen B2001 30 2 shanghai C3003 30 12 beijing D4004 100 7 chengdu
  数值序但键延伸到行尾 -k2n: A1002 30 9 shenzhen B2001 30 2 shanghai C3003 30 12 beijing D4004 100 7 chengdu

补零只解决了一半

既然字典序把 100 排在 30 前面,那给金额补成定长是不是两种写法就一致了?补零之后字典序确实对了,键的范围差异还在。

== 把金额改成定长补零:字典序对了,但两种写法的键范围还是不同 ==
  补零后 -k2  :  C3003 030 12 beijing B2001 030 2 shanghai A1002 030 9 shenzhen D4004 100 7 chengdu
  补零后 -k2,2:  A1002 030 9 shenzhen B2001 030 2 shanghai C3003 030 12 beijing D4004 100 7 chengdu

这是两个独立的问题,补零处理的是"位数决定大小",键的范围处理的是"并列之后拿谁比",改一个不会顺带修好另一个。

键写全,顺序写死

本机这份输出用的是 macOS 自带的 BSD sort:

== 本机 sort 版本 ==
2.3-Apple (197)

BSD 的 sort 没有 GNU 的 --debug,看不到键的划分,只能靠构造有并列的数据来验证。不过"键不给结束字段就延伸到行尾"这条语义两边一致,GNU 环境里同样成立。

写脚本时把键的起止和比较方式一次写全,比如按逗号分列、按第 2 列数值排、并列时保持稳定,就写 sort -t, -k2,2n -s。同一个排序需求,两种写法都能跑通,结果不同却都不报错,这类差异我一般靠构造数据来验:故意让键并列,把输出跟预想对一眼,比读文档快得多。

    🤞 分享