為什么要學習 Shell 腳本語言?
現代的互聯網架構底層系統幾乎都是基於 Linux 操作系統構建的,Linux 的核心價值在於提供了強大的系統內核功能進行文件管理和信息交互管理。
而 Shell 則是軟件研發人員高效控制和使用 Linux 的工具和橋梁。Shell 本身是 C 語言編寫的系統軟件,通常也叫命令行工具。它具有一個基礎的界面,用戶在這個界面通過 Shell 腳本語言(Shell Script)來訪問 Linux 操作系統內核服務。
在科幻電影里,我們經常看到在暗色調的屏幕上 Shell 腳本代碼在快速滾動,這簡直成為了 Geek/Hacker 的一個形象標簽。實際工作中,Shell 也備受開發、運維、測試人員甚至運營人員的青睞,幾乎是 IT 技術人員的必備技能。
而在軟件測試領域,Shell 腳本編程作為自動化測試技術的基石,是測試開發工程師必須熟練掌握的技能。
Tips:盡管通常我們把「Shell」和「Shell 腳本語言」都叫做 “Shell”,但其實這兩者是有本質區別的。
Shell 腳本語言編程有哪些優勢?
Shell 腳本語言的優勢在於能夠以輕量級、最快捷的速度處理 Linux 操作系統偏底層的業務。比如軟件的自動化安裝、更新版本、監控報警、日志分析等。雖然其他高級編程語言如 PHP、Python、Ruby 等語言也能做到,但是效率和開發成本上會大打折扣,所謂“殺雞用牛刀”,有點得不償失。
成熟的技術人會摒棄華而不實的方法,根據不同的場景選擇最合適的工具去解決問題,朴實但高效。比如本文着重介紹的 Linux 三劍客:grep、awk 和 sed 就是 Linux 文本處理問題的最高效工具。
下面,我們將依次介紹 Linux 文本處理三劍客的基礎語法,使用場景和特性,以及給出對應的實戰演練題目。
Shell 編程環境
1. Windows 用戶,建議安裝 Git Bash 軟件。
2. Mac 用戶,建議安裝 iterm2 軟件。
3. ssh 工具
- 霍格沃茲測試學院學員用自己的帳號登錄
- `ssh 手機號后8位@http://shell.testing-studio.com`
- 沒有 ssh 賬號的可以臨時用
- `ssh hogwarts2019@shell.testing-studio.com`
4. 演練文檔:
http://testerhome.com/tmp/nginx.log 保存了一份一天的 Nginx 訪問 log。
Linux 三劍客介紹
grep
grep 示例 Shell 腳本代碼
ps -ef | grep bash echo "ABC" | grep -i abc ps -ef | grep bash | grep -v grep echo "1234 7654" | grep -o "[0-9]4" echo "1234 7654" | grep -oE "[0-9]4|76"
grep 實戰演練題目
- 找出 nginx.log 中所有 404 和 503 報錯的 log 數據,取出前 3 條數據,把命令貼到回復里。
- 找出 testerhome 首頁的所有 http 和 https 的鏈接。
awk
awk 示例 Shell 腳本代碼
ps | awk 'BEGIN{print "start"}{print $0}END{print "end"}' awk '/ 404 | 500 /' /tmp/nginx.log echo '1 2 3 4 5' | awk '/2/,/4/' echo '1 2 3 4 5' | awk '$0>3' ps | awk 'NR>1' ps | awk '{print $NF}' echo $PATH | awk 'BEGIN{RS=":"}{print $0}' | grep -v "^$" | awk 'BEGIN{FS="\n";ORS=":"}{print $0}END{printf "\n" }' echo '1,10 2,20 3,30' | awk 'BEGIN{a=0;FS=","}{a+=$2}END{print a,a/NR}' awk 'BEGIN{print 33*20*76/200/3}' echo "123|456_789" | awk 'BEGIN{FS="\\||_"}{print $2}' echo "123|456_789" | awk "BEGIN{FS=\"\\\\||_\"}{print \$2}" #盡量使用單引號
awk 實戰演練題目
- 找出 404 和 500 的數據,只打印狀態碼這一列,然后排序去重。把命令貼到回復里
- 去 testerhome 首頁找到所有的 http 的連接,然后打印不帶 http 的純域名部分
sed
pattern表達式
- 20 30,35 行數與行數范圍
- /pattern/ 正則匹配
- //,// 正則匹配的區間
action
- d 刪除
- p 打印,通暢結合-n參數
- s/REGEXP/REPLACEMENT/[FLAGS]
- 替換時引用 \1 \2 匹配的字段
sed 示例 Shell 腳本代碼
ps | sed -n 1,3p ps | sed 's/CMD/command/' ps | sed -n '/ps/p' echo '1 2 3 4 5' | sed -n '/3/,/4/p' echo '1 2 3 4 5' | sed '/3/,/4/d' ps | sed -e 's/CMD/command/' -e 's#00#20#g'
grep
作為linux中最為常用的三大文本(awk,sed,grep)處理工具之一,掌握好其用法是很有必要的。
grep命令的常用格式為:grep [選項] ”模式“ [文件]
grep家族總共有三個:grep,egrep,fgrep
常用選項:
-E :開啟擴展(Extend)的正則表達式。
-i :忽略大小寫(ignore case)。
-v :反過來(invert),只打印沒有匹配的,而匹配的反而不打印。
-n :顯示行號
-w :被匹配的文本只能是單詞,而不能是單詞中的某一部分,如文本中有liker,而我搜尋的只是like,就可以使用-w選項來避免匹配liker
-c :顯示總共有多少行被匹配到了,而不是顯示被匹配到的內容,注意如果同時使用-cv選項是顯示有多少行沒有被匹配到。
-o :只顯示被模式匹配到的字符串。
--color :將匹配到的內容以顏色高亮顯示。
-A n:顯示匹配到的字符串所在的行及其后n行,after
-B n:顯示匹配到的字符串所在的行及其前n行,before
-C n:顯示匹配到的字符串所在的行及其前后各n行,context
案例:
[root@zabbix ~]#
[root@zabbix ~]# grep "root" /etc/passwd
root:x:0:0:root:/root:/bin/bash
operator:x:11:0:operator:/root:/sbin/nologin
[root@zabbix ~]#
[root@zabbix ~]# grep -i "Root" /etc/passwd
root:x:0:0:root:/root:/bin/bash
operator:x:11:0:operator:/root:/sbin/nologin
[root@zabbix ~]#
[root@zabbix ~]# grep -n "root" /etc/passwd
1:root:x:0:0:root:/root:/bin/bash
10:operator:x:11:0:operator:/root:/sbin/nologin
[root@zabbix ~]#
[root@zabbix ~]# grep -vc "root" /etc/passwd
21
[root@zabbix ~]#
[root@zabbix ~]# grep -o "root" /etc/passwd
root
root
root
root
[root@zabbix ~]#
[root@zabbix ~]#
[root@zabbix ~]# grep -A 2 "core id" /proc/cpuinfo
core id : 0
cpu cores : 1
apicid : 0
[root@zabbix ~]#
[root@zabbix ~]#
[root@zabbix ~]# grep -B 2 "core id" /proc/cpuinfo
physical id : 0
siblings : 1
core id : 0
[root@zabbix ~]#
[root@zabbix ~]# grep -C 2 "core id" /proc/cpuinfo
physical id : 0
siblings : 1
core id : 0
cpu cores : 1
apicid : 0
[root@zabbix ~]#
模式部分:
直接輸入要匹配的字符串,這個可以用fgrep(fast grep)代替來提高查找速度,比如我要匹配一下hello.c文件中printf的個數:fgrep -c "printf" hello.c
使用基本正則表達式,下面談關於基本正則表達式的使用:
匹配字符:
. :任意一個字符。
[abc] :表示匹配一個字符,這個字符必須是abc中的一個。
[a-zA-Z] :表示匹配一個字符,這個字符必須是a-z或A-Z這52個字母中的一個。
[^123] :匹配一個字符,這個字符是除了1、2、3以外的所有字符。
對於一些常用的字符集,系統做了定義:
[A-Za-z] 等價於 [[:alpha:]]
[0-9] 等價於 [[:digit:]]
[A-Za-z0-9] 等價於 [[:alnum:]]
tab,space 等空白字符 [[:space:]]
[A-Z] 等價於 [[:upper:]]
[a-z] 等價於 [[:lower:]]
標點符號 [[:punct:]]

