Chapter-07 Unix Data Tools
本节我们将详细了解如何在Unix shell中利用命令行工具进行生物信息学研究。
Inspecting and Manipulating Text Data with Unix Tools
在生物信息学中,很多数据都是以表格的形式存储的——tabular plain-text files,各个数据之间以制表符分割,其的优势是可以被很多Unix shell命令,Python和R识别。
Tabular Plain-Text Data Formats
表格形式的纯文本数据通常单行独立,列间分割,每列之间隔开的分隔符可分为制表符、逗号和可变空格。在生物信息学中常见制表符分隔。
- 常见表格数据格式以
\t
制表符分隔。 - CSV以逗号
,
分隔数据。 - 有些表格数据格式的文件采用空格
来分割数据,但是这不被提倡。
How does file sperate lines?
值得我们注意的是,在Linux中以单个换行符\n
分割各行,而在Windows中则遵循DOS风格的\r\n
换行。特别的,对于CSV而言,它通常也遵循\r\n
的换行风格。
本节我们主要涉及BED格式和GTF格式的文件,这两个格式都是典型的制表符分隔的文件。
Inspecting Data with Head and Tail
一个生信数据文件往往很长,如果我们无脑cat
,你的终端马上会陷入一团糟,更好的方法是使用head
命令来查看文件前几行:
❯ head Mus_musculus.GRCm38.75_chr1.bed
1 3054233 3054733
1 3054233 3054733
1 3054233 3054733
1 3102016 3102125
1 3102016 3102125
1 3102016 3102125
1 3205901 3671498
1 3205901 3216344
1 3213609 3216344
1 3205901 3207317
head
提供了-n
参数,可以控制查看的行数,默认是10:
head -n 3
是一个很甜点的命令,它恰好可以让我一窥文件的列数,是否有行标题和列标题以及数据大致是啥样等等。
与head
相反,tail
可以打印文件最后几行的内容:
❯ tail -n 3 Mus_musculus.GRCm38.75_chr1.bed
1 195240910 195241007
1 195240910 195241007
1 195240910 195241007
和head
略有不同的是,tail
的-n
可以指定为+2
等,其含义是“从第2行开始输出”,通过这个命令,我们可以很方便的截断文件可能存在的标题行。
将head
和tail
结合起来,我们可以快速检查文件的开头和结尾:
❯ (head -n 2; tail -n 2) < Mus_musculus.GRCm38.75_chr1.bed | column -t
1 3054233 3054733
1 3054233 3054733
1 195240910 195241007
1 195240910 195241007
这个命令可能太长,我们可以在shell的配置文件里增设一个指令:
这样inspect
就等同于上面那个命令的作用了!
❯ inspect Mus_musculus.GRCm38.75_chr1.bed
1 3054233 3054733
1 3054233 3054733
1 195240910 195241007
1 195240910 195241007
head
很适合查看管道符中传递的数据,假如我们想要跟踪我们grep
命令产生的数据:
grep 'gene_id "ENSMUSG00000025907"' Mus_musculus.GRCm38.75_chr1.gtf | head -n 1 | column -t
1 protein_coding gene 6206197 6276648 . + . gene_id "ENSMUSG00000025907"; gene_name "Rb1cc1"; gene_source "ensembl_havana"; gene_biotype "protein_coding";
SIGPIPE
在以上的命令中,grep
不会筛选所有的文本,因为整个命令中,head
在打印一行后就停止了,对于管道符而言,当它所辖的一个命令已经停止后,会发出一个SIGPIPE
信号——类似于ctrl+c
,使得命令中的所有进程停止。
在以下这个命令中,所有程序都会在head
打印出三行内容后停止(除非grep
提前终止了)。
less
less
的哲学是“少即是多”,它是一个终端分页器,可以让你轻松浏览一个长文本文件,以下是常用的快捷键,你可以随时在less
界面里按h
来查看:
less
还是一个很好的调试工具,用来查看管道之间的输出,并且less
在文本充满屏幕的时候会拒绝输入,这会使得管道被堵塞,从而无需担心我们在实时调试时候无端耗费性能,因为less
迫使整个命令暂停了。
你可以随时退出less
,只需要按q
。
Plain-Text Data Summary Information with wc, ls, and awk
我们有时候不仅需要检查文件的内容(头部或尾部内容),还需要检查一些其他的文本信息,比如字数,行数或列数。对此,我们可以使用wc
命令:
默认情况下,wc
会输出指定文件的行数-l
、字数-w
和字符数-m
,一般我们只关心行数,所以我们可以指定参数-l
:
wc
可以很好的处理多文件:
❯ wc Mus_musculus.GRCm38.75_chr1.bed Mus_musculus.GRCm38.75_chr1.gtf
81226 243678 1698545 Mus_musculus.GRCm38.75_chr1.bed
81231 2385570 26607149 Mus_musculus.GRCm38.75_chr1.gtf
162457 2629248 28305694 total
也许你注意到了,同一组数据文件的不同格式使用wc
统计出的行数并不一致,让我们用head
查看一下:
❯ head -n 5 Mus_musculus.GRCm38.75_chr1.gtf
#!genome-build GRCm38.p2
#!genome-version GRCm38
#!genome-date 2012-01
#!genome-build-accession NCBI:GCA_000001635.4
#!genebuild-last-updated 2013-09
五行差异就来源于此,需要注意的是,很多数据文件都有这样的注释,在统计行数的时候需要关照一下。
Warning
虽然wc
可以快速的统计出文件的行数,但前提是文件的结构是排列良好的。如果我们仅有三行数据,但是文件中意外地在结尾多出了两个空格,那么wc -l
的结果就5了。
当然,我们可以通过grep
避免这种情况,如果你有一个文件results.txt
,想要统计其中的空行数,你可以:
当然,我们也很想知道文件的大小,使用ls -l
即可:
❯ ls -l Mus_musculus.GRCm38.75_chr1.bed
-rw-r--r-- 1 yangshu233 yangshu233 1698545 May 28 16:20 Mus_musculus.GRCm38.75_chr1.bed
ls -l
以字节数在第四列打印出文件大小,使用ls -lh
更像人话一点:
❯ ls -lh Mus_musculus.GRCm38.75_chr1.bed
-rw-r--r-- 1 yangshu233 yangshu233 1.7M May 28 16:20 Mus_musculus.GRCm38.75_chr1.bed
当然,表格的列数也是我们十分关心的事情,Unix shell提供了awk
命令,它是一个功能强大的小编程语言,但这里我们只简单演示一下如何用它查看文件列数:
其中NF
是awk
内置的一个变量,其记录了当前文件的字段数量。默认情况下,awk
以空格,制表符视为字段分隔符,这里我们通过参数-F
规定分隔符为\t
。
特别地,有些文件,比如Mus_musculus.GRCm38.75_chr1.gtf
在开头是有几行注释的,这会使得awk
命令无法正常工作,因为他返回的是第一行注释的字段数量,所以我们要手动筛选一下:
# 使用tail命令
❯ tail -n +6 Mus_musculus.GRCm38.75_chr1.gtf | awk -F "\t" '{print NF; exit}'
9
# 使用grep命令
❯ grep -v "^#" Mus_musculus.GRCm38.75_chr1.gtf | awk -F "\t" '{print NF; exit}'
9
通常,使用grep
命令更加健壮,因为它对不同注释数量的文件表现都很良好。你可以在shell的配置文件中加入如下语句:
再使用source
重新加载一下配置,你就可以使用ckcolumn
来检查一个表格文件的列数了!
Working with Column Data with cut and Columns
有时候我们只想要表格文件中的某一列信息,这时候可以用cut
命令:
我们通过参数-f
指定cut
提取文件中的第二列数据,当然,它还可以被指定为列数范围2-5
或者特定列数2,4,5
等等。
Note
cut
命令无法对列重新排列,即使你指定-f
为3,2,1
,其仍然遵循1,2,3
的顺序输出。
假如我们想要提取GTF文件中的基因组范围(染色体,基因起始与结束为止),可以采用以下命令:
❯ grep -v "^#" Mus_musculus.GRCm38.75_chr1.gtf | cut -f1,4,5 | head -n 3
1 3054233 3054733
1 3054233 3054733
1 3054233 3054733
类似于awk
,cut
命令可以通过-d
参数指定分隔符,比如处理CSV文件时:
Formatting Tabular Data with column
对于表格本身来说,其采用制表符来区分列元素,或者说标签的方式方便了计算机处理,但是有时候不适合人来阅读。原因是因为可变的数据长度破坏了良好的表格对齐方式,这对计算机来说没什么,但是对于以视觉为依靠的人类来说,下面这份数据看起来就一团糟:
❯ grep -v "^#" Mus_musculus.GRCm38.75_chr1.gtf | cut -f1-8 | head -n3
1 pseudogene gene 3054233 3054733 . + .
1 unprocessed_pseudogene transcript 3054233 3054733 . + .
1 unprocessed_pseudogene exon 3054233 3054733 . + .
Unix当然考虑到了这种情况,我们可以通过column -t
命令来重新对齐我们的表格:
❯ grep -v "^#" Mus_musculus.GRCm38.75_chr1.gtf | cut -f1-8 | head -n3 | column -t
1 pseudogene gene 3054233 3054733 . + .
1 unprocessed_pseudogene transcript 3054233 3054733 . + .
1 unprocessed_pseudogene exon 3054233 3054733 . + .
其中-t
参数代表着输入数据为表格类型。最终我们得到的结果显然更容易被阅读了!
和cut
类似的是,column
默认分隔符为\t
,如果我们想要处理类似于CSV这样的文件,可以用-s
参数另外指定分隔符。
❯ column -s"," -t Mus_musculus.GRCm38.75_chr1_bed.csv | head -n 3
1 3054233 3054733
1 3054233 3054733
1 3054233 3054733
Make Data for Computer
很多时候,我们强调代码的可读性,是因为代码主要是给人类阅读的,操作代码的主要是人。但如果面向数据,则其主要的操作者是计算机,也就是说,数据的格式是要为计算机服务的。很多数据格式对于人类不好辨认,却容易被计算机处理,比如使用\t
而不是可变长度的空格来分割列标签。
更重要的是,column
这一命令行工具传递着这样的信息:“请为计算机组织数据格式”,虽然数据的原始格式对你来说可能阅读困难,但是column
可以帮你转化,这促使人类更多将数据转化为计算机好处理的,而无需顾虑自己是否容易阅读。
所以,Program for human, data for computer.
The All-Powerful Grep
在前面的内容中,我们有简单接触过grep
这个命令,但是那些只是浮于表面的知识,实际上grep
是一个及其强大,而且快速的指令,它的诞生就是为了实现这么一个功能:快速找到匹配要求的文件行。
让我们简单回顾一下grep
的用法:grep [OPTION]... PATTERNS [FILE]...
,其接受一些参数,需要pattern
来匹配目标行,以及接受传入一个文本文件。
以下这个例子向我们展示了如何在文件Mus_muscu‐lus.GRCm38.75_chr1_genes.txt中找到基因"Olfr418-ps1":
包围pattern
的引号不是必需的,但是其确实是一个好习惯,其避免了pattern
被grep
错误地解读。
grep
会返回所有能匹配上的行,即使这些行可能只是部分匹配:
❯ grep "Olfr" Mus_musculus.GRCm38.75_chr1_genes.txt | head -n 5
ENSMUSG00000067064 Olfr1416
ENSMUSG00000057464 Olfr1415
ENSMUSG00000042849 Olfr1414
ENSMUSG00000058904 Olfr1413
ENSMUSG00000046300 Olfr1412
参数--color=always
非常好用,它会启用你终端的配色方案,并且将匹配到的字符用显眼的颜色标注出来,很多时候这个参数会被默认启用。
Warning
值得注意的是,Unix的这些工具有不同版本的实现,分别是以MacOS为代表的BSD utils和以Linux为代表的GNU coreutils。后者目前仍在活跃开发的阶段中,并且有很多很有意思的扩展功能,这些往往是前者所不具备的。所以这里很建议使用GNU coreutils的工具,更新更好。
前面我们有说,grep
提供了-v
这么一个参数,被称作反向模式,它很迅速帮我们排除了数据文件中的注释行。当然,这种反向排除的方法的应用场景还有很多,比如我们想知道所有包含"Olfr"但是不包含"Olfr1413"的基因:
请打住!需要强调的是,这种匹配方式往往也会过滤掉诸如"Olfr1413a"等基因,虽然它们符合我们的匹配规则,但并不是我们要排除的基因。为了避免这种情况的发生,我们可以指定-w
参数选项,使得grep
进行完全匹配——完全匹配被空格包围的单词。我们可以通过一个简单的例子来看看它是如何工作的:
❯ cat example.txt
bio
bioinfo
bioinformatics
computational biology
❯ grep -v bioinfo example.txt
bio
computational biology
❯ grep -v -w bioinfo example.txt
bio
bioinformatics
computational biology
使用-w
是一种将我们的匹配变得更加精准,或者说严格的方式。通常情况下,我们的模式应该尽可能的严格,以避免意外的匹配结果。
不知道你有没有发现,之前的例子中,grep
返回的结果虽然精确,却不能提供足够的上下文信息给到我们。而这点Unix早已想到,grep
提供了以下三个参数来返回更多信息:
-B
:before,返回匹配行与前文信息-A
:after,返回匹配行与后文信息-C
:context before and after,返回匹配行的前后文信息
以上三个参数均支持传入一个数字,以决定要打印多少行前后文信息。
❯ grep -B1 "AGATCGG" contaminated.fastq | head -n 6
@DJB775P1:248:D0MDGACXX:7:1202:12362:49613
CACTGCTTGCTCTGCGTTGATACCACTGCTTACTCTGCGTTGATACCACTGCTTACTCTGCGTTGATACCACTGCTTAGATCGGAAGAGCACACGTCTGAA
--
@DJB775P1:248:D0MDGACXX:7:1202:12782:49716
TTACTCTGCGTTGATACCACTGCTTACTCTGCGTTGATACCACTGCTTACTCTGCGTTGATACCACTGCTTACTCTGCGTTGATACCACTGCTTAGATCGG
--
❯ grep -A2 "AGATCGG" contaminated.fastq | head -n 6
CACTGCTTGCTCTGCGTTGATACCACTGCTTACTCTGCGTTGATACCACTGCTTACTCTGCGTTGATACCACTGCTTAGATCGGAAGAGCACACGTCTGAA
+
CCCFFFFFHHHHHGHGFHIJJ9HHIIJJJJJIJJJJIIJJJIIJIJJJJJJJJJIIJJJJJJHIHHHGHFFFFFFCEEEEEDBD?DDDDDDBDDDABDDCA
--
TTACTCTGCGTTGATACCACTGCTTACTCTGCGTTGATACCACTGCTTACTCTGCGTTGATACCACTGCTTACTCTGCGTTGATACCACTGCTTAGATCGG
+
ERE
在前文中我们一直使用的都是被称为POSIX Basic Reugular Expressions (BRE)的正则语法,其不如诸如Python等语言中提供的正则库强大,特别是在多值匹配的问题上(如果你足够熟悉它们的语法的话)。
对此,grep
提供了对POSIX Extended Regular Expressions (ERE)的支持,它支持使用管道符|
进行多值匹配:
❯ grep -E "(Olfr1413|Olfr1411)" Mus_musculus.GRCm38.75_chr1_genes.txt
ENSMUSG00000058904 Olfr1413
ENSMUSG00000062497 Olfr1411
当然,在这么短的篇幅里肯定无法涵盖BRE和ERE的精髓,仅作一个说明而已。
grep
提供参数-c
,可以统计匹配模式的行数:
Note
在书中使用的命令是grep -c "\tOlfr" Mus_musculus.GRCm38.75_chr1_genes.txt
,但在grep
默认的BRE语法中,其对正则表达语法的支持是很薄弱的,比如在这里\t
会被解释为t
而不是制表符。类似于-E
启用ERE语法支持,-P
可以启用PCRE——Perl兼容正则表达式(最强的支持引擎)的语法支持。
借助wc -l
也可以实现:
在表格样式的文件中,grep
对匹配行数的计算同样有用,比如它可以让我们知道有某一特定属性的样本有哪些,像是基因类型为snRNA的:
有时候我们想要更多的上下文信息,有时候只希望得到准确匹配的部分,grep
说没问题,请使用-o
参数:
或者我们想知道所有的基因id:
❯ grep -o -E 'gene_id "\w+"' Mus_musculus.GRCm38.75_chr1.gtf | head -n 5
gene_id "ENSMUSG00000090025"
gene_id "ENSMUSG00000090025"
gene_id "ENSMUSG00000090025"
gene_id "ENSMUSG00000064842"
gene_id "ENSMUSG00000064842"
Decoding Plain-Text Data: hexdump
我们拿到的大多数数据文件都是ASCII编码的(想要了解更多ASCII的信息?在终端里使用man ASCII
即可!),在大多数情况下编码和我们要解决的问题没啥关系,但是在某些特殊情况中,比如存在一个非ASCII字符时候,我们需要从基本的原始编码数据上查看文件,这需要我们掌握一些Unix提供的工具。
首先是如何查看文件的编码类型。Unix提供了file
命令,其可以根据文件内容自动推断出文件的编码类型。
同时,UTF-8也是一个常见的文件编码格式,它是ASCII的一个拓展,支持更多特殊字符:
由于UTF-8本身属于ASCII的拓展,如果我们将文件中所有的非ASCII字符全部删去,file
会返回ASCII编码。
对于大多数你拿到的数据文件来说,其都是标准的ASCII编码文件,比如从NCBI下载下来的数据。但是对于一些他人手动整理的数据文件,请务必小心,因为其中还很可能混入一些不正确的字符编码!有时候这种情况及其隐蔽,作者可能都不知情。
比如有一个improper.fa
文件,表面上它是一个很正常的FASTA文件:
❯ cat improper.fa
>good-sequence
AGCTAGCTACTAGCAGCTACTACGAGCATCTACGGCGCGATCTACG
>bad-sequence
GATCAGGCGACATCGAGCTATCACTACGAGCGAGΑGATCAGCTATT
让我们使用hexdump
这个命令行工具来一探究竟:
❯ hexdump -c improper.fa
0000000 > g o o d - s e q u e n c e \n A
0000010 G C T A G C T A C T A G C A G C
0000020 T A C T A C G A G C A T C T A C
0000030 G G C G C G A T C T A C G \n > b
0000040 a d - s e q u e n c e \n G A T C
0000050 A G G C G A C A T C G A G C T A
0000060 T C A C T A C G A G C G A G 316 221
0000070 G A T C A G C T A T T \n
000007c
可以发现,在"CGAGCGAG"后跟着两个不是对劲的非ASCII字符,但是当我们将其当做纯ASCII文本打印在终端上,两个非ASCII字符会被忽略,从而看起来没啥问题。
使用file
命令也能发现不对劲:
当然,另一种查看非ASCII字符的方法,是使用grep
命令,不过其比较复杂:
❯ LC_CTYPE=C grep --color='auto' -P "[\x80-\xFF]" improper.fa
GATCAGGCGACATCGAGCTATCACTACGAGCGAG��GATCAGCTATT
我强烈建议将其设置一个别名,因为它既快速也好用。
总的来说,file
,hexdump
和grep
都为我们检查文件编码问题提供了方便,还是那句话:不要太信任你的数据。
od
hexdump
是一个随着BSD Unix流行起来的命令行工具,而本身不属于POSIX标准,也就是说,有些类Unix可能不含有它。
而POSIX标准里支持的查看文件原始编码信息的,是od
(Octal Dump),顾名思义,其的重心在八进制输出上。
od
的跨平台性更好,因为它是POSIX标准的一部分,其本身也支持丰富的格式输出,在很多时候其是不逊色于hexdump
的。但是hexdump
通常被认为更适合复杂的二进制分析任务,或者逆向工程。
Storing Plain-Text Data with Sort
可以理解的是,排序后的数据无论对于计算机还是对于人类,都是一个更良好的数据结构,比起随机排序的数据来说。
特别是在生信分析中,面对超大量的样本,排序良好的数据对某些命令来说是一种优化,意味着它们的运行效率更高,甚至可以覆盖掉预先排序的时间损耗。
在本节中,我们主要讨论sort
命令的使用。类似于cut
,sort
主要面向表格形式文件的列操作,如果不指定任何参数,sort
会简单以行为单位,按照字母顺序排列:
❯ cat example.bed
chr1 26 39
chr1 32 47
chr3 11 28
chr1 40 49
chr3 16 27
chr1 9 28
chr2 35 54
chr1 10 19
❯ sort example.bed
chr1 10 19
chr1 26 39
chr1 32 47
chr1 40 49
chr1 9 28
chr2 35 54
chr3 11 28
chr3 16 27
Tip
默认情况下,sort
以空白符号(如制表符或空格)作为字段分隔符,如果你在处理的文件使用其他分隔符(例如CSV),可以通过-t
指定(e.g., -t ","
)。
当然,仅以字母顺序进行排列无法满足我们的实际需求.对于sort
,我们希望它可以:
- 按照指定的列进行排序
- 能够指定特定的值类型(比如数值类型)
显然,这些需求对于sort
来说不过是顺手的事,让我们用它按照染色体编号和起始位置来对example.bed
排序:
❯ sort -k1,1 -k2,2n example.bed
chr1 9 28
chr1 10 19
chr1 26 39
chr1 32 47
chr1 40 49
chr2 35 54
chr3 11 28
chr3 16 27
此处我们使用参数-k
来指定用来排序的列,该参数需要分别指定一个起始列和一个结束列,比如-k1,1
以第一列为排序依据列。同一个sort
可以有多个排序依据列,按照先后顺序被依次满足(默认下是升序)。
Reverse Sorting
默认情况下sort
采用升序(无论是字典序还是数值排序)排序,我们可以指定-r
参数来反转排序,也就是使用降序排序。
❯ sort -r -k1,1 -k2,2n example.bed
chr3 11 28
chr3 16 27
chr2 35 54
chr1 9 28
chr1 10 19
chr1 26 39
chr1 32 47
chr1 40 49
直接指定-r
参数是对整体的排序进行反转,但是如果你只想在某一列上的排序反转,只需要在对应的-k
参数后加上r
即可。
特别地,对于-k2,2n
中n的含义为数值类型,默认下sort
以字典序排序,指定n可以让其以数值类型进行排序。当然,如果你希望sort
完全以数值类型排序,可以指定-n
参数,而无需额外在-k
中指明。
Warning
或许你在写Python,或者别的地方了解到过,对于两个拥有完全相同排序键的对象,排序算法是无法将其区分的。很多时候,排序算法会试图通过某种方法来指定两者先后,其结果通常是不符合预期的。
而对于类似于Python中sorted
函数来说,它面对两个排序键完全相同的对象时候,会选择遵循其原顺序。有这种特性的排序算法被称作是稳定的(stable)。
对于sort
来说,如果不想要它随意改变两个“相同行”的原顺序,你可以指定-s
参数使其稳定。
当然,sort
是一种消耗大量资源的命令,在执行其之前,先检查一遍目标文件是否已排序是一个更为经济的选择。对于sort
来说,指定参数-c
使其启用检查模式,若目标文件已排序,则退出代码为0,否则为1。
❯ sort -k1,1 -k2,2n -c example.bed
sort: example.bed:4: disorder: chr1 40 49
❯ echo $?
1
❯ sort -k1,1 -k2,2n -c example_sorted.bed
❯ echo $?
0
此外,sort
还有很多十分实用的参数值得了解,但首先要说明的是,这些参数仅在 GUN sort
中被支持。
首先是-V
,使用这个参数可以启用更加智能的对于字符串中数字的识别与排序。
❯ cat example2.bed
chr2 15 19
chr22 32 46
chr10 31 47
chr1 34 49
chr11 6 16
chr2 17 22
chr2 27 46
chr10 30 42
❯ sort -k1,1 -k2,2n example2.bed
chr1 34 49
chr10 30 42
chr10 31 47
chr11 6 16
chr2 15 19
chr2 17 22
chr2 27 46
chr22 32 46
在上面这个例子中,sort
命令似乎做的很完美,它按照我们给定的列对文本文件做了排序操作,但是请注意第一列,对于sort
来说,chr11
在ASCII编码的角度下显然是小于chr2
的(因为在第四位的比较上1
小于2
),但是这显然不符合人的一般认知,我们都会把chr11
和chr2
中的11
和2
当做数字来看待。
如果我们启用-V
参数,sort
可以更加智能的识别字符串中的数字。
❯ sort -k1,1V -k2,2n example2.bed
chr1 34 49
chr2 15 19
chr2 17 22
chr2 27 46
chr10 30 42
chr10 31 47
chr11 6 16
chr22 32 46
sort
有一个独到的优点使得其对大文件的排序处理也得心应手——其采用归并排序。这种算法的一大亮点在于:当文件体积超出内存容量时,它会将已排序的中间结果暂存至磁盘进行处理,这使得有限的内存可以处理尽可能大的文本文件。
Merge Sort
归并排序是一种“分而治之”的排序算法,其将待排序的对象分为子问题,再将子问题作为父问题做进一步分解,直至问题可以直接被解决(比如两个数字之间的比较),然后一步步将无数子问题的结果合并为各自父问题的结果,最终推回原本的问题的结果。
这种算法天生适合递归方式,因为其具有可分解为相似的子问题的特质。这种算法同样也利好于问题的简单化,这意味着我们不必“一口气吃成胖子”,如果问题(比如未排序的文件)太大,我们可以选取其的子问题来解决(子问题太大,那就继续分解),最后逐步将已经解决的问题拼起来。
但需注意的是,对磁盘的读写速度远远慢于对内存的读写速度(数量级上的差异),如果sort
一次性可以支配的内存过小,频繁的IO操作将会是工具的性能瓶颈,幸运的是,-S
允许我们设置sort
的内存缓冲区大小。
-S
参数很聪明,它能理解K、M和G等字节单位,同样支持使用百分比(比如-S50%
代表使用50%的内存)。
同样的,如果允许sort
调用更多CPU核心并行处理(毕竟有很多个子问题需要处理),同样可以显著提升文件排序的速度,这可以通过-parallel
实现。
Note