Linux探索之旅|第五部分第八課:Shell做統計練習

Linux探索之旅|第五部分第八課:Shell做統計練習


《Linux探索之旅》全系列

內容簡介


  1. 前言

  2. 成果展示

  3. 解題步驟和答案

  4. 可能的優化

  5. 第五部分第九課預告

1. 前言


上一課 Linux探索之旅 | 第五部分第七課:Shell實現圖片展示網頁 中,我們做了一個有趣的練習。

這個練習用一個 Shell 腳本來生成一個 HTML 文件,這個 HTML 文件是一個展示圖片縮略圖的網頁,點擊每個縮略圖還會鏈接到原始圖片。

這一課我們繼續做一個進階的 Shell 腳本練習。這個練習要實現的是對一個英語字典做統計。

通過這個練習,你將鞏固 Shell 和 Linux 的知識點。

https://github.com/frogoscar/english-dictionary

你可以選擇 git clone 到你本地目錄,或者下載 zip 壓縮包。然後提取裡面的 words.txt 文件即可。

Linux探索之旅|第五部分第八課:Shell做統計練習

對於 Git 和 Github 的使用有任何疑問,歡迎閱讀我的文章 :Git,Github和Gitlab簡介和基本使用

當然了,如果你自己能在網上找到其他完整的英文字典的文本文檔也可以,不一定要用我這個。

2. 成果展示


我們要用到的字典文本文檔裡的內容類似如下:

Linux探索之旅|第五部分第八課:Shell做統計練習

字典開頭

Linux探索之旅|第五部分第八課:Shell做統計練習

字典結尾

我們要寫一個 Shell 腳本,來顯示這個龐大的字典中 26 個英文字母(從 a 到 z )出現的次數,而且以次數最多到最少的順序排列。

成果是像下面這樣的:

Linux探索之旅|第五部分第八課:Shell做統計練習

可以看到,字母 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

Linux探索之旅|第五部分第八課:Shell做統計練習

可以看到 -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

那麼輸出是這樣的:

Linux探索之旅|第五部分第八課:Shell做統計練習

理解了嗎?不加 -o 參數,那麼 grep 就會輸出每一個包含 a 的行。而每一行 (字典文件中一行有一個單詞)也許包含不止一個 a。因此為了統計所有的 a,我們須要加上 -o 參數。

既然我們已經用 grep -io a words.txt 命令來輸出了所有字母 a 的 出現(逐行顯示),那麼我們可以用

wc -l 命令來統計行數,即可知道 a 的出現次數了。

接下來我們就用管道來把 grep 命令的結果賦給 wc 命令:

grep -io a words.txt | wc -l

Linux探索之旅|第五部分第八課:Shell做統計練習

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

我們也可以不加 -o 參數來測試一下:

grep -i a words.txt | wc -l

Linux探索之旅|第五部分第八課:Shell做統計練習

可以看到輸出是 206518,比 273400 少了很多,因為不加 -o 參數只統計了 a 出現的那些行(相當於統計了包含 a 的單詞數目),而不是統計 a 的真正出現次數。

我們現在已經知道如何統計字母 a 的次數了,那麼舉一反三,統計其他 25 個字母也不在話下。我們可以用一個循環語句來實現:

for char in {a..z}; do

Linux探索之旅|第五部分第八課:Shell做統計練習

可以看到我們在終端輸入 for 循環語句後,依次打印出了 a, b, c, 一直到 z 這 26 個字母在 words.txt 文件中出現的次數。

雖然現在我們只是開了個頭,但是已經可以來寫我們的 Shell 腳本了。

我們首先寫一些基礎的部分:

#!/bin/bash# Verification of parameter# 確認參數if [ -z $1 ]

上面兩段代碼分別用於確認參數和確認文件存在,如果不滿足 if 條件,那麼用 echo 顯示提示信息,然後用 exit 命令退出 Shell。

然後,我們來定義一個函數,就叫 statistics 好了,我們繼續在 statistics.sh 這個文件中加入以下代碼:

# Definition of function# 函數定義statistics () { for char in {a..z}

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

Linux探索之旅|第五部分第八課:Shell做統計練習

可以看到我們的腳本文件如我們所願從 a 到 z 輸出了這 26 個字母,格式也是我們需要的:

字母 - 出現次數

但是,目前我們的字母沒有大寫,而且還不是按出現次數最多到最小排序的,因此我們還要繼續探索。

為了使 echo 命令的輸出中的小寫字母被轉成大寫,我們可以用 tr 命令。tr 是 translate 的縮寫,表示「翻譯,轉化」。

我們的函數改為如下:

# Definition of function# 函數定義statistics () { for char in {a..z}

tr /a-z/ /A-Z/ 表示把所有 a 到 z 的小寫字母轉為對應的大寫字母 A-Z。

Linux探索之旅|第五部分第八課:Shell做統計練習

這下我們的字母已經都變成大寫了,我們還剩最後一點沒做:對這 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}

我們在 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 ]

上面只是我的解法,你的解題思路和代碼當然不必和我一樣。而且我也非常肯定我的代碼不夠優。

我相信各位能想出更好的解法,歡迎留言補充(如果留言支持代碼,可以把你的代碼貼出來)。

這個程序雖然短小,但是我們用到了 Linux 中的 grep 命令,sort 命令,wc 命令,rm 命令,echo 命令,exit 命令,管道 ( | ),重定向( >> )。Shell 中的條件語句 ( if ),循環語句 ( for ),函數,等知識點。

4. 可能的優化


我給出的解方是基礎的,你可以自由發揮。

下面提出幾點優化的設想:

  1. 除了第一個參數,也就是要統計的字典文件的名字,我們還可以添加其他參數,來完成更多任務。

  2. 改變輸出的形式,使之更美觀。

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

  4. 嘗試不借助中間文件 tmp.txt。

其他優化,就有待大家去發揮自己的想象力咯!

5. 第五部分第九課預告


今天的課就到這裡,一起加油吧!

下一課我們來做一些測試吧 :第五部分測試題

然後就進入第六部分了。

我是[謝恩銘](http://www.jianshu.com/u/44339a8a9afa),在巴黎奮鬥的軟件工程師。

[我的簡介](http://www.jianshu.com/p/e1c5835fee7d)

[我的經歷](http://www.jianshu.com/p/86c2cfe3b390)

熱愛生活,喜歡游泳,略懂烹飪。

人生格言:“向著標杆直跑”


分享到:


相關文章: