2.8 排序、单一与重复

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

2.8.1 预备知识

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

2.8.2 实战演练

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

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

或是

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

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

  1. $ cat sorted_file.txt | uniq> uniq_lines.txt

2.8.3 工作原理

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

按数字进行排序:

  1. $ sort -n file.txt

按逆序进行排序:

  1. $ sort -r file.txt

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

  1. $ sort -M months.txt

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

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

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

  1. $ sort -m sorted1 sorted2

2.8.4 补充内容

  • 依据键或列进行排序

如果需要将下面的文本排序,我们可以按列来进行。

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

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

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

  1. # 依据第1列,以逆序形式排序
  2. $ sort -nrk 1 data.txt
  3. 4 linux 1000
  4. 3 bsd 1000
  5. 2 winxp 4000
  6. 1 mac 2000
  7. # -nr表明按照数字,采用逆序形式排序
  8.  
  9. # 依据第2列进行排序
  10.  
  11. $ sort -k 2 data.txt
  12. 3 bsd 1000
  13. 4 linux 1000
  14. 1 mac 2000
  15. 2 winxp 4000

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

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

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

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

用第一个字符作为键:

  1. $ sort -nk 1,1 data.txt

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

  1. $ sort -z data.txt | xargs -0
  2. # 终止符\0使得xargs命令的使用更加安全

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

  1. $ sort -bd unsorted.txt

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

  • uniq

uniq命令通过消除重复内容,从给定输入中(stdin或命令行参数文件)找出单一的行。它也可以用来找出输入中出现的重复行。uniq只能用于排过序的数据输入,因此,uniq要么使用管道,要么将排过序的文件作为输入,并总是以这种方式与sort命令结合起来使用。

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

  1. $ cat sorted.txt
  2. bash
  3. foss
  4. hack
  5. hack
  6.  
  7. $ uniq sorted.txt
  8. bash
  9. foss
  10. hack

或是

  1. $ sort unsorted.txt | uniq

或是

  1. $ sort -u unsorted.txt

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

  1. $ uniq -u sorted.txt
  2. bash
  3. foss

或是

  1. $ sort unsorted.txt | uniq -u

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

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

找出文件中重复的行:

  1. $ sort unsorted.txt | uniq -d
  2. hack

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

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

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

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

  1. $ cat data.txt
  2. u:01:gnu
  3. d:04:linux
  4. u:01:bash
  5. u:01:hack

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

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

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

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

  1. $ uniq -z file.txt

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

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

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

  • uniq生成字符串样式

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

输入:ahebhaaa

输出:4a1b1e2h

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

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

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

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

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

  • sed '/^$/d':最后一个字符会被sed替换成“字符+\n”,因此会多出一个换行符并在最后形成一个空行。而这个命令就用来删除这最后的空行。

  • sort:因为每行只有一个字符,所以可以进行排序,并可以将排序结果作为uniq的输入。

  • uniq -c:这个命令打印出每一行各重复了多少次(计数)。

  • tr -d '\n':将输入中的空格和换行符删除,生成所要求的输出格式。