MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Bash文本排序与去重:sort与uniq命令

2022-09-265.9k 阅读

Bash 中的排序工具:sort 命令

在处理文本数据时,排序是一项常见的操作。Bash 提供了 sort 命令,它功能强大且灵活,能满足各种文本排序需求。

基本用法

sort 命令的基本语法很简单:sort [选项] [文件]。如果不指定文件,sort 会从标准输入读取数据。例如,假设有一个名为 data.txt 的文件,内容如下:

apple
banana
cherry
date

要对这个文件进行排序,可以使用以下命令:

sort data.txt

执行上述命令后,输出结果会按照字母顺序排列:

apple
banana
cherry
date

按数字排序

默认情况下,sort 按字符顺序对文本行进行排序。如果要按数字顺序排序,需要使用 -n 选项。假设有一个包含数字的文件 numbers.txt,内容如下:

10
5
20
3

使用 -n 选项进行数字排序:

sort -n numbers.txt

输出结果为:

3
5
10
20

反向排序

使用 -r 选项可以进行反向排序。例如,对 data.txt 进行反向字母排序:

sort -r data.txt

输出结果为:

date
cherry
banana
apple

同样,对于数字排序也可以使用 -r 实现反向数字排序。对 numbers.txt 进行反向数字排序:

sort -nr numbers.txt

输出结果为:

20
10
5
3

按字段排序

在处理表格形式的数据时,常常需要按特定字段进行排序。假设我们有一个文件 students.txt,每行包含学生的姓名、年龄和成绩,格式为“姓名 年龄 成绩”:

Alice 20 85
Bob 19 78
Charlie 21 90

要按年龄字段进行排序,可以使用 -k 选项指定排序字段。年龄在第二列,所以命令如下:

sort -k 2n students.txt

这里 -k 2n 表示按第二列进行数字排序,输出结果为:

Bob 19 78
Alice 20 85
Charlie 21 90

如果要按成绩进行反向排序,成绩在第三列,命令为:

sort -k 3nr students.txt

输出结果为:

Charlie 21 90
Alice 20 85
Bob 19 78

忽略大小写排序

有时候我们希望在排序时忽略大小写,使用 -f 选项可以实现这一点。假设有一个文件 fruits.txt,内容如下:

Apple
banana
Cherry
Date

使用 -f 选项进行忽略大小写排序:

sort -f fruits.txt

输出结果会将大小写视为相同,按字母顺序排列:

Apple
banana
Cherry
Date

处理重复行

sort 命令本身不会去除重复行,但在与 uniq 命令结合使用时可以实现去重功能,这将在后面关于 uniq 命令的部分详细介绍。不过,sort 有一个 -u 选项,它在排序的同时会去除完全相同的相邻行。假设有一个文件 duplicates.txt,内容如下:

apple
apple
banana
cherry
cherry

使用 -u 选项:

sort -u duplicates.txt

输出结果为:

apple
banana
cherry

需要注意的是,-u 选项只会去除相邻的重复行。如果重复行不相邻,它不会起作用。例如,将 duplicates.txt 修改为:

apple
banana
apple
cherry
cherry

执行 sort -u duplicates.txt,输出结果为:

apple
banana
apple
cherry

要处理这种情况,就需要先使用 sort 进行全面排序,再结合 uniq 命令。

其他常用选项

  • -b:忽略每行前面的空白字符。例如,有一个文件 whitespace.txt,内容如下:
  apple
banana
  cherry

使用 -b 选项:

sort -b whitespace.txt

输出结果会将开头的空白字符忽略,按字母顺序正确排序:

  apple
  cherry
banana
  • -o:将排序结果输出到指定文件,而不是标准输出。例如,要将 data.txt 的排序结果输出到 sorted_data.txt,可以使用以下命令:
sort data.txt -o sorted_data.txt

这样,sorted_data.txt 文件中就会保存排序后的内容。

文本去重工具:uniq 命令

