2.9 排序、唯一与重复

同文本文件打交道时,少不了要用到排序。sort命令能够帮助我们对文本文件和stdin进行排序操作。它通常会配合其他命令来生成所需要的输出。uniq是一个经常与sort一同使用的命令。它的作用是从文本或stdin中提取唯一(或重复)的行。这则攻略将演示sortuniq命令的常见用法。

2.9.1 预备知识

sort命令既可以从特定的文件,也可以从stdin中获取输入,并将输出写入stdoutuniq的工作方式和sort一样。

2.9.2 实战演练

  1. 我们可以按照下面的方式轻松地对一组文件(例如file1.txt和file2.txt)进行排序:

    $ sort file1.txt file2.txt > sorted.txt
    

    或是

    $ sort file1.txt file2.txt -o sorted.txt
    
  2. 按照数字顺序进行排序:

    $ sort -n file.txt
    
  3. 按照逆序进行排序:

    $ sort -r file.txt
    
  4. 按照月份进行排序(依照一月,二月,三月……):

    $ sort -M months.txt
    
  5. 合并两个已排序过的文件:

    $ sort -m sorted1 sorted2
    
  6. 找出已排序文件中不重复的行:

    $ sort file1.txt file2.txt | uniq
    
  7. 检查文件是否已经排序过:

    #!/bin/bash
    #功能描述:排序
    sort -C filename ;
    if [ $? -eq 0 ]; then
       echo Sorted;
    else
       echo Unsorted;
    fi
    

    filename替换成你需要进行检查的文件名,然后运行该脚本。

2.9.3 工作原理

sort命令包含大量的选项,能够对文件数据进行各种排序。如果使用uniq命令,那sort更是必不可少,因为前者要求输入数据必须经过排序。

sortuniq可以应用于多种场景。让我们来看一下这些命令的各种选项及用法。

要检查文件是否排序过,可以采用以下方法:如果文件已经排序,sort会返回为0的退出码($?),否则返回非0。

2.9.4 补充内容

我们已经介绍了sort命令的基本用法。下面来看看如何利用sort来完成一些复杂的任务。

  1. 依据键或列进行排序

    我们可以按列将下面的文本排序:

    $ cat data.txt
    1  mac    2000
    2  winxp    4000
    3  bsd    1000
    4  linux    1000
    

    有很多方法可以对这段文本排序。目前它是按照序号(第一列)来排序的。我们也可以依据第二列和第三列来排序。

    -k指定了排序应该按照哪一个键(key)来进行。键指的是列号,而列号就是执行排序时的依据。-r告诉sort命令按照逆序进行排序。例如:

    # 依据第1列,以逆序形式排序
    $ sort -nrk 1  data.txt
    4  linux    1000
    3  bsd    1000
    2  winxp    4000
    1  mac    2000
    # -nr表明按照数字,采用逆序形式排序
     
    # 依据第2列进行排序
    $ sort -k 2  data.txt
    3  bsd    1000
    4  linux    1000
    1  mac    2000
    2  winxp    4000
    

    一定要留意用于按数字顺序进行排序的选项-nsort命令对于字母表排序和数字排序有不同的处理方式。因此,如果要采用数字顺序排序,就应该明确地给出-n选项。

    通常在默认情况下,键就是文本文件中的列。列与列之间用空格分隔。但有时候,我们需要将特定范围内的一组字符(例如,key1=character4-character8)作为键。在这种情况下,必须明确地将键指定为某个范围内的字符,这个范围可以用键起止的字符位置来表明。例如:

    $ cat data.txt
    1010hellothis
    2189ababbba
    7464dfddfdfd
    $ sort -nk 2,3 data.txt
    

    把醒目的字符作为数值键。为了提取这个键,用字符在行内的起止位置作为键的书写格式(在上面的例子中,起止位置是2和3)。

    用第一个字符作为键:

    $ sort -nk 1,1 data.txt
    

    为了使sort的输出与以\0作为终止符的xargs命令相兼容,采用下面的命令:

    $ sort -z data.txt | xargs -0
    # 终止符\0用来保证xargs命令的使用安全
    

    有时文本中可能会包含一些像空格之类的不必要的多余字符。如果需要忽略这些字符,并以字典序进行排序,可以使用:

    $ sort -bd unsorted.txt
    

    其中,选项-b用于忽略文件中的前导空白行,选项-d用于指明以字典序进行排序。

  2. uniq

    uniq命令通过消除重复内容,从给定输入中(stdin或命令行参数文件)找出唯一的行。它也可以用来找出输入中出现的重复行。

    uniq只能作用于排过序的数据输入,因此,uniq要么使用管道,要么将排过序的文件作为输入,与sort命令结合使用。

    你可以按照下面的方式从给定的输入数据中生成唯一的行(所谓“唯一的行”是指来自输入的所有行都会被打印出来,但是其中重复的行只会被打印一次):

    $ cat sorted.txt
    bash
    foss
    hack
    hack
     
    $ uniq sorted.txt
    bash
    foss
    hack
    

    或是

    $ sort unsorted.txt | uniq
    

    只显示唯一的行(在输入文件中没有重复出现的行):

    $ uniq -u sorted.txt
    bash
    foss
    

    或是

    $ sort unsorted.txt | uniq -u
    

    要统计各行在文件中出现的次数,使用下面的命令:

    $ sort unsorted.txt | uniq -c
          1 bash
          1 foss
          2 hack
    

    找出文件中重复的行:

    $ sort unsorted.txt | uniq -d
    hack
    

    我们可以结合-s-w来指定键:

    • -s指定可以跳过前n个字符;

    • -w指定用于比较的最大字符数。

    这个对比键被用作uniq操作的索引:

    $ cat data.txt
    u:01:gnu
    d:04:linux
    u:01:bash
    u:01:hack
    

    我们需要使用醒目的字符作为唯一的键。可以通过忽略前2个字符(-s 2),使用-w选项(-w 2)指定用于比较的最大字符数。

    $ sort data.txt | uniq -s 2 -w 2
    d:04:linux
    u:01:bash
    

    我们将命令输出作为xargs命令的输入时,最好为输出的各行添加一个0值字节(zero-byte)终止符。在将uniq命令的输入作为xargs的数据源时,同样应当如此。如果没有使用0值字节终止符,那么在默认情况下,xargs命令会用空格作为定界符分割参数。例如,来自stdin的文本行“this is a line”会被xargs当做包含4个不同的参数,但实际上它只是一个单行而已。如果使用0值字节终止符,那么\0就被作为定界符,此时,包含空格的单行就能够被正确地解析成单个参数。

    uniq命令生成包含0值字节终止符的输出:

    $ uniq -z file.txt
    

    下面的命令将删除所有指定的文件,这些文件的名字是从files.txt中读取的:

    $ uniq -z file.txt | xargs -0 rm
    

    如果某个文件名在文件中出现多次,uniq命令只会将这个文件名写入stdout一次。