默认排序方式
现有如下文本
$ cat hard-sort.txt
3
1234
6
Adb
85
Abc
aAb
aab
现在我想用 sort 命令对这些行进行排序
$ sort hard-sort.txt
3
1234
6
85
Abc
Adb
aAb
aab
这个排序结果似乎是杂乱无章的,但实际是有规律可循的。sort 命令把文件的每一行当做一个字符串,然后以比较字符串的方式来对行进行排序。
字符串比较方式是逐个比较字符。
字段的定义
在实际中,通常是根据行中的某个列进行排序的,这个列在 shell 中被称为字段。
假如即将被排序的某一行如下
-rw-r--r-- 1 david staff 29 12 28 22:04 hard-sort.txt
字段默认是被空白字符(空格和tab)分割的,因此这一行其实可以被分割为9个字段。然而字段的值却包括字段开头的空白字符,例如第五个字段的值其实是三个空格再加上29。
然而如果在使用 sort 命令的时候,加上 -b 选项,那么字段就不包括开头的空白字符。
-k 选项
sort 命令的 -k 选项用来指定排序用到的 sort key,语法如下
sort -k field1[,field2]
field1 和 field2 的格式为 m.n 。m 表示行中字段的序号,从 1 开始。n 表示第 m 个字段的第 n 个字符,并且 n 也是从 1 开始。
因此 field1 和 field2 指定了一个区间的字符串,而这个字符串就是用来排序的 sort key,它有如下规则
- 如果 field2 被省略,那么 sort key 字符串结束于行尾。
- m.n 后面可以加上修饰符,例如 b, n, r 。这些修饰符对应于 sort 相应的选项,例如 -b 选项表示忽略字段开头的空白字符,-n 选项表示把 sort key 看作数值而不是字符串,-r 选项表示反向排序。
- 除了修饰符 b 以外,其它的修饰符,例如 n ,只需要使用一次,即可对 field1 和 field2 同时起作用。
- 如果省略了 field1 中的 .n ,那么默认为 .1,表示 sort key 起始于 field1 指定的第 m 个字段中的第一个字符。
- 如果没有指定 field2 中的 .n ,那么 sort key 字符串结束于 field2 指定的第 m 个字段的末尾。
例子分析
有了这些理论基础并不代表你在实际中能够使用好 sort 命令,我们通过一个例子来加深理解。
假设现在有一个输出,如下
$ ls -l
total 64
-rwxr--r-- 1 david staff 654 12 24 22:51 file_expression.sh
-rwxr--r-- 1 david staff 330 12 24 23:07 integer_expression.sh
-rwxr--r-- 1 david staff 513 12 24 23:23 integer_expression2.sh
-rw-r--r-- 1 david staff 40 12 28 22:33 person.txt
-rw-r--r-- 1 david staff 5 12 28 23:33 short-size.txt
-rwxr--r-- 1 david staff 469 12 24 23:03 string_expression.sh
-rwxr--r-- 1 david staff 162 12 24 22:56 test.sh
-rwxr--r-- 1 david staff 387 12 24 23:36 test_operator.sh
现在我需要对这个结果根据文件的大小进行排序,那么就需要使用 sort 命令根据第五个字段进行排序
$ ls -l | sort -k 5,5
total 64
-rw-r--r-- 1 david staff 5 12 28 23:33 short-size.txt
-rw-r--r-- 1 david staff 40 12 28 22:33 person.txt
-rwxr--r-- 1 david staff 162 12 24 22:56 test.sh
-rwxr--r-- 1 david staff 330 12 24 23:07 integer_expression.sh
-rwxr--r-- 1 david staff 387 12 24 23:36 test_operator.sh
-rwxr--r-- 1 david staff 469 12 24 23:03 string_expression.sh
-rwxr--r-- 1 david staff 513 12 24 23:23 integer_expression2.sh
-rwxr--r-- 1 david staff 654 12 24 22:51 file_expression.sh
-k 5,5 表示 sort key 的值开始于第五个字段(包括空白字符)第一个字符,结束于第五个字段的最后一个字符,也就是说 sort key 就是第五个字段(包括空白字符)。而 -k 5 表示 sort key 的值为起始于第五个字段开头,结束于行尾。
这个结果看起来是正确,其实是一个巧合而已。现在我们去掉字段开头的空白,来看看排序的结果
$ ls -l | sort -k 5b,5b
total 64
-rwxr--r-- 1 david staff 162 12 24 22:56 test.sh
-rwxr--r-- 1 david staff 330 12 24 23:07 integer_expression.sh
-rwxr--r-- 1 david staff 387 12 24 23:36 test_operator.sh
-rw-r--r-- 1 david staff 40 12 28 22:33 person.txt
-rwxr--r-- 1 david staff 469 12 24 23:03 string_expression.sh
-rw-r--r-- 1 david staff 5 12 28 23:33 short-size.txt
-rwxr--r-- 1 david staff 513 12 24 23:23 integer_expression2.sh
-rwxr--r-- 1 david staff 654 12 24 22:51 file_expression.sh
修饰符 b 去掉了字段开头的空白字符,我们发现排序的结果已经混乱了,讲道理不应该呀,我只是去掉了空白字符而已,这样的结果是为什么呢?
只有修饰符 b 才分别需要对 field1 和 field2 使用,这样才能同时去掉 field1 和 field2 指定字段前的空白符。然而其它的修饰符只需要使用一次,即可对 field1 和 field2 同时起作用。
第五个字段去掉了开头的空白字符,然而第五个字段还是一个字符串,它不是整型,因此 sort 命令还是通过字符串的比较方式进行排序的,那么出面上面的结果就不意外了。
那么我们怎么纠正上面的结果呢?使用 -n 选项,按照字符串的数字值进行排序。
$ ls -l | sort -k 5b,5bn
total 64
-rw-r--r-- 1 david staff 5 12 28 23:33 short-size.txt
-rw-r--r-- 1 david staff 40 12 28 22:33 person.txt
-rwxr--r-- 1 david staff 162 12 24 22:56 test.sh
-rwxr--r-- 1 david staff 330 12 24 23:07 integer_expression.sh
-rwxr--r-- 1 david staff 387 12 24 23:36 test_operator.sh
-rwxr--r-- 1 david staff 469 12 24 23:03 string_expression.sh
-rwxr--r-- 1 david staff 513 12 24 23:23 integer_expression2.sh
-rwxr--r-- 1 david staff 654 12 24 22:51 file_expression.sh
修饰符 n 表示把 sort key 当作数值来对行进行排序。现在这个结果就不是巧合了,这是正确使用选项而导致的结果。
现在我不想用文件大小来对行进行排序,而是使用第八个字段,也就是小时和分钟组合的时间。但是很不幸,sort 没有这个选项来比较这样格式的时间,因此我需要分别比较小时,然后比较分钟。
首先使用小时来排序
$ ls -l | sort -k 8.1b,8.2bn
total 64
-rw-r--r-- 1 david staff 40 12 28 22:33 person.txt
-rwxr--r-- 1 david staff 162 12 24 22:56 test.sh
-rwxr--r-- 1 david staff 654 12 24 22:51 file_expression.sh
-rw-r--r-- 1 david staff 5 12 28 23:33 short-size.txt
-rwxr--r-- 1 david staff 330 12 24 23:07 integer_expression.sh
-rwxr--r-- 1 david staff 387 12 24 23:36 test_operator.sh
-rwxr--r-- 1 david staff 469 12 24 23:03 string_expression.sh
-rwxr--r-- 1 david staff 513 12 24 23:23 integer_expression2.sh
8.1b,8.2bn 的意思是,第八个字段去掉了前面的空白字符,然后使用它的第一个字符和第二个字符作为 sort key , 并根据 sort key 的数字值对行进行排序。
现在已经按小时排好序,如果小时相等,那再按分钟进行排序
$ ls -l | sort -bn -k 8.1,8.2 -k 8.4,8.5
total 64
-rw-r--r-- 1 david staff 40 12 28 22:33 person.txt
-rwxr--r-- 1 david staff 654 12 24 22:51 file_expression.sh
-rwxr--r-- 1 david staff 162 12 24 22:56 test.sh
-rwxr--r-- 1 david staff 469 12 24 23:03 string_expression.sh
-rwxr--r-- 1 david staff 330 12 24 23:07 integer_expression.sh
-rwxr--r-- 1 david staff 513 12 24 23:23 integer_expression2.sh
-rw-r--r-- 1 david staff 5 12 28 23:33 short-size.txt
-rwxr--r-- 1 david staff 387 12 24 23:36 test_operator.sh
这里首先利用 -k 8.1,8.2 按小时对行进行排序,然后使用 -k 8.4, 8.5 按分钟对行进行排序,并且这两个排序都受到 -bn 影响,也就是去掉字段开头的空白字符以及把 sort key 当作数字值而不是字符串。
-bn -k 8.1,8.2 -k 8.4,8.5 与 -k 8.1b,8.2bn -k 8.4b,8.5bn 的效果是一样的,区别在于,如果 n, b 字符用于选项,那么全局有效,而如果只是用作修饰符,只局部有效。
-t 选项
sort 命令默认是使用空白字符(空格和tab)来对行中的字段进行分割,然而在实际中,文本字段的分割并不总是使用空白字符,例如 /etc/passwd 文本就是使用冒号进行分割字段的。如果遇到这样的不使用空白字符分割字段的文本,就用 -t 选项重新定义分割字符。
假如现在有如下文本
$ cat person.txt
David;22
Pekky;11
Panda;18
这个文本的行的字段是用分号进行分割的,如果想用 sort 命令进行排序,就必须使用 -t 选项重新定义字段分割符为冒号
$ sort -t ';' -k 2n person.txt
Pekky;11
Panda;18
David;22
-t ‘;’ 重新定义了 sort 命令使用的字段分割符为分号,-k 2n 表示使用第二个字段,按照数字值进行排序。