在处理文本数据时,去除重复行是一个常见需求。uniq 命令用于报告或忽略文件中的重复行。

基本用法

uniq 命令的基本语法为:uniq [选项] [输入文件] [输出文件]。如果不指定输入文件,uniq 会从标准输入读取数据。假设我们有一个经过排序的文件 sorted_duplicates.txt,内容如下:

apple
apple
banana
cherry
cherry

使用 uniq 命令去除相邻的重复行:

uniq sorted_duplicates.txt

输出结果为:

apple
banana
cherry

需要强调的是,uniq 命令只对相邻的重复行起作用。如果文件中的重复行不相邻,需要先使用 sort 命令对文件进行排序,然后再使用 uniq。例如,对于一个未排序且有不相邻重复行的文件 unsorted_duplicates.txt,内容如下:

apple
banana
apple
cherry
cherry

先使用 sort 命令排序:

sort unsorted_duplicates.txt > sorted_unsorted_duplicates.txt

然后再使用 uniq 命令:

uniq sorted_unsorted_duplicates.txt

这样才能正确去除所有重复行,输出结果为:

apple
banana
cherry

统计重复行数

uniq 命令提供了 -c 选项,用于在每行开头显示该行重复出现的次数。对于 sorted_duplicates.txt 文件,使用 -c 选项:

uniq -c sorted_duplicates.txt

输出结果为:

  2 apple
  1 banana
  2 cherry

这在分析数据中重复项的出现频率时非常有用。

忽略指定字段和字符

uniq 命令可以使用 -f 选项忽略前 n 个字段,使用 -s 选项忽略前 n 个字符。假设有一个文件 fields.txt,内容如下:

1 apple
1 banana
2 apple
2 cherry

如果要忽略第一列字段来判断重复行,可以使用 -f 1 选项:

uniq -f 1 fields.txt

输出结果为:

1 apple
1 banana
2 cherry

这里第一列的数字被忽略,只根据后面的水果名称判断重复行。

同样,如果使用 -s 选项,例如要忽略前两个字符来判断重复行。假设有一个文件 chars.txt,内容如下:

ap apple
ap banana
ch cherry
ch apple

使用 -s 2 选项:

uniq -s 2 chars.txt

输出结果为:

ap apple
ap banana
ch cherry

这里前两个字符被忽略,根据剩余字符判断重复行。

比较行的特定字符位置

uniq 命令还可以通过 -w 选项指定比较行的特定字符位置。假设有一个文件 positions.txt,内容如下:

apple1
apple2
banana1
banana2

如果只比较每行的前 5 个字符来判断重复行,使用 -w 5 选项:

uniq -w 5 positions.txt

输出结果为:

apple1
banana1

因为只比较前 5 个字符,所以 apple1apple2 被视为重复行,banana1banana2 同理。

sort 与 uniq 命令的结合使用

在实际应用中,sortuniq 命令经常结合使用来实现更复杂的文本处理需求。

全面去重

如前文所述,对于包含不相邻重复行的文本文件,先使用 sort 命令对文件进行排序,然后再使用 uniq 命令去除所有重复行。例如,对于一个杂乱的包含重复行的文件 random_duplicates.txt,内容如下:

cherry
apple
banana
apple
cherry

首先使用 sort 命令排序:

sort random_duplicates.txt > sorted_random_duplicates.txt

然后使用 uniq 命令去重:

uniq sorted_random_duplicates.txt

最终得到去重后的结果:

apple
banana
cherry

这一过程可以通过管道操作简化为一条命令:

sort random_duplicates.txt | uniq

管道操作符 |sort 命令的输出作为 uniq 命令的输入,实现了排序和去重的连续操作。

统计不同项及其出现次数

结合 sortuniq -c 可以统计文本中不同项及其出现的次数。假设有一个文件 words.txt,内容如下:

apple
banana
apple
cherry
banana
apple

