大纲

1、sort 介绍

2、sort 语法格式

3、sort 工作原理

4、sort 实战演示


sort 版本


sort (GNU coreutils) 8.4



一、sort 介绍

    In Unix-like operating systems, sort is a standard command line program that prints the lines of its input or concatenation of all files listed in its argument list in sorted order. Sorting is done based on one or more sort keys extracted from each line of input. By default, the entire input is taken as sort key. Blank space is the default field separator.


二、sort 语法格式

[root@localhost ~]# sort --help
Usage: sort [OPTION]... [FILE]...
Write sorted concatenation of all FILE(s) to standard output.

Mandatory arguments to long options are mandatory for short options too.
Ordering options:

  -b, --ignore-leading-blanks  ignore leading blanks
  -d, --dictionary-order     consider only blanks and alphanumeric characters
  -f, --ignore-case         fold lower case to upper case characters
  -g, --general-numeric-sort    compare according to general numerical value
  -i, --ignore-nonprinting    consider only printable characters
  -M, --month-sort        compare (unknown) < `JAN' < ... < `DEC'
  -n, --numeric-sort       compare according to string numerical value
  -r, --reverse          reverse the result of comparisons

Other options:

  -c, --check           check whether input is sorted; do not sort
  -k, --key=POS1[,POS2]   start a key at POS1, end it at POS2 (origin 1)
  -m, --merge           merge already sorted files; do not sort
  -o, --output=FILE        write result to FILE instead of standard output
  -s, --stable        stabilize sort by disabling last-resort comparison
  -S, --buffer-size=SIZE    use SIZE for main memory buffer
  -t, --field-separator=SEP  use SEP instead of non-blank to blank transition
  -T, --temporary-directory=DIR  use DIR for temporaries, not $TMPDIR or /tmp;
                    multiple options specify multiple directories
  -u, --unique              with -c, check for strict ordering;
                              without -c, output only the first of an equal run
  -z, --zero-terminated     end lines with 0 byte, not newline
      --help     display this help and exit
      --version  output version information and exit

POS is F[.C][OPTS], where F is the field number and C the character position
in the field.  OPTS is one or more single-letter ordering options, which
override global ordering options for that key.  If no key is given, use the
entire line as the key.

SIZE may be followed by the following multiplicative suffixes:
% 1% of memory, b 1, K 1024 (default), and so on for M, G, T, P, E, Z, Y.

With no FILE, or when FILE is -, read standard input.

三、sort 工作原理

3.1 工作原理描述

  sort 命令对 FILE 参数指定的文件中的行排序,并将结果写到标准输出。

  • 如果 FILE 参数指定多个文件,那么 sort 命令将这些文件连接起来,并当作一个文件进行排序。

  • -(减号)代替文件名指定标准输入。如果未指定任何文件名,那么该命令对标准输入排序。

  • 可以使用 -o 标志指定输出文件。

  • 如果未指定任何标志,sort 命令基于当前语言环境的整理顺序对输入文件的所有行排序。

 就像awk,cut,join一样:sort将输入看作具有多条记录的数据流,而记录是由可变宽度的字段组成,记录是以换行字符作为定界符,字段的定界符则是空白字符或是用户指定的单个字符。

  1. 以最简单的情况来说,如果未提供命令行选项时,整个记录都会根据当前locale所定义的次序排序。从首字符一直向后,依次按locale所定义的次序排序。所以,在排序之前我们最好定义locale,如下:LANG=C。

  2. 如果要进一步控制排序操作,可以用 –k 选项指定排序字段,并且用–t选项来选择字段定界符。如未指定–t,则表示字段以空白分隔且记录内开头与结尾的空白都将忽略;如果指定-t选项,则被指定的字符会分隔字段,且空白是有意义的。

  • 如果仅指定一个字段编号,则排序键值会自该字段的起始处开始,一直继续到记录的结尾(而非字段的结尾)。-k2 表示从第2个字段开始,到整个记录结尾。-k POS1[,POS2] start a key at POS1 (origin 1), end it at POS2 (default end of line)。如果仅指定第2个字段呢?正解:-k2,2

  • 如果给的是一对用逗号隔开的字段数字,则排序键值将由第一个字段值的起始处开始,结束于第二个字段值的结尾。

  • 使用点号字符位置,则比较的开始(一对数字的第一个)或结束(一对数字的第二个)在该字符位置处:-k2.4,5.6指的是从第2个字段的第4个字符开始比较,一直比到第5个字段的第6个字符。

  • 当出现多个 -k选项,会先从第一个键值字段开始排序,找出匹配该键值的记录后,再进行第二个键值字段的排序,以此类推。


