-- 作者 谢恩铭 转载请注明出处


《Linux探索之旅》全系列

内容简介


  1. 前言
  2. 成果展示
  3. 解题步骤和答案
  4. 可能的优化
  5. 第五部分第九课预告

1. 前言


上一课 Linux探索之旅 | 第五部分第七课:Shell实现图片展示网页 中,我们做了一个有趣的练习。

这个练习用一个 Shell 脚本来生成一个 HTML 文件,这个 HTML 文件是一个展示图片缩略图的网页,点击每个缩略图还会链接到原始图片。

这一课我们继续做一个进阶的 Shell 脚本练习。这个练习要实现的是对一个英语字典做统计。

通过这个练习,你将巩固 Shell 和 Linux 的知识点。

为了完成它,我们需要用到一个文本文件:words.txt。这是一个包含 354935 个英文单词的字典,请从我的 Github 上下载 (下面也会给出百度云盘下载链接):

github.com/frogoscar/e…

你可以选择 git clone 到你本地目录,或者下载 zip 压缩包。然后提取里面的 words.txt 文件即可。

对于 Git 和 Github 的使用有任何疑问,欢迎阅读我的文章 :Git,Github和Gitlab简介和基本使用

对于不用 Github 的朋友,我也把字典文件上传到百度云盘了,请 点我下载 。

当然了,如果你自己能在网上找到其他完整的英文字典的文本文档也可以,不一定要用我这个。

2. 成果展示


我们要用到的字典文本文档里的内容类似如下:

字典开头
字典结尾

我们要写一个 Shell 脚本,来显示这个庞大的字典中 26 个英文字母(从 a 到 z )出现的次数,而且以次数最多到最少的顺序排列。

成果是像下面这样的:

可以看到,字母 e 出现的次数最多,是 363325 次; 字母 j 出现的次数最少,是 5073 次。

下面给出我的解题步骤和答案,希望大家最好先不看答案,尝试着自己解决问题,然后再来看答案。
你的解法也许比我还要好。相信你可以的,加油!

3. 解题步骤和答案


首先,我们创建一个文件夹,然后把 words.txt 这个字典文件放进去。

然后,我们在文件夹中创建一个文件,就是我们的脚本,叫 statistics.sh 好了,因为 statistics 是英语「统计」的意思。

vim statistics.sh复制代码

因为这个练习涉及到数据的处理,所以可以回去参考 Linux探索之旅 | 第三部分第一课:数据处理,慢条斯理 那一课。

还有 「管道、流、重定向」 ( Linux探索之旅 | 第三部分第二课:流、管道、重定向,三管齐下 )等等。

你也许还会在使用一些命令时忘了如何用,那你可以查一下命令的使用手册 ( Linux探索之旅 | 第二部分第八课:RTFM 阅读那该死的手册 )。

根据上面的成果那张截图,我们可以看到要实现的是 :

「在终端打印出结果,按照字母出现的次数来排列,由最多到最少。在次数左边,依次是 该次数对应的字母、空格、短横杠、空格。而且每个字母是大写的(在字典文件中字母都是小写,因此需要小写到大写的转换)」。

因此,我们首先需要统计每个字母出现的次数。

怎么做呢?我们想到了 grep 命令,它可以帮助我们在文件中查找所需的字母。

我们首先用命令行来测试,之后再着手编写我们的 statistics.sh 这个文件。

首先,在命令行中输入以下命令:

grep -io a words.txt复制代码

回车运行后可以看到输出了许多行,每一行包含一个 a。

因为 grep 就是用于在文件中查找关键字,并且显示关键字所在的行。

这里我们用了 -i 和 -o 两个参数。-i 参数我们之前学过,是 ignore-case 的简写,表示「忽略大小写」。

而-o 这个参数我们之前没学过,不过可以用 man grep 来看看:

man grep复制代码

可以看到 -o 参数中的 o 是英语 only-matching 的简写,表示「只匹配」。其描述 「 Print only the matched (non-empty) parts of a matching line, with each such part on a separate output line. 」可以翻译为 「只显示匹配行中不为空的那个匹配的部分,每个这样的部分被单独显示在一行上」。

如果不加 -o 参数而直接用

grep -i a words.txt复制代码