先使用 sort 命令排序:

sort words.txt > sorted_words.txt

然后使用 uniq -c 统计重复次数:

uniq -c sorted_words.txt

输出结果为:

  3 apple
  2 banana
  1 cherry

同样,通过管道操作可以简化为一条命令:

sort words.txt | uniq -c

这样可以快速统计文本中每个不同项的出现频率,在数据分析、词频统计等场景中非常实用。

按特定字段排序并去重

当处理表格形式的数据时,可能需要按特定字段排序后再去重。例如,有一个文件 employees.txt,每行包含员工的部门、姓名和工资,格式为“部门 姓名 工资”:

HR Alice 5000
IT Bob 6000
HR Alice 5000
IT Charlie 6500

要按部门字段排序并去除重复的员工记录(假设重复定义为部门和姓名都相同),首先按部门字段排序:

sort -k 1 employees.txt > sorted_employees.txt

然后使用 uniq 命令去重,忽略第一列字段来判断重复行:

uniq -f 1 sorted_employees.txt

输出结果为:

HR Alice 5000
IT Bob 6000
IT Charlie 6500

通过管道操作可以简化为:

sort -k 1 employees.txt | uniq -f 1

这种操作在处理数据库导出的表格数据,去除重复记录时非常有效。

实际应用场景

日志分析

在系统日志文件中,常常会有重复的记录。例如,在服务器的访问日志中,可能会有大量相同 IP 地址的访问记录。假设日志文件 access.log 内容如下(每行格式为“IP 地址 时间 访问页面”):

192.168.1.1 2023-10-01 10:00:00 /index.html
192.168.1.2 2023-10-01 10:01:00 /about.html
192.168.1.1 2023-10-01 10:02:00 /index.html
192.168.1.3 2023-10-01 10:03:00 /contact.html

要统计每个 IP 地址的访问次数,可以使用以下命令:

cut -d ' ' -f 1 access.log | sort | uniq -c

这里 cut -d ' ' -f 1 命令用于提取每行的第一个字段,即 IP 地址。然后通过 sort 命令排序,最后使用 uniq -c 统计每个 IP 地址的出现次数。输出结果类似:

  2 192.168.1.1
  1 192.168.1.2
  1 192.168.1.3

这有助于分析哪些 IP 地址访问频率较高,是否存在异常访问等情况。

数据清洗

在处理从各种数据源获取的数据时,可能会包含大量重复数据。例如,从多个渠道收集的客户信息文件 customers.txt,内容如下(每行格式为“姓名 年龄 联系方式”):

Alice 25 alice@example.com
Bob 30 bob@example.com
Alice 25 alice@example.com
Charlie 35 charlie@example.com

要去除重复的客户记录,可以使用:

sort customers.txt | uniq

这将得到清洗后不包含重复记录的客户信息。如果数据量较大,还可以考虑使用更高效的数据库工具进行数据清洗,但在简单场景下,sortuniq 命令提供了一种快速有效的解决方案。

词频统计

在文本分析中,统计单词出现的频率是一个常见任务。假设有一个文本文件 article.txt,内容如下:

This is an example sentence. This sentence is for testing.

要统计每个单词的出现次数,可以使用以下命令:

tr ' ' '\n' < article.txt | sort | uniq -c | sort -nr

这里 tr ' ' '\n' 命令将文本中的空格替换为换行符,使每个单词单独成行。然后通过 sort 排序,uniq -c 统计每个单词的出现次数,最后再次使用 sort -nr 按出现次数从高到低排序。输出结果类似:

  2 This
  2 is
  2 sentence
  1 an
  1 example
  1 for
  1 testing.

这对于分析文本的关键词、主题等非常有帮助。

性能考虑

大数据量处理