3.2 排序关键字【key】

    POS is F[.C][OPTS], where F is the field number and C the character position in the field.  OPTS is one or more single-letter ordering options, which override global ordering options for that key.  If no key is given, use the entire line as the key.

    排序关键字是输入行的一部分,由【字段号】和【字符位置】指定。字段是输入行的组成部分,由字段分隔符分隔。缺省字段分隔符是由一个或多个连续空白字符组成的序列。然而,这些空白字符被看作以下用于排序的字段的一部分。您可以指定 -b 选项来忽略这些开头的空白字符。使用 -t 标志可指定不同的字段分隔符。在 C 语言和英语语言环境下,制表符和空格字符都是空白符。

  使用排序关键字时,sort 命令首先根据第一个排序关键字的内容对所有行排序。然后,根据第二个排序关键字的内容,对所有第一个排序关键字相同的行排序,如此进行下去。按照排序关键字在命令行中出现的顺序给它们编号。如果两行对所有排序关键字的排序都相同,那么对全部行依据当前语言环境的整理顺序进行比较。

  对字段中的列进行编号时,缺省字段分隔符中的空格符将作为后继字段计数。不会将由 -t 标志指定的字段分隔符算作字段的一部分。可使用 -b 标志忽略前导空格符。


3.3 关键选项

-u选项(unique)只有唯一的记录,丢弃所有具有【相同键值的记录,只留下其中的第一条。只有键值字段是重要的,也就是说:被丢弃的记录其他部分可能是不同值。和uniq命令不同的是: sort -u 消除操作是依据匹配的键值,而非匹配的记录。

重复的行:对于linux来说,repeated lines的定义是:必须相邻并且完全相同的行(相邻 && 相同)