那么输出是这样的:

理解了吗?不加 -o 参数,那么 grep 就会输出每一个包含 a 的行。而每一行 (字典文件中一行有一个单词)也许包含不止一个 a。因此为了统计所有的 a,我们须要加上 -o 参数。

既然我们已经用 grep -io a words.txt 命令来输出了所有字母 a 的 出现(逐行显示),那么我们可以用 wc -l 命令来统计行数,即可知道 a 的出现次数了。

接下来我们就用管道来把 grep 命令的结果赋给 wc 命令:

grep -io a words.txt | wc -l复制代码

可以看到输出是 273400,表示 words.txt 文件中字母 a 出现了 273400 次。

我们也可以不加 -o 参数来测试一下:

grep -i a words.txt | wc -l复制代码

可以看到输出是 206518,比 273400 少了很多,因为不加 -o 参数只统计了 a 出现的那些行(相当于统计了包含 a 的单词数目),而不是统计 a 的真正出现次数。

我们现在已经知道如何统计字母 a 的次数了,那么举一反三,统计其他 25 个字母也不在话下。我们可以用一个循环语句来实现:

for char in {a..z}; dogrep -io "$char" words.txt | wc -l
done复制代码

可以看到我们在终端输入 for 循环语句后,依次打印出了 a, b, c, 一直到 z 这 26 个字母在 words.txt 文件中出现的次数。

虽然现在我们只是开了个头,但是已经可以来写我们的 Shell 脚本了。

我们首先写一些基础的部分:

#!/bin/bash# Verification of parameter
# 确认参数if [ -z $1 ]
thenecho "Please enter the file of dictionary !"exit
fi# Verification of file existence
# 确认文件存在if [ ! -e $1 ]
thenecho "Please make sure that the file of dictionary exists !"exit
fi复制代码

上面两段代码分别用于确认参数和确认文件存在,如果不满足 if 条件,那么用 echo 显示提示信息,然后用 exit 命令退出 Shell。

然后,我们来定义一个函数,就叫 statistics 好了,我们继续在 statistics.sh 这个文件中加入以下代码:

# Definition of function
# 函数定义statistics () {for char in {a..z}doecho "$char - `grep -io "$char" $1 | wc -l`"done
}# Use of function
# 函数使用statistics $1复制代码

for char in {a..z} 不难理解,用于遍历 a 到 z 这 26 个英语字母。

echo "$char - `grep -io "$char" $1 | wc -l`" 这句首先用 echo 命令输出 char 变量的值 (依次取值 a 到 z ),然后输出一个空格,输出短横杠,再输出一个空格,然后输出 grep -io "$char" $1 | wc -l 这句命令的运行结果,也就是 char 变量对应的字母的出现次数 。

我们运行这个脚本(别忘了用 chmod +x statistics.sh 为脚本加上可执行权限):

./statistics.sh words.txt复制代码

可以看到我们的脚本文件如我们所愿从 a 到 z 输出了这 26 个字母,格式也是我们需要的:

字母 - 出现次数复制代码

但是,目前我们的字母没有大写,而且还不是按出现次数最多到最小排序的,因此我们还要继续探索。

为了使 echo 命令的输出中的小写字母被转成大写,我们可以用 tr 命令。tr 是 translate 的缩写,表示「翻译,转化」。

我们的函数改为如下:

# Definition of function
# 函数定义statistics () {for char in {a..z}doecho "$char - `grep -io "$char" $1 | wc -l`" | tr /a-z/ /A-Z/done
}# Use of function
# 函数使用statistics $1复制代码

tr /a-z/ /A-Z/ 表示把所有 a 到 z 的小写字母转为对应的大写字母 A-Z。

这下我们的字母已经都变成大写了,我们还剩最后一点没做:对这 26 行输出根据字母出现次数排序。

为了实现这个,我们需要用到 sort 命令,sort命令用于对文件的行进行排序。

我们还需要一个中转的文件,用于暂时储存我们的 echo 命令循环输出的这 26 行数据。

因此我们可以用输出重定向来把 echo "$char - `grep -io "$char" $1 | wc -l`" | tr /a-z/ /A-Z/ 的结果依次写入一个文件,比如取名为 tmp.txt。

