2.8 排序、单一与重复
同文本文件打交道时,总避不开排序,那是因为对于文本处理任务而言,排序(sort)可以起到不小的作用。sort
命令能够帮助我们对文本文件和stdin
进行排序操作。通常,它会结合其他命令来生产所需要的输出。uniq
是一个经常与sort
一同使用的命令。它的作用是从文本或stdin
中提取单一的行。sort
和uniq
能够用来查找重复数据。这则攻略将演示sort
和uniq
命令的大多数用法。
2.8.1 预备知识
sort
命令既可以从特定的文件,也可以从stdin
中获取输入,并将输出写入stdout
。uniq
的工作模式和sort
一样。
2.8.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
2.8.3 工作原理
sort
和uniq
可以派上用场的地方有很多。让我们来认识一些命令选项和使用方法。
按数字进行排序:
- $ 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
2.8.4 补充内容
- 依据键或列进行排序
如果需要将下面的文本排序,我们可以按列来进行。
- $ 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
用于指明以字典序进行排序。
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
一次。
- 用
uniq
生成字符串样式
这里有一个很有意思的问题:我们有一个包含重复字符的字符串,如何才能知道每个字符在字符串中出现的次数,并依照下面的格式输出字符串?
输入:ahebhaaa
输出:4a1b1e2h
任何一个字符只要出现重复,就在之前加上它在字符串中出现过的次数。我们可以结合运用uniq
和sort
来解决这个问题:
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'
:将输入中的空格和换行符删除,生成所要求的输出格式。