删除重复(uniqunique

    uniq命令提供另一种过滤数据的方式:它常用于管道中,用来删除已使用sort排序完成, 重复记录。因为:uniq在检查重复行的时候,仅会检查相邻的行。


-k选项:-k选项的后面接着是一个字段编号,或者是一对数字,有时在-k之后可用空白分隔。每个编号后面都可以接一个点号的字符位置,以及/或修饰符字母之一。字段以及字段里的字符都是由1开始。当-k指定的域相同时,而又没有再指定其他的域,则sort会按第一个域继续排序。

-k选项的具体语法格式:

[ FStart [.CStart] ] [ Modifier ] [ , [ FEnd [. CEnd ] ] [ Modifier ] ]

其语法被其中的“,”分为两大部分,start部分和end部分。核心思想:如果不设定Fend部分,那么就认为end被设定为行尾。如果不设定Cend,则End为域结尾。[ key 的表示,用于描述比较字段 ] 这个概念很重要的,但往往你不会重视它。

FStart . CStart,其中FStart表示域,而CStart表示在FStart域中的第几个字符开始算为”排序首字符”。Modifier 表示可以使用的选项:n rb d f i等(定义域内部的排序)

sort t:’–k1.2nr  file  #按第1个域的第2个字符开始排序,且按数字逆序排序(nr).

使用 -k 标志来定义排序关键字

-k KeyDefinition 标志采用下列形式:

-k [ FStart [ .CStart ] ] [ Modifier ] [ , [ FEnd [ .CEnd ] ][ Modifier ] ]

排序关键字包括所有以 FStart 变量指定的字段和 CStart 变量指定的列开头的字符及以 FEnd 变量指定的字段和 CEnd 变量指定的列结束的字符。如果未指定 Fend,就假定行的最后一个字段。如果未指定 CEnd,就假定 FEnd 字段的最后一个字符。KeyDefinition 变量中的任何字段号或列号都可以省略。缺省值为:

wKiom1OaWUKzmyROAABEnjHPY8Q461.jpg

如果这些字段间有空格,排序将他们作为分开的几个不同字段进行处理。

Modifier 变量的值可以是字母 bdfin  r 中的一个或多个。修饰符仅应用于它们连接的字段定义,与同一字母的标志有同样的效果。修饰符字母 b 仅应用于其连接的字段定义的末尾。例如:

-k 3.2b,3r

指定排序关键字,从第三字段的第二非空格列开始并扩展至第三字段结束,对这个关键字的排序以逆向整理顺序完成。如果 FStart 变量和 CStart 变量在命令行末尾以外或在 FEnd 变量和 CEnd 变量之后,那么该排序关键字被忽略。

注:一行的最大字段数为 32

标志

注:在任何排序关键字定义前出现的 -b-d-f-i-n  -r 标志应用于所有排序关键字。-b-d-f-i-n  -r 标志都不能单独出现在 -kKeyDefinition 之后;如果它们作为修饰符连接 KeyDefinition 变量,那么就只应用于连接排序关键字。

其中n和r你肯定已经很熟悉了。

b表示忽略本域的签到空白符号。

d表示对本域按照字典顺序排序(即,只考虑空白和字母)。

f表示对本域忽略大小写进行排序。

i表示忽略“不可打印字符”,只针对可打印字符进行排序。(有些ASCII就是不可打印字符,比如\a是报警,\b是退格,\n是换行,\r是回车等等)


3.4 resort 机制

因为sort有一个resort重排序机制,当我们指定 -k2,2 以第2个字段进行排序,那么当第2个字段有相同的,该如何排序呢? sort 此刻会马上回到整个【line】的开头,从第1个字段开始排序,如果还不能排序完成,那么将跳过指定的第2个字段,对后续的字段进行排序。resort (按默认的方式排序,相当于未指定任何选项)

如果不希望这种情况出现,需要使用 -s 选项来阻止 resort 机制。

 

3.5 数值排序,跨字段(域)的假象(仅仅针对数值排序,想想数值中间怎么可能包含非数字的字符嘛。所以这个设计是合理的)

默认情况下,sort 按照 string(字符串、字典) 进行排序的,要按照数值,需要 -n 选项。

sort -k2,3n   file.txt

指定 2-3字段按数值排序,结果第3字段不会干活。这是数值排序的特殊之处,只对指定的第1字段有效,除此之外的其他字段是无效的,排完指定的字段后,返回记录开头,进行 resort (按默认的方式排序,相当于未指定任何选项)。当然,要阻止还是使用 -s

那如果我想对第3字段也使用数值排序呢?是的,你只有使用下面的苦逼办法,一列一列的指定。

sort -k2,2n -k3,3n file.txt

当然,如果已经能根据第2个字段进行排序了(没有相同的),那么就不会再根据第3字段排序。

数值排序(只有按数值排序才会出现这个情况,排序字符串不会)的特殊情况:

wKioL1UJhk3gq7uTAACZndJRDsU159.jpg

  以第2个域的第2个字符开始到第3个域的第1个字符结束的部分进行排序。如上第2行提取10  5,第3行提取00  5,第4行提取0  3,第5行提取00  4

又因为sort认为: 0 < 00 < 000 < 0000 .

  但是为什么 00  500  4前面呢?对于数值排序来说,“跨域的设定是个假象”,sort只会比较第2个域的第2个字符到第2个域的最后一个字符的部分,而不会把第3个域的开头字符纳入比较范围。

四、sort 实战演示

  sort默认行为是将输入文件的每一行作为排序关键字(If no key is given, use the entire line as the key.。比较规则是从首字符向后,依次按ASCII码进行比较,最后将其按升序打印到标准输出。

 为了便于演示,准备测试文件内容如下:

[root@localhost ~]# cat names.txt
Emma Thomas:100:Marketing
Sanjay Gupta:400:Support
Alex Jason:200:Sales
Madison Randy:300:Product Development
Sanjay Gupta:400:Support
Nisha Singh:500:Sales
Emma Thomas:600:Marketing

a、对文件默认进行升序排序

[root@localhost ~]# sort names.txt
Alex Jason:200:Sales
Emma Thomas:100:Marketing
Emma Thomas:600:Marketing 
Madison Randy:300:Product Development
Nisha Singh:500:Sales
Sanjay Gupta:400:Support
Sanjay Gupta:400:Support    # 重复行

b、-u(--unique)选项:去掉重复行, 通常不这么用。  重复行一般通过管道组合 uniq 来删除。

[root@localhost ~]# sort -u names.txt
Alex Jason:200:Sales
Emma Thomas:100:Marketing
Emma Thomas:600:Marketing 
Madison Randy:300:Product Development
Nisha Singh:500:Sales
Sanjay Gupta:400:Support

此时,"Sanjay Gupta:400:Support"被无情的删除了。

c、-r(--reverse)选项:反转默认的排序方式【默认为升序,那么-r则为降序】

[root@localhost ~]# sort -r names.txt 
Sanjay Gupta:400:Support
Sanjay Gupta:400:Support
Nisha Singh:500:Sales
Madison Randy:300:Product Development
Emma Thomas:600:Marketing 
Emma Thomas:100:Marketing
Alex Jason:200:Sales

d、-n(--numeric-sort)选项:按数值排序

  你有没有遇到过10比2小的情况。出现这种情况是由于排序程序将这些数字按字符(ASCII)来排序了,排序程序会先比较1和2,显然1小,所以就将10放在2前面喽。这也是sort的一贯作风。

  如果想改变这种现状,就要使用 -n 选项,来告诉sort,“要以数值来排序”!

测试文本如下:

[root@localhost ~]# cat number.txt 
10
3
87
8
5

我们对其进行升序排序:

[root@localhost ~]# sort number.txt 
10
3
5
8
87

看到没有?10竟然比3小,有没有搞错,显然这是不满足我们要求的。奇怪吧,因为默认sort是按照字符一个一个比较的。可以强制它compare according to string numerical value,来看看-n选项

[root@localhost ~]# sort -n number.txt
3
5
8
10
87

e、-t(--field-separator=SEP); -k(--key=POS1,[POS2])选项:指定字段分隔符 以及 指定排序关键字

  names.txt文件的格式是以 ":" 分隔的多个字段组成,那么如果我想以第2个字段作为排序关键字进行排序,如果利用sort实现呢?

  幸好,sort提供了-t 选项,后面可以设定间隔符。(是不是想起了cut和paste的-d选项,共鸣~~这就是学习linux比较困扰的地方,完成相同目的选项在不同程序中却完全不一样,坑。。),指定了间隔符之后,就可以用 -k 来指定字段(排序关键字)了。

[root@localhost ~]# sort -t: -k2 names.txt 
Emma Thomas:100:Marketing
Alex Jason:200:Sales
Madison Randy:300:Product Development
Sanjay Gupta:400:Support
Sanjay Gupta:400:Support
Nisha Singh:500:Sales
Emma Thomas:600:Marketing

我们以":"作为字段分隔符,然后把第2个字段作为排序关键字,排序结果很理想。


f、其他常用选项

-f    会将小写字母都转换为大写字母来进行比较,亦即忽略大小写

-c    会检查文件是否已排好序,如果乱序,则输出第一个乱序的行的相关信息,最后返回1

-M    会以月份来排序,比如JAN小于FEB等等

-b    会忽略每一行前面的所有空白部分,从第一个可见字符开始比较。



进阶

下面,我们进阶来一点高级的用法。

第一个域是公司名称,第二个域是公司人数,第三个域是员工平均工资。(除了公司名称,其他的别信,都瞎写的^_^)

准备测试数据:

[root@localhost ~]# cat test.txt
guge 50 3000
baidu 100 5000
sohu 100 4500
google 110 5000

1、按照公司人数(第2个字段)进行排序

[root@localhost ~]# sort -n -k2 test.txt
guge 50 3000
baidu 100 5000
sohu 100 4500
google 110 5000

此时,出现一个问题,baidu和sohu都是100,这个时候怎么办呢?按照默认规矩(resort 机制),是从第一个字段开始进行升序排序,因此baidu排在了sohu前面。

严格的说,这样的写法是不规范的。 按照前面的sort的原理来看,如果指定 -k2 , 那么真正 key 的结束是行尾。然而由于是按照 数值 进行比较,所以是不会进行跨字段比较的。 如果按字符串(依次按ASCII码值进行比较)进行比较,那就会得到另一个结果。


2、按第公司人数(2个字段)排序,如果人数相同(第2个字段相同)则按员工工资(第3个字段)排序

[root@localhost ~]# sort -n -k2 -k3 test.txt
guge 50 3000
sohu 100 4500
baidu 100 5000
google 110 5000

我们加了一个-k2 -k3就解决了问题。对滴,sort支持这种设定,就是说设定域排序的优先级,先以第2个域进行排序,如果相同,再以第3个域进行排序。(如果你愿意,可以一直这么写下去,设定很多个排序优先级)


3、按第3个字段降序排序,如果相同则按第2个字段升序排序

[root@localhost ~]# sort -n -k3r -k2 test.txt 
baidu 100 5000
google 110 5000
sohu 100 4500
guge 50 3000

此处有使用了一些小技巧,你仔细看看,在-k3后面偷偷加上了一个小写字母r。你想想能得到答案么?揭晓:r和-r选项的作用是一样的,就是表示逆序。因为sort默认是按照升序排序的,所以此处需要加上r表示第三个域(员工平均工资)是按照降序排序。


突发奇想,从公司英文名称的第二个字母开始进行排序:

[root@localhost ~]# sort -k1.2 test.txt 
baidu 100 5000
sohu 100 4500
google 110 5000
guge 50 3000

看,我们使用了-k 1.2,这就表示对第一个域的第二个字符开始到行尾最后一个字符为止的字符串进行排序。你会发现baidu因为第二个字母是a而名列榜首。sohu和 google第二个字符都是o,但sohu的h在google的o前面,所以两者分别排在第二和第三。guge只能屈居第四了。

又突发奇想,,只针对公司英文名称的第二个字母进行排序,如果相同的按照员工工资进行降序排序:

[root@localhost ~]# sort -k 1.2,1.2 -k 3,3nr test.txt
baidu 100 5000
google 110 5000
sohu 100 4500
guge 50 3000

由于只对第二个字母进行排序,所以我们使用了-k 1.2,1.2的表示方式,表示我们“只”对第二个字母进行排序。(如果你问“我使用-k 1.2怎么不行?”,当然不行,因为你省略了End部分,这就意味着你将对从第二个字母起到本行最后一个字符为止的字符串进行排序)。对于员工工资进行排序,我们也使用了-k 3,3,这是最准确的表述,表示我们“只”对本域进行排序,因为如果你省略了后面的3,就变成了我们“对第3个域开始到行尾位置所有字符串内容进行排序” 了。

思考关于-k和-u联合使用的例子:

## 这是最原始的facebook.txt文件。
[root@localhost ~]# cat test.txt
google 110 5000
baidu 100 5000
guge 50 3000
sohu 100 4500

[root@localhost ~]# sort -n -k 2 test.txt
guge 50 3000
baidu 100 5000
sohu 100 4500
google 110 5000

[root@localhost ~]# sort -n -k 2 -u test.txt
guge 50 3000
baidu 100 5000
google 110 5000

当设定以公司员工域进行数值排序,然后加-u后,sohu一行就被删除了!原来-u只识别用-k设定的域, -u 很傻很天真,发现 -k 指定的域相同(不再看其他字段的情况),就认为该行是相同的, 然后删除。

[root@localhost ~]# sort  -k 1 -u facebook.txt
baidu 100 5000
google 110 5000
guge 50 3000
sohu 100 4500

[root@localhost ~]# sort  -k 1.1,1.1 -u facebook.txt
baidu 100 5000
google 110 5000
sohu 100 4500

这个例子也同理,开头字符是g的guge也没有幸免, 直接被误杀。

[root@localhost ~]# sort -n -k 2 -k 3 -u facebook.txt
guge 50 3000
sohu 100 4500
baidu 100 5000
google 110 5000

咦!这里设置了两层排序优先级的情况下,使用-u就没有删除任何行。原来-u是会权衡所有指定的-k选项,将都相同的才会删除,只要其中有一级不同都不会轻易删除的:)(不信,你可以自己加一行sina 100 4500试试看)


有时候在sort命令后会看到+1 -2这些符号,这是什么东东?

关于这种语法,最新的sort是这么进行解释的:

On older systems, `sort’ supports an obsolete origin-zero syntax `+POS1 [-POS2]‘ for specifying sort keys.  POSIX 1003.1-2001 (*note Standards conformance::) does not allow this; use `-k’ instead.

原来,这种古老的表示方式已经被淘汰了,以后可以理直气壮的鄙视使用这种表示方法的脚本喽!

(为了防止古老脚本的存在,在这再说一下这种表示方法,加号表示Start部分,减号表示End部分。最最重要的一点是,这种方式方法是从0开始计数的,以前所说的第一个域,在此被表示为第0个域。以前的第2个字符,在此表示为第1个字符。明白?)

五、sort 效率

根据实际经验,在进行大文件排序时,由于某些资源的限制,系统会报错。比如

  • sort: Fatal error, too many temp files needed

  • error:0653-657 排序时写错误

主要的思想是:

1 分割文件,使分割的文件能全部加载到内存。
2 分别排序每一个分割的文件
3 合并文件

难的是合并操作:

1 跌增合并,一次合并两个文件。依次类推,直到最终只剩一个文件。时间复杂度主要在读取文件,要多次读取。

2 利用堆,一次合并多个文件  时间复杂度主要取决于堆的查找。(堆主要用于查找当前最小的行)


最好对大文件进行分解,分解成N个小文件(多小呢,看机器的性能),排序完成后,再对小文件进行合并,这样会快很多。而对当个大文件的排序,sort效率很低。当然,还可以利用多线程的功能。

另外,文件太大,sort会使用部分很大的临时文件,一般在/tmp下,如果/tmp空间不够,可以使用 -T 指定一个临时文件的存放目录。

1、将文件split (split  -l )成若干小文件,2、然后sort每个小文件。  3、sort -m (--merge)  合并各个已经排序的小文件。 sort -snm file1 file2 file3    

    如果数据实在太大,可以考虑导入数据库,然后用order by 重新导出来, 妥妥的。

 

r2007@www r2007 $ uname -or
2.4.23_pre6-gss-r1 GNU/Linux
r2007@www r2007 $ cat file1
1
35
88
r2007@www r2007 $ cat file2
9
11
44
66
r2007@www r2007 $ sort -snm file1 file2                                      
1
9
11
35
44
66
88
r2007@www r2007 $