然后再用 sort 命令对这个文件的行进行排序,把排序结果显示到终端。

我们的函数改为如下:

# Definition of function
# 函数定义statistics () {for char in {a..z}doecho "$char - `grep -io "$char" $1 | wc -l`" | tr /a-z/ /A-Z/ >> tmp.txtdonesort -rn -k 2 -t - tmp.txtrm tmp.txt
}# Use of function
# 函数使用statistics $1复制代码

我们在 echo "$char - `grep -io "$char" $1 | wc -l`" | tr /a-z/ /A-Z/ 之后加了 >> tmp.txt,以把输出重定向到文件 tmp.txt 末尾。

然后用 sort 命令对 tmp.txt 文件中的行进行排序。

我们用了 sort 命令的 -r,-n,-k 和 -t 四个参数。

其中 -r 和 -n 参数我们比较熟悉,-n 参数用于对数字排序,-r 参数用于倒序排列。

-k 参数用于指定根据哪几列进行排序,这里用 -k 2 表示根据第 2 列来排序。

-t 参数用于指定列和列之间用什么作为分隔符,这里用 -t - 表示分隔符是 - 。

然后每次我们都要把 tmp.txt 这个临时文件删除,用 rm tmp.txt

我的最终代码:

#!/bin/bash# Verification of parameter
# 确认参数if [ -z $1 ]
thenecho "Please enter the file of dictionary !"exit
fi# Verification of file existence
# 确认文件存在if [ ! -e $1 ]
thenecho "Please make sure that the file of dictionary exists !"exit
fi# Definition of function
# 函数定义statistics () {for char in {a..z}doecho "$char - `grep -io "$char" $1 | wc -l`" | tr /a-z/ /A-Z/ >> tmp.txtdonesort -rn -k 2 -t - tmp.txtrm tmp.txt
}# Use of function
# 函数使用statistics $1复制代码

上面只是我的解法,你的解题思路和代码当然不必和我一样。而且我也非常肯定我的代码不够优。

我相信各位能想出更好的解法,欢迎留言补充(如果留言支持代码,可以把你的代码贴出来)。

这个程序虽然短小,但是我们用到了 Linux 中的 grep 命令,sort 命令,wc 命令,rm 命令,echo 命令,exit 命令,管道 ( | ),重定向( >> )。Shell 中的条件语句 ( if ),循环语句 ( for ),函数,等知识点。

4. 可能的优化


我给出的解方是基础的,你可以自由发挥。

下面提出几点优化的设想:

  1. 除了第一个参数,也就是要统计的字典文件的名字,我们还可以添加其他参数,来完成更多任务。

  2. 改变输出的形式,使之更美观。

  3. 每一行可以输出更多信息。

  4. 尝试不借助中间文件 tmp.txt。

其他优化,就有待大家去发挥自己的想象力咯!

5. 第五部分第九课预告


今天的课就到这里,一起加油吧!

下一课我们来做一些测试吧 :Linux探索之旅 | 第五部分第九课:第五部分测试题

然后就进入第六部分了。


微信公众号「程序员联盟」ProgrammerLeague
我是谢恩铭,在巴黎奋斗的软件工程师。
我的简介
我的经历
热爱生活,喜欢游泳,略懂烹饪。
人生格言:「向着标杆直跑」

