編程小技巧之 Linux 文本處理命令

合格的程序員都善於使用工具,正所謂君子性非異也,善假於物也。合理的利用 Linux 的命令行工具,可以提高我們的工作效率。

本文簡單的介紹三個能使用 Linux 文本處理命令的場景,給大家開闊一下思路。希望大家閱讀完這篇文章之後,要多加實踐,將這些技巧內化到自己的日常工作習慣中,真正的提高效率。內化很重要,就像開玩笑所說的一樣,即使我知道高內聚,低耦合的要求,瞭解 23 種設計模式和 6 大原則,熟讀代碼整潔之道,卻仍然寫不出優秀的代碼。知道和內化到行爲中區別還是很大的。

能不能讓正確的原則指導正確的行動本身,其實就是區分是否是高手的一個顯著標志。

程序員日常工作中往往要處理一些數據和文本,比如說統計一些服務日誌文件信息,根據數據庫數據生成一些處理數據的SQL和搜索文件內容等。可以直接通過編寫代碼處理,但不夠便捷,因爲有時候線上相關的代碼環境依賴不一定具備。而直接使用 Linux 的文本處理命令可以很方便地處理這些問題。

日誌文件撈數據

在工作中,我們往往需要對一些具有固定格式的文件進行信息統計,比如說根據 nginx 的 access.log 文件數據,計算出每個後端 API 接口的調用次數,並且排序。

nginx 的 access.log 文件文件格式配置如下所示,每個字段之間通過空格分隔開來。

log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                  '$status $body_bytes_sent "$http_referer" '
                  '"$http_user_agent" "$http_x_forwarded_for"';

上述配置中字段含義如下:

  • $remote_addr : 發送請求的源地址
  • $remote_user : 發送請求的用戶信息
  • $time_local : 接收請求的本地時間
  • $request : 請求信息,比如說 http 的 method 和 路徑。
  • $status : 請求狀態,比如說 200、401或者 500。
  • $body_bytes_sent : 請求 body 字節數。
  • $http_referer : 域名。
  • $http_user_agent : 用戶端 agent 信息,一般就是瀏覽器信息
  • $http_x_forwarded_for : 其他信息。

具體的一段 access.log 內容如下所示。

58.213.85.34 - - [11/Sep/2019:03:36:11 +0800] "POST /publish/pending/list HTTP/2.0" 200 1328 "https://remcarpediem.com/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36"
58.213.85.34 - - [11/Sep/2019:03:36:30 +0800] "GET /publish/search_inner?key=test HTTP/2.0" 200 34466 "https://remcarpediem.com/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36"

那麼,我們可以通過下面命令來統計所有接口調用的次數,並且從大到小排序顯示。

cat /var/log/nginx/access.log | awk '{print $7}' | awk -F'?' '{print $1}' | sort | uniq -c | sort -nr

這條命令涉及了 cat、awk、sort , uniq 四個命令行工具和 | 連接符的含義,我們依次簡單講解一下它們的使用,感興趣的同學可以自行去全面瞭解學習。

cat 命令是將文件內容打印到標準輸出設備上,可以是終端,也可以是其他文件。比如說:

cat /var/log/nginx/access.log # 打印到終端
cat /var/log/nginx/access.log > copy.log # 打印到其他文件中

| 符號是管道操作符,它將的一個命令的 stdout 指向第二個命令的 stdin。在這條命令中 | 符號將 cat 命令的輸出指向到 awk 命令的輸入中。

awk 是貝爾實驗室 1977 年搞出來的文本流處理工具,用於對具有固定格式的文件進行流處理。比如說 nginx 的 access.log 文件,它各個字段之間通過空格分隔開來,awk 就很適合處理此類文件。

'{print $7}' 就是 awk 的指令聲明,表示打印出變量$7$7則是 awk 內置的變量,代表按照分隔符分隔開來的第七個文本內容。對於 access.log 文件來說就是 $request 代表的路徑相關的內容。 $request 的全部內容是POST /publish/pending/list HTTP/2.0$6 對應 POST,而 $7 對應的就是 /publish/pending/list

awk '{print $7}' Access.log # ''中是命令聲明,後邊跟着要操作的文件,也就是awk的輸入流。

但是有些時候我們發現文本內容並不是按照空格進行分隔的,比如說 $request 內容可能爲 /publish/search_inner?key=test,雖然是相同的 path,但是 query 不同,我們統計接口調用量時需要將 query 部分過濾掉。我們可以使用 awk 的 -F 指令指定分隔符。

awk -F'?' '{print $1}' 
# 可以將 /publish/search_inner?key=test 處理爲 /publish/search_inner

sort 是專門用於排序的命令,它有多個參數:

  • -n 按數值進行排序,默認是按照字符值排序,按照數值比較 10 > 2 但是按照字符值排序,2 >10 ,因爲字符值會先比較首位,也就是 2 > 1。
  • -r 默認是升序排列,這個參數指定按照逆序排列。
  • -k N 指定按第N列排序,默認是第一個值
sort -nr Access.log # 按照數值逆序排序

最後一個命令是 uniq,它用於消除重複行,或者統計。

sort unsort.txt | uniq #  消除重複行
sort unsort.txt | uniq -c # 統計各行在文件中出現的次數,輸入格式是[字數] [內容]
sort unsort.txt | uniq -d # 找出重複行

比如說cat /var/log/nginx/access.log | awk '{print $7}' | awk -F'?' '{print $1}' | sort | uniq -c 命令的輸出如下所示,正好作爲 sort -nr 的輸入。

5 /announcement/pending/list
5 /announcement/search_inner

利用這些指令,我們可以通過 access.log 統計很多信息,比如下列這些信息( access.log 的信息配置不同,不可以直接照搬 )。

cat access.log | awk -F ‘^A’ ‘{if($5 == 500) print $0}’ 
#查找當前日誌文件 500 錯誤的訪問:
tail -f access.log | awk -F ‘^A’ ‘{if($6>1) print $0}’ 
#查找耗時超過 1s 的慢請求

數據庫SQL

在業務迭代過程中,有些數據庫數據可能需要使用腳本去修改,這是我們可以要根據一些數據生成對應的 SQL 命令,這裏我們可以使用命令行工具快速生成。
比如說我們要將一系列訂單狀態有問題,需要將其恢復成正常的狀態。你現在已經收集到了這批訂單的信息。

oder_id name info good_id
100000  '褲子' '山東' 1000
100001  '上衣' '江蘇' 1000
100002  '內衣' '內蒙古' 1000
........
100003  '襪子' '江西' 1000

那麼你可以使用如下命令直接生成對應的 SQL 語句。

cat ErrorOrderIdFile | awk '{print"UPDATE ORDER SET state = 0 WHERE resource_id = "$1}'

這裏 ''中都是 awk 的命令內容,而""中是打印的純文本,所以我們可以將需要補充的 SQL 命令打印出來。

代碼信息統計

在大公司中,各個團隊往往會公開出自己的接口給兄弟團隊調用,但是隨着版本地快速迭代,公開的接口越來越多,想要關閉掉又往往不清楚上游調用方是哪個部門的,輕易不敢關閉或者修改。這時,如果你能訪問整個公司的代碼庫,就可以通過下面的腳本搜索一下項目中是否出現該接口相關的關鍵詞。

筆者公司團隊中微服務間通過 FeignClient 相互調用,所以對於這種情況,可以直接將搜索出對應 FeignClient 的函數名出現的文件名稱。

下面是一段在多個項目中統計某些關鍵詞出現次數,並打印出文件名的 bash 腳本。

#!/bin/bash
keyword=$1 # 將bash命令的第一個參數賦值給 keyword
prefix=`echo $keyword | tr -s '.' '|' | sed 's/$/|/'` # 處理前綴
files=`find services -name "*.java" -or -name "*.js" | xargs grep -il $keyword` 
# 最關鍵的一條,搜索services文件夾下文件名後綴爲.java或者.js並且內容中有關鍵詞的文件名稱。
if [ -z "$files" ];then
    echo ${prefix}0
fi
# 打印
for f in $files;do
echo "$prefix$f"
done

我們只看一下最關鍵的 find 命令,其他的命令比如 tr 或者 sed,大家可以自行了解學習。

find 用於查找文件,可以按照文件名稱、文件操作權限、文件屬主、文件訪問時間等條件來查找。

find services -name "*.java" -or -name "*.js" # 搜索 services 文件夾下
find . -atime 7 -type f -print 
# -atime是訪問時間,-type 是文件類型,區分文件和目錄,查找最近7天訪問過的文件。
find . -type f -user remcarpediem -print// 找用戶 remcarpediem 所擁有的文件
find . ! -name "*.java" -print # !是否定參數,查找所有不是以 .java 結尾的文件。
find . -type f -name "*.java" -delete # find 之後的操作,可以刪除當前目錄下所有的 java 文件
find . type f -name "*.java" | xargs rm # 上邊語句的另外一種寫法

xargs 命令能夠將輸入數據轉化爲特定命令的命令行參數,比如說多行變一行等,串聯多個命令行,比如說上邊 find 和 rm。

> ls 
Sentinel                    groovy-engine                    spring-cloud-bus-stream-binder-rocketmq
agent-demo                    hash                        spring-cloud-stream-binder-rabbit
> ls | xargs # 將 ls 的輸出內容變成一行。
Sentinel agent-demo groovy-engine  hash spring-cloud-bus-stream-binder-rocketmq spring-cloud-stream-binder-rabbit
> echo "nameXnameXnameXname" | xargs -dX 
name name name name
# -d 選項可以自定義一個定界符,相信你已經瞭解 xargs 的大致作用了吧,按照分隔符拆分文本到一行,默認分隔符當時是回車了。

最後一個命令時 grep,它是文本搜索命令,它可以搜索文本內容的關鍵詞。

grep remcarpediem file # 將 file 文件中的帶有 remcarpediem 關鍵詞的行。
grep -C10 remcarpediem file # 將 file 文件中的帶有 remcarpediem 關鍵詞前後10行的內容。
cat LOG.* | grep "FROM " | grep "WHERE" > b # 將日誌中的所有帶where條件的sql查找查找出來
grep -li remcarpediem file # 忽略大小寫,並且打印出文件名稱

現在大家在回頭看一下這段 bash 腳本,是不是大致瞭解它執行的過程和原理啦。

files=`find services -name "*.java" -or -name "*.js" | xargs grep -il $keyword` 

後記

本文簡單介紹了程序員日常工作中可能用到 Linux 命令的三個場景。大家可以根據自己的實際情況,來判斷是否需要繼續全面詳細地學習相關的知識。畢竟只有能運用於實踐,給自己工作產生價值的技術纔是真技術。學習一項技術,就要堅持學以致用的目的。

個人博客,歡迎來玩
clipboard.png

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章