Linux 命令 - sort 与 uniq

1 sort 与 uniq 简介

sort 命令用来对文件中的所有行进行排序。具体地,是对关键字进行排序。可以将一行当作一个关键字,也可以将一行中某个字段(由分隔符隔开)当作一个关键字。

而 uniq 命令用于统计文件中所有 连续重复出现 的行(只打印第一行)。因此,一般情况下 uniq 与 sort 联用,先用 sort 进行排序,使得所有重复的行都在一起,然后再用 uniq 进行统计并进行打印。

2 sort 用法

2.1 一般用法

sort filename

sort 会根据本地的 locale 设置中的字符编码,对每一行(将整行当作一个关键字)进行 升序 排列。

关于编码的影响,可以看以下例子:

1
2
3
4
5
6
7
8
9
10
cat filename
南京
市长
江大桥

LC_ALL=zh_CN.UTF-8
sort test3.txt
江大桥
南京
市长

其中,LC_ALL 用来临时设置当前shell进程的所有 locale 相关的变量(覆盖)。可以看出,当设置本地的编码后,是按照中文的字典序进行排序的。

2.2 忽略关键字开始的空格

排序时忽略关键字开始的空格: sort -b filename

2.3 降序

sort -r filename

2.4 去掉重复的关键字所在的行

sort -u filename

注意,假设关键字是一行中的某个字段(见后面的按指定列排序),如果某几行的关键字相同,但是其余部分不同,那么仍然会删除掉这些重复行(保留其中某一行)。

2.5 按数字大小排序

当是对一些数字进行排序时,由于默认是按照字符编码排序的,有时候会出现些意外的排序结果。例如:

1
2
3
4
5
6
7
8
9
cat filename
111
3
22

sort filename
111
22
3

发现,111 比 22 大,却排在了 22 的前面。同样,22 比 3 大,却排在了 3 的前面。这与常识不符。

可以使用如下命令按照数字大小而非数字编码进行排序: sort -n filename

2.6 按指定列排序

有时候一行可能会被某个符号分隔成多列。sort 支持按某列进行排序。

可以通过 -t 指定分隔符,通过 -k 参数指定哪些列为关键字。

若分隔符为制表符,则 -t 后面的符号为 $'\t'

2.6.1 关键字格式

-k 指定的关键字格式为:

F[.C][OPTS][,F[.C][OPTS]]

其中:

  • 分为两个部分,第一个部分指定开始位置,第二个部分指定结束位置,之间用逗号隔开。当然,第二个部分也可以不用指定,此时默认表示到行尾。
  • 每个部分有三个字段,分别是列号,列中某个字符的位置,修饰符。

    一行中第一列的编号为 1,一列中第一个字符的编号也为 1。修饰符有 [bdfgiMhnRrV],是 sort 的相关参数。常用的有 [bnr]。
  • 如果第二个部分指定了,那么表示将这个区间的部分当作一个关键字。

    比如:-k 2.4,3,表示将从第二列的第 4 个字符开始到该列末尾,以及第三列中的全部字符当作关键字。

2.6.2 具体用法

在排序时,首先根据第一个指定的关键字进行排序。如果这个关键字在多行中相同,那么根据第二个指定的关键字进行排序。如果所有关键字在多行中相同,那么将整行当作一个关键字进行排序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
cat filename
ustc: 3: 40
sjtu: 5: 63
nju: 7: 80
tsinghua: 1: 23
fudan: 4: 50
zju: 6: 72
peking: 2: 28

sort -t ':' -k 2,2nb filename
peking: 1: 28
tsinghua: 1: 23
ustc: 3: 40
fudan: 4: 50
sjtu: 4: 63
zju: 6: 72
nju: 7: 80

首先将第二列当作关键字进行排序。会发现 peking 和 tsinghua 的第二列相同,此时由于命令行中没有指定其他关键字了,因此将整行当作关键字进行排序。因此 peking 在 tsinghua 的前面。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
cat filename
ustc: 3: 40
sjtu: 5: 63
nju: 7: 80
tsinghua: 1: 23
fudan: 4: 50
zju: 6: 72
peking: 2: 28

sort -t ':' -k 2,2nb -k 3,3nb filename
tsinghua: 1: 23
peking: 1: 28
ustc: 3: 40
fudan: 4: 50
sjtu: 4: 63
zju: 6: 72
nju: 7: 80

这里指定了两个关键字。peking 和 tsinghua 的第一个关键字相同,此时会按照第二个关键字进行排序。因此,tsinghua 排在 peking 前面。

2.7 稳定排序

默认情况下,sort 排序是不稳定的。可以将其变成稳定排序: sort -s filename

2.8 合并多个有序文件

sort -t xxx -k xxx -m filename1 filename2

按照归并算法根据给定的关键字进行合并。如果待归并的文件是有序的,则归并后的结果仍然是有序的。

3 uniq 用法

3.1 uniq 的一般用法

uniq filename

对于连续重复出现的行,只会打印第一行,而不会打印其他重复行。

3.2 打印出连续重复的次数

打印出连续重复的行中行的个数: uniq -c filename

3.3 只打印连续重复的行

只打印连续重复的行中的第一行: uniq -d filename

打印连续重复的行中的所有行: uniq -D filename

3.4 只打印不存在连续重复得行

uniq -u filename

3.5 忽略掉前 n 个域

一行可以由多个空格或多个 Tab 分隔成多个域,有时候想将某个域之后的部分当作关键字进行统计并打印。

uniq -f n filename

表示忽略掉每行的前 n 个域,将第 (n + 1) 个域及其后面的部分当作关键字。

注意:如果忽略掉了所有域,那么将以空字符串进行比较,而空串都是相同的。另外,所有行的域分隔格式都要对应相同才可以。

1
2
3
4
5
6
7
cat filename
hello world
hello world

uniq -f 1 filename
hello world
hello world

第一行的 hello 后面有一个空格,第二行的 hello 后面有两个空格。此时忽略掉第一个域后,第一行和第二行剩余的部分是不同的(world 和前面带有空格的 world 显然不同)。因此,两行均会打印出来。

3.6 忽略掉前 n 个字符

uniq -s n filename

-s 可以和 -f 合用。比如: uniq -f m -s n filename

表示忽略掉前 m 个域,并忽略掉第 (m + 1) 个域的前 n 个字符。

不过,uniq 命令好像多中文字符支持得不是很好,即使设置 LC_ALL 为 zh_CN.UTF-8 也不行。

3.7 最多比较 n 个字符

uniq -w n filename

表示从行开始处最多比较 n 个字符。

-w 可以和 -f 以及 -s 合用: uniq -f m -s n -w k filename

表示忽略掉前 m 个域,并忽略掉第 (m + 1) 个域的前 n 个字符,然后最多比较 k 个字符。


Reference