Linux 探索之旅 | 第五部分第八课:用 Shell 做统计练习相关推荐

  1. Linux 探索之旅 | 第五部分第六课:一朝 Shell 函数倾,斗转星移任我行

    -- 作者 谢恩铭 转载请注明出处 内容简介 前言 函数的作用 函数的定义 传递参数 返回值 变量作用范围 重载命令 函数的设计 总结 第五部分第七课预告 1. 前言 上一课 Linux探索之旅 | ...

  2. Linux 探索之旅 | 第五部分第五课:循环往复,Shell 开路

    -- 作者 谢恩铭 转载请注明出处 内容简介 前言 while 循环 until 循环 for 循环 总结 第五部分第六课预告 1. 前言 上一课 Linux探索之旅 | 第五部分第四课:条件一出,S ...

  3. Linux 探索之旅 | 第三部分第四课:后台运行及合并多个终端

    -- 作者 谢恩铭 转载请注明出处 内容简介 第三部分第四课:后台运行及合并多个终端 第三部分第五课预告:延时执行,唯慢不破 后台运行及合并多个终端 上一课 Linux探索之旅 | 第三部分第三课:监 ...

  4. 【C++探索之旅】第一部分第八课:传值引用,文件源头

    内容简介 1.第一部分第八课:传值引用,文件源头 2.第一部分第九课预告:数组威武,动静合一 传值引用,文件源头 这一课的标题有点怪.其实是由这一课的几个重点内容结合起来取的名,慢慢学习就知道啦. 上 ...

  5. 【Linux探索之旅】第二部分第三课:文件和文件夹,组织不会亏待你

    内容简单介绍 1.第二部分第三课:文件和文件夹,组织不会亏待你 2.第二部分第四课预告:文件操纵.鼓掌之中 文件和文件夹,组织不会亏待你 上一次课我们讲了命令行,这将成为伴随我们接下来整个Linux课 ...

  6. 【Linux探索之旅】第一部分第四课:磁盘分区,并完毕Ubuntu安装

    内容简单介绍 1.第一部分第四课:磁盘分区,并完毕Ubuntu安装 2.第一部分第五课预告:Unity桌面,人生若仅仅如初见 磁盘分区 上一课我们正式開始安装Ubuntu了.可是到了分区的那一步.小编 ...

  7. 【Linux探索之旅】第二部分第五课:用户和权限,有权就任性

    内容简单介绍 1.第二部分第五课:用户和权限,有权就任性 2.第二部分第六课预告:Nano,刚開始学习的人的文本编辑器 用户和权限.有权就任性 今天的标题也挺任性的啊,虽说小编是一个非常本分的人(真的 ...

  8. Linux 探索之旅 | 第三部分第五课:延时执行,唯慢不破

    -- 作者 谢恩铭 转载请注明出处 内容简介 第三部分第五课:延时执行,唯慢不破 第三部分第六课预告:第三部分测验题 延时执行,唯慢不破 上一课 Linux探索之旅 | 第三部分第四课:后台运行及合并 ...

  9. Linux 探索之旅 | 第四部分第二课:SSH 连接,安全快捷

    -- 作者 谢恩铭 转载请注明出处 内容简介 第四部分第二课:SSH连接,安全快捷 第四部分第三课预告:文件传输,潇洒同步 SSH连接,安全快捷 上一课是 Linux探索之旅 | 第四部分第一课:压缩 ...

最新文章

  1. java字符串转换成日期型对象
  2. synchronized关键字实现原理
  3. mysql将字符串字段转为数字排序或比大小
  4. 惠普服务器bios查看硬件属性,查看硬件信息
  5. 熔断器 Hystrix 的原理与使用
  6. 启动“powershell.exe”时出现错误 0x8007000
  7. Java消息服务~开发者分配的消息头
  8. 程序员面试金典——18.13 最大字母矩阵
  9. Python中父类和子类间类属性(非实例属性)的设置获取的传递
  10. C# dataGridView中的数据导出到excel
  11. 外星人笔记本 键盘灯不亮解决 Alienware 13
  12. 腾讯云开发低代码平台初探
  13. Elasticsearch-analysis-pinyin7.6.0--可选参数详情
  14. 数字钱包(IOST)使用指南
  15. 2017“中国好SaaS”上海站Top3出炉,企业级SaaS创业正在向产业化迈进
  16. 阿里云ACP认证(SLB专项)
  17. mysql数据库多实例启动_Mysql多实例运行
  18. 2020国赛C思路分析:中小微企业的信贷决策
  19. 叮咚上市,我的前同事挣了1个亿!
  20. LeakCanary使用,案例静态Toast引起的内存泄漏

热门文章

  1. 使用nvm下载安装Node
  2. 解决www.coursera.org可以登录但无法播放视频
  3. 【虚幻引擎UE】打包异常问题合集
  4. 自顾不暇的大搜车能为吉利汽车做些什么?
  5. openssl 自制国密证书
  6. vue下拉框使用法则
  7. 转载给浮躁的软件业同仁 (作者不是我,但是确实是好文)
  8. wn万能命令,wn.run怎么用?
  9. 大疆Tello UDP控制协议接口
  10. decode()的用法