当处理大数据量的文本文件时,sortuniq 命令的性能可能会成为问题。对于 sort 命令,在排序大数据文件时,内存使用可能会成为瓶颈。sort 命令默认会将整个文件读入内存进行排序,如果文件过大,可能导致系统内存不足。为了解决这个问题,sort 提供了一些优化选项。例如,-S 选项可以指定排序时使用的内存大小。假设要对一个非常大的文件 large_data.txt 进行排序,并且希望限制使用的内存为 1GB,可以使用以下命令:

sort -S 1G large_data.txt

这样可以在一定程度上控制内存使用,避免系统因内存耗尽而崩溃。

对于 uniq 命令,在处理大数据量时,其本身的性能开销相对较小,因为它主要是基于相邻行的比较。但是,如果在使用 uniq 之前需要先对大数据文件进行排序,那么排序过程的性能问题就会影响整体效率。在这种情况下,除了优化 sort 命令的内存使用外,还可以考虑分块处理数据。即将大文件分成多个小文件,分别对每个小文件进行排序和去重,最后再合并结果。

优化策略

  1. 并行处理:对于多核系统,可以利用并行处理来提高排序和去重的效率。虽然 sortuniq 命令本身没有直接的并行处理选项,但可以结合其他工具实现。例如,parallel 工具可以将大文件分成多个部分,并行地对每个部分进行排序,然后再合并排序结果。这样可以充分利用多核处理器的性能,加快处理速度。
  2. 数据预处理:在对数据进行排序和去重之前,可以进行一些预处理操作来减少数据量。例如,对于日志文件,可以先根据时间范围、特定关键字等条件过滤掉不需要的数据,然后再进行排序和去重。这样可以减少处理的数据量,从而提高整体性能。
  3. 选择合适的工具:在某些情况下,sortuniq 命令可能不是最佳选择。例如,对于非常大的数据集,使用数据库管理系统进行排序和去重可能更高效。数据库系统通常具有更优化的存储和索引结构,能够处理大规模数据的排序和去重操作,并且提供了丰富的查询优化功能。

与其他工具的结合

与 grep 命令结合

grep 命令用于在文本中搜索指定的模式。它可以与 sortuniq 命令结合使用,实现更复杂的文本处理。例如,在一个系统日志文件 syslog.txt 中,要查找所有出现“error”的行,并统计每个错误消息的出现次数,可以使用以下命令:

grep 'error' syslog.txt | cut -d ' ' -f 5- | sort | uniq -c

这里 grep 'error' syslog.txt 用于筛选出包含“error”的行,cut -d ' ' -f 5- 用于提取错误消息部分(假设错误消息从第 5 个字段开始),然后通过 sortuniq -c 统计每个错误消息的出现次数。

与 awk 命令结合

awk 是一种强大的文本处理语言,它可以对文本进行逐行处理,并支持复杂的条件判断和操作。awksortuniq 命令结合可以实现更灵活的文本处理。例如,有一个文件 sales.txt,每行包含销售记录,格式为“日期 产品 销售额”:

2023-10-01 product1 1000
2023-10-01 product2 1500
2023-10-02 product1 1200

要按产品统计总销售额,并按销售额从高到低排序,可以使用以下命令:

awk '{sum[$2]+=$3} END {for (product in sum) print product, sum[product]}' sales.txt | sort -k 2nr

这里 awk 命令用于按产品统计销售额,sum[$2]+=$3 表示将每个产品的销售额累加到对应的变量中。END 块用于在处理完所有行后输出结果。然后通过 sort -k 2nr 按销售额(第二列)从高到低排序。

通过将 sortuniq 命令与其他文本处理工具结合使用,可以充分发挥 Bash 脚本的强大功能,实现复杂的文本处理任务。无论是在系统管理、数据分析还是文本处理等领域,这些工具的组合都能为用户提供高效的解决方案。在实际应用中,需要根据具体的需求和数据特点,选择合适的工具和命令组合,以达到最佳的处理效果。同时,要注意性能优化,特别是在处理大数据量时,合理利用系统资源,确保任务能够快速、稳定地完成。