匹配次數:
\{m,n\} :匹配其前面出現的字符至少m次,至多n次。
\? :匹配其前面出現的內容0次或1次,等價於\{0,1\}。
* :匹配其前面出現的內容任意次,等價於\{0,\},所以 ".*" 表述任意字符任意次,即無論什么內容全部匹配。
案例:
[root@zabbix ~]#
[root@zabbix ~]# grep "/.*sh" /etc/passwd
root:x:0:0:root:/root:/bin/bash
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin
ctsi:x:1000:1000:ctsi:/home/ctsi:/bin/bash
apache:x:48:48:Apache:/usr/share/httpd:/sbin/nologin
[root@zabbix ~]#
[root@zabbix ~]#
[root@zabbix ~]# grep "/.\{0,2\}sh" /etc/passwd
root:x:0:0:root:/root:/bin/bash
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin
ctsi:x:1000:1000:ctsi:/home/ctsi:/bin/bash
apache:x:48:48:Apache:/usr/share/httpd:/sbin/nologin
[root@zabbix ~]#
[root@zabbix ~]#
[root@zabbix ~]# grep -w ".\{0,2\}sh" /etc/passwd
root:x:0:0:root:/root:/bin/bash
ctsi:x:1000:1000:ctsi:/home/ctsi:/bin/bash
[root@zabbix ~]#
位置錨定:
^ :錨定行首
$ :錨定行尾。技巧:"^$"用於匹配空白行。
\b或\<:錨定單詞的詞首。如"\blike"不會匹配alike,但是會匹配liker
\b或\>:錨定單詞的詞尾。如"\blike\b"不會匹配alike和liker,只會匹配like
\B :與\b作用相反。
案例:
[root@zabbix ~]#
[root@zabbix ~]# grep "h" /etc/passwd
root:x:0:0:root:/root:/bin/bash
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin
chrony:x:998:996::/var/lib/chrony:/sbin/nologin
ctsi:x:1000:1000:ctsi:/home/ctsi:/bin/bash
apache:x:48:48:Apache:/usr/share/httpd:/sbin/nologin
[root@zabbix ~]#
[root@zabbix ~]# grep "^h" /etc/passwd #匹配以h開頭的
halt:x:7:0:halt:/sbin:/sbin/halt
[root@zabbix ~]#
[root@zabbix ~]# grep "h$" /etc/passwd #匹配以h結尾的內容
root:x:0:0:root:/root:/bin/bash
ctsi:x:1000:1000:ctsi:/home/ctsi:/bin/bash
[root@zabbix ~]#
[root@zabbix ~]#
[root@zabbix ~]# grep "sh" /etc/passwd
root:x:0:0:root:/root:/bin/bash
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin
ctsi:x:1000:1000:ctsi:/home/ctsi:/bin/bash
apache:x:48:48:Apache:/usr/share/httpd:/sbin/nologin
[root@zabbix ~]#
[root@zabbix ~]# grep "\<sh" /etc/passwd
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
apache:x:48:48:Apache:/usr/share/httpd:/sbin/nologin
[root@zabbix ~]#
[root@zabbix ~]# grep "\Bsh\b" /etc/passwd
root:x:0:0:root:/root:/bin/bash
ctsi:x:1000:1000:ctsi:/home/ctsi:/bin/bash
[root@zabbix ~]#
分組及引用:
\(string\) :將string作為一個整體方便后面引用
\1 :引用第1個左括號及其對應的右括號所匹配的內容。
\2 :引用第2個左括號及其對應的右括號所匹配的內容。
\n :引用第n個左括號及其對應的右括號所匹配的內容。
案例:
[root@zabbix ~]#
[root@zabbix ~]# grep "^\([A-Za-z]\).*\1$" /etc/passwd
nobody:x:99:99:Nobody:/:/sbin/nologin
[root@zabbix ~]#
擴展的(Extend)正則表達式(注意要使用擴展的正則表達式要加-E選項,或者直接使用egrep):
匹配字符:這部分和基本正則表達式一樣
匹配次數:
* :和基本正則表達式一樣
? :基本正則表達式是\?,而這里沒有\。
{m,n} :相比基本正則表達式也是沒有了\。
+ :匹配其前面的字符至少一次,相當於{1,}。
位置錨定:和基本正則表達式一樣。
分組及引用:
(string) :相比基本正則表達式也是沒有了\。
\1 :引用部分和基本正則表達式一樣。
\n :引用部分和基本正則表達式一樣。
或者:
a|b :匹配a或b,注意a是指 | 的左邊的整體,b也同理。比如 C|cat 表示的是 C或cat,而不是Cat或cat,如果要表示Cat或cat,則應該寫為 (C|c)at 。記住(string)除了用於引用還用於分組。
注1:默認情況下,正則表達式的匹配工作在貪婪模式下,也就是說它會盡可能長地去匹配,比如某一行有字符串 abacb,如果搜索內容為 "a.*b" 那么會直接匹配 abacb這個串,而不會只匹配ab或acb。
注2:所有的正則字符,如 [ 、* 、( 等,若要搜索 * ,而不是想把 * 解釋為重復先前字符任意次,可以使用 \* 來轉義。
下面用一個練習來結束本次grep的學習:
在網絡配置文件 /etc/sysconfig/network-scripts/ifcfg-ens32 中檢索出所有的 IP
檢索出 0-255的范圍
[root@zabbix ~]#
[root@zabbix ~]# egrep ."[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5]" /etc/sysconfig/network-scripts/ifcfg-ens32
NAME=ens32
UUID=a42c58f3-b9ef-42fb-a6dd-827892c88da4
DEVICE=ens32
[root@zabbix ~]#