同文本文件打交道时,总避不开排序,那是因为对于文本处理任务而言,排序(sort)可以起到不小的作用。sort命令能够帮助我们对文本文件和stdin进行排序操作。通常,它会结合其他命令来生产所需要的输出。uniq是一个经常与sort一同使用的命令。它的作用是从文本或stdin中提取单一的行。sortuniq能够用来查找重复数据。这则攻略将演示sortuniq命令的大多数用法。

1. 预备知识

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

2. 实战演练

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

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

或是

$ sort file1.txt file2.txt .. -o sorted.txt

找出已排序文件中不重复的行:

$ cat sorted_file.txt | uniq> uniq_lines.txt

###3. 工作原理

sortuniq可以派上用场的地方有很多。让我们来认识一些命令选项和使用方法。

按数字进行排序:

$ sort -n file.txt

按逆序进行排序:

$ sort -r file.txt

按月份进行排序(按照一月、二月、三月……这样的顺序):

$ sort -M months.txt

还可以用下面的方法测试一个文件是否已经被排过序:

#!/bin/bash
#用途: 排序
sort -c file ;
if [ $? -eq 0 ]; then
   echo Sorted;
else
   echo Unsorted;
fi
#要检查是否按数字进行排序,应该使用sort -nC

如果需要合并两个排过序的文件,而且不需要对合并后的文件再进行排序,可以使用:

$ sort -m sorted1 sorted2

4. 补充内容

  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
    

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

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

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

    突出显示的字符将用作数值键。为了提取这个键,用字符的起止位置作为键的书写格式。

    用第一个字符作为键:

    $ 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
    

    或是

    $ sort -u unsorted.txt
    

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

    $ 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值字节终止符。在将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一次。

  3. uniq生成字符串样式

    这里有一个很有意思的问题:我们有一个包含重复字符的字符串,如何才能知道每个字符在字符串中出现的次数,并依照下面的格式输出字符串?

    输入:ahebhaaa

    输出:4a1b1e2h

    任何一个字符只要出现重复,就在之前加上它在字符串中出现过的次数。我们可以结合运用uniqsort来解决这个问题:

    INPUT= "ahebhaaa"
    OUTPUT=` echo $INPUT | sed 's/[^\n]/&\n/g' | sed '/^$/d' | sort | uniq
    -c | tr -d ' \n'`
    echo $OUTPUT
    

    将上面代码中的管道命令进行分解:

    echo $INPUT # 将输入打印到stdout
    sed 's/[^.]/&\n/g'
    

    在每个字符后追加一个换行符,使得每行只出现一个字符。这让我们可以用sort命令对字符进行排序。sort命令只能用于由换行符分隔的记录上。

    • sed '/^$/d':最后一个字符会被sed替换成“字符+\n”,因此会多出一个换行符并在最后形成一个空行。而这个命令就用来删除这最后的空行。
    • sort:因为每行只有一个字符,所以可以进行排序,并可以将排序结果作为uniq的输入。
    • uniq -c:这个命令打印出每一行各重复了多少次(计数)。
    • tr -d '\n':将输入中的空格和换行符删除,生成所要求的输出格式。