本章主要介紹如何使用bash
- 了解通配符
- 了解變量
- 了解返回值和數值運算
- 判斷語句
grep的用法是“grep 關鍵字 file”,意思是從file中過濾出含有關鍵字的行
例如,grep root /var/log/messages,意思是從/var/log/messages 中過濾出含有root 的行。這里很明確的是過濾含有“root”的行
不管是通配符還是正則表達式,都是為了模糊匹配,為了匹配某一類內容,而不是具體的某個關鍵字。通配符一般用在shell語言中,正則表達式一般用在其他語言中
不管是通配符還是正則表達式,主要是理解它們的元字符,然后用元字符來組合成我們想要的那一類字符,本章主要講解通配符的使用
1.1 通配符
通配符一般用在shell語言中,通配符中常見的元字符如下:
(1)[]:匹配一個字符,匹配的是出現在中括號中的字符
(2)[abc]:匹配一個字符,且只能是a或b或c
(3)[a-z]:“-”有特殊意義,表示“到”的意思,這里表示a~z,即匹配任一字母
(4)[0-9]:表示匹配任一數字
如果想去除含有特殊意義的字符,前面加“\”表示轉義,即去除此字符的特殊意義
(5)[a\-z]:這里的“-”就沒有“到”的意思了,匹配的是“a”或“-”或“z”這三個中的一個
如果想表示“除了”的意思,則在第一個中括號后面加“!”或“^”
(6)[!a-z]、[^a-z]:表示除字母外的其他字符
(7)?:表示一個任意字符,這里強調是一個,不是0個也不是多個,但不能匹配表示隱藏文件的點
(8)*:表示任意多個任意字符,可以是0個,也可以是1個或多個,但不能匹配表示隱藏文件的點
練習:先創建目錄vv并在目錄中創建如下幾個測試文件
[root@redhat8 ~]# mkdir vv
[root@redhat8 ~]# cd vv
[root@redhat8 vv]# touch 1_aa aa11 Aa11 _aaa aa.txt f1aa u_12
[root@redhat8 vv]#
找出首字符是字母、第二個字符是數字的文件
[root@redhat8 vv]# ls [a-z][0-9]*
f1aa
[root@redhat8 vv]#
找出首字符是字母、第二個字符不是數字的文件
[root@redhat8 vv]# ls [a-z][^0-9]*
aa11 Aa11 aa.txt u_12
[root@redhat8 vv]#
找出首字符不是字母、第二個字符不是數字的文件
[root@redhat8 vv]# ls [^a-z][^0-9]*
1_aa _aaa
[root@redhat8 vv]#
可以看到,找出來的文件完全符合我們的需求。下面找出首字符是大寫字母、第二個字符是非數字的文件
[root@redhat8 vv]# ls [A-Z][!0-9]*
Aa11 u_12
[root@redhat8 vv]#
可以看到,首字符是大寫字母的文件列出來了,首字符是小寫字母的文件有的列出來了, 有的沒有列出來,所以[a-z]或[A-Z]有時并不精確。如果要更精確,可以用如下元字符
(1)[[:upper:]]:純大寫
(2)[[:lower:]]:小寫
(3)[[:alpha:]]:字母
(4)[[:alnum:]:字母和數字
(5)[[:digit:]]:數字
列出首字符是小寫字母、第二個字符是數字的文件
[root@redhat8 vv]# ls [[:lower:]][0-9]*
f1aa
[root@redhat8 vv]#
列出首字符是大寫字母、第二個字符是數字或字母的文件
[root@redhat8 vv]# ls [[:upper:]][[:alnum:]]*
Aa11
[root@redhat8 vv]#
如果想在yum 源中列出所有以 vsftpd開頭的包
[root@redhat8 vv]# yum list vsftpd*
正在更新 Subscription Management 軟件倉庫。
無法讀取客戶身份本系統尚未在權利服務器中注冊。可使用 subscription-manager 進行注冊。上次元數據過期檢查:1 day, 19:22:27 前,執行于 2023年12月06日 星期三 15時34分15秒。
可安裝的軟件包
vsftpd.x86_64 3.0.3-34.el8 aa
[root@redhat8 vv]#
在當前目錄中創建一個文件vsftpdxxx
[root@redhat8 vv]# touch vsftpdxxx
[root@redhat8 vv]#
然后再執行yum list vsftpd*命令
[root@redhat8 vv]# yum list vsftpd*
正在更新 Subscription Management 軟件倉庫。
無法讀取客戶身份本系統尚未在權利服務器中注冊。可使用 subscription-manager 進行注冊。上次元數據過期檢查:1 day, 19:23:15 前,執行于 2023年12月06日 星期三 15時34分15秒。
錯誤:沒有匹配的軟件包可以列出
[root@redhat8 vv]#
此處顯示沒有匹配的包,為什么呢?因為yum是 bash的一個子進程,vsftpd在 bash中首先被解析成了vsftpdxxx,然后再經過yum。所以,本質上執行的是yum list vsftpdxx命令,而yum源中是沒有vsftpdxxx 這個包的,所以報錯
為了防止 bash 對這里的*進行解析,可以加上轉義符“\”,所以下面的命令是正確的
[root@redhat8 vv]# yum list vsftpd\*
正在更新 Subscription Management 軟件倉庫。
無法讀取客戶身份本系統尚未在權利服務器中注冊。可使用 subscription-manager 進行注冊。上次元數據過期檢查:1 day, 19:24:25 前,執行于 2023年12月06日 星期三 15時34分15秒。
可安裝的軟件包
vsftpd.x86_64 3.0.3-34.el8 aa
[root@redhat8 vv]#
1.2 變量
所謂變量,指的是可變的值,并非具體的值。例如,我自己嘴中發出的“我”,指的是我自己,張三嘴中發出的“我”,指的是張三,那么這個“我”就是一個變量
變量可以分為本地變量、環境變量、位置變量和預定義變量
1.2.1 本地變量
定義本地變量的格式如下
變量名=值
定義變量有以下幾點需要注意
(1)變量名可以包含_、數字、大小寫字母,但不能以數字開頭
(2)“=”兩邊不要有空格
(3)“值”如果含有空格,要使用單引號''或雙引號""引起來
(4)定義變量時,變量名前是不需要加$的,引用變量時需要在變量名前加$
本章實驗都放在~/yy中練習
[root@redhat8 ~]# mkdir yy
[root@redhat8 ~]# cd yy
[root@redhat8 yy]#
下面開始練習定義變量
[root@redhat8 yy]# 1aa=123
bash: 1aa=123: 未找到命令...
文件搜索失敗: /mnt/AppStream was not found
[root@redhat8 yy]#
這里定義變量不正確,因為變量名不能以數字開頭
[root@redhat8 yy]# aa-1=123
bash: aa-1=123: 未找到命令...
文件搜索失敗: /mnt/AppStream was not found
[root@redhat8 yy]#
這里定義變量不正確,因為變量名只能是字母、數字、下劃線的組合
[root@redhat8 yy]# aa =123
bash: aa: 未找到命令...
文件搜索失敗: /mnt/AppStream was not found
[root@redhat8 yy]#
這里的錯誤是因為等號左邊有空格
[root@redhat8 yy]# aa=1 2
bash: 2: 未找到命令...
[root@redhat8 yy]#
這里的錯誤是因為“值”部分有空格沒有用引號引起來
[root@redhat8 yy]# aa=123
[root@redhat8 yy]#
這里正確地定義了一個變量
在使用本地變量時,變量名前需要加$
[root@redhat8 yy]# echo $aa
123
[root@redhat8 yy]#
本地變量的特點是只能影響當前shell,不能影響子shell
[root@redhat8 yy]# echo $aa
123
[root@redhat8 yy]# echo $$
1841
[root@redhat8 yy]#
當前shell的PID是1841。下面打開一個子shell
[root@redhat8 yy]# bash
[root@redhat8 yy]# echo $$
2183
[root@redhat8 yy]#
這個子shell的PID是2183
[root@redhat8 yy]# echo $aa[root@redhat8 yy]#
可以看到,沒有aa變量
[root@redhat8 yy]# exit
exit
[root@redhat8 yy]# echo $$
1841
[root@redhat8 yy]# echo $aa
123
[root@redhat8 yy]#
定義變量除剛才顯式的定義外,還可以使用如下兩種方法
(1)把一個命令的結果賦值給一個變量,這個變量要使用$()括起來,或者用反引號“引起來。這里是反引號,與波浪號~是同--個鍵,不是單引號
例如,定義一個名稱是ip的變量,對應的值是ens160的IP
[root@redhat8 yy]# ip=$(ifconfig ens160 | awk '/inet /{print $2}')
[root@redhat8 yy]# echo $ip
192.168.161.16
[root@redhat8 yy]#
(2)通過read命令來獲取變量
read的用法如下
read ‐p "提示信息" 變量
當遇到read命令時,系統會等待用戶輸入,用戶所輸入的值會賦值給read后面的變量
[root@redhat8 yy]# read -p "請輸入你的名字:" aa
請輸入你的名字:jin
[root@redhat8 yy]# echo $aa
jin
[root@redhat8 yy]#
當執行read這條命令時,系統會提示用戶輸人一些內容,所輸入的內容會賦值給aa變 量。這里我們輸入的是 jin,所以打印aa變量時,看到的值是jin
這樣的用法比較適合寫需要和用戶交互的腳本
1.2.2 環境變量
定義環境變量的注意點和本地變量是一樣的。在定義環境變量時,前面加上export 即可
[root@redhat8 yy]# export bb=123
[root@redhat8 yy]#
或者先定義為本地變量,然后再通過export轉變為環境變量
[root@redhat8 yy]# bb=123
[root@redhat8 yy]# export bb
[root@redhat8 yy]#
要想查看所有的環境變量,可以執行env命令
環境變量的特點是可以影響shell,這里強調的是子shell,不能影響父shell
[root@redhat8 yy]# echo $$
1841
[root@redhat8 yy]# echo $bb
123
[root@redhat8 yy]
當前shell的PID是1841,里面有一個環境變量bb
[root@redhat8 yy]# echo $$
2330
[root@redhat8 yy]# echo $bb
123
[root@redhat8 yy]#
打開一個子shell,PID為2330,里面可以看到bb變量的值,說明環境變量已經影響到 子shell 了
[root@redhat8 yy]# export bb=456
[root@redhat8 yy]# exit
exit
[root@redhat8 yy]#
在子 shell中重新給bb賦值為456,然后退回到父shell
[root@redhat8 yy]# echo $$
1841
[root@redhat8 yy]# echo $bb
123
[root@redhat8 yy]#
可以看到,在父shell 中,bb的值仍然是123,說明在子shell 中定義的變量不會影響到父shell
系統中默認已經存在很多個變量
(1)UID:表示當前用戶的uid
(2)USER:表示當前用戶名
(3)HOME:表示當前用戶的家目錄
分別顯示這些變量的值
[root@redhat8 yy]# echo $UID
0
[root@redhat8 yy]# echo $USER
root
[root@redhat8 yy]# echo $HOME
/root
[root@redhat8 yy]#
有一個很重要的環境變量PATH,當我們執行命令時,一定要指定這個命令的路徑,如果沒有寫路徑,則會到PATH變量所指定的路徑中進行查詢。先查看當前用戶的PATH變量
[root@redhat8 yy]# echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin
[root@redhat8 yy]#
PATH變量由多個目錄組成,每個目錄之間用冒號“:”分隔,我們把寫好的腳本放在PATH變量指定的目錄中之后,運行此腳本時就不需要指定路徑了
以上定義的環境變量也只是在當前終端中生效,關閉終端之后這個變量也就消失了。如果想讓定義的變量永久生效,可以寫人家目錄的.bash_profile中。因為打開終端時,首先會運行家目錄下的一個隱藏文件.bash_profile
1.3 返回值
執行某命令之后,結果不是正確的就是錯誤的。命令正確執行了,返回值為0,如果沒有正確執行則返回值為非零。返回值為非零,不一定是語法錯誤,執行結果如果有“否定”的意思,返回值也為非零。例如, ping 192.168.161.12,語法沒有錯誤,但是沒有ping通,返回值 也為非零
返回值記錄在$?中,且$?只記錄剛剛執行過命令的返回值。因為$?的值會被新執行命令的返回值覆蓋
練習:先執行一個xxx命令
[root@redhat8 yy]# xxx
bash: xxx: 未找到命令...
文件搜索失敗: /mnt/AppStream was not found
[root@redhat8 yy]# echo $?
127
[root@redhat8 yy]# echo $?
0
[root@redhat8 yy]#
先執行一個xxx命令,這個命令是錯誤的命令,$?記錄的是剛剛執行過xxx命令的返回值。所以,查看$?的值是127,是一個非零的值。再次查看$?的值時,卻變成了0,因為這個$?記錄的不再是xxx命令的返回值,而是它前面執行過的echo $?命令的返回值
邏輯上“否定”的意思也是可以體現出來的
[root@redhat8 yy]# grep ^root /etc/passwd
root:x:0:0:root:/root:/bin/bash
[root@redhat8 yy]# echo $?
0
[root@redhat8 yy]#
這里在/etc/passwd過濾行開頭為root的行,結果找到了,所以返回值為0
[root@redhat8 yy]# grep ^rootxxx /etc/passwd
[root@redhat8 yy]# echo $?
1
[root@redhat8 yy]#
這里在/etc/passwd過濾行開頭為rootxxx的行,結果沒有找到,即使語法沒有錯誤,但是邏輯上有“否定”的意思,所以返回值為非零
1.4 數值運算
在寫腳本時,有時我們經常要做一些數學運算。數學運算的符號如下
(1)+:表示加
(2)-:表示減
(3)*:表示乘
(4)/:表示除
(5)**:表示次方
進行數學運算的表達式有$(())、$[]、let等
[root@redhat8 yy]# echo $((4+1))
5
[root@redhat8 yy]# echo $((4*1))
4
[root@redhat8 yy]# echo $((4**1))
4
[root@redhat8 yy]# echo $[4**1]
4
[root@redhat8 yy]#
其中$(O)和$[]的用法是一樣的,如果不用這樣的表達式
[root@redhat8 yy]# echo 2**3
2**3
[root@redhat8 yy]#
這里并不是計算的2的3次方,而是直接把這4個字符打印出來了
let也可以用于數學運算
[root@redhat8 yy]# let aa=1+2
[root@redhat8 yy]# echo $aa
3
[root@redhat8 yy]#
這里aa的值就是為3
下面來看不使用let的情況
[root@redhat8 yy]# aa=1+2
[root@redhat8 yy]# echo $aa
1+2
[root@redhat8 yy]#
這里并沒有把aa的值1+2當成數字,而是當成了3個字符:“1”“+”“2”,所以結果顯示的也是1+2
可以實現定義aa為整數類型,然后再做數學運算
[root@redhat8 yy]# declare -i aa
[root@redhat8 yy]# aa=1+2
[root@redhat8 yy]# echo $aa
3
[root@redhat8 yy]#
首先declare -i aa把aa定義為一個整數,所以1+2等于3,然后賦值給aa.所以aa的值為3
以上表達式不能求得小數,如果要得到小數需要使用 bc 命令
echo "scale=N ; 算法 | bc"
這里N是一個數字,表示小數點后面保留幾位
計算2/3,小數點后面保留3位,命令如下
[root@redhat8 yy]# echo "scale=3 ; 2/3" | bc
.666
[root@redhat8 yy]#
這里得到的結果是0.666,整數部分的0沒有顯示
1.5 if判斷語句
在腳本中執行某條命令需要滿足一定的條件,如果不滿足就不能執行。此時我們就要用到 判斷語句了
先看if判斷,if判斷的語法如下
1. if 條件1 ; then
2. 命令1
3. elif 條件2 ; then
4. 命令2
5. else 命令3
6. fi
先判斷if后面的判斷是不是成立
如果成立,則執行命令1,然后跳到f后面,執行6后面的命令
如果不成立,則不執行命令1,然后判斷elif后面的條件2是不是成立
如果成立,則執行命令2,然后跳到f后面,執行f后面的命令
如果不成立,則不執行命令2,進行下一輪的elif 判斷,以此類推
如果所有if和elif都不成立,則執行clse中的命令3
練習:寫一個腳本/opt/sc1.sh,要求只有root用戶才能執行此腳本,其他用戶不能執行
[root@redhat8 ~]# cat /opt/sc1.sh
#/bin/bash
if [ $UID -ne 0 ]; thenecho "只有root才能執行此腳本"exit 1
fi
echo "hello root"[root@redhat8 ~]# chmod +x /opt/sc1.sh
[root@redhat8 ~]#
?腳本分析如下
root的uid是0,其他用戶的uid不為0。第一個判斷,如果uid不等于0,則打印警告信 息“只有root才能執行此腳本”,然后exit退出腳本
如果這里不加 exit,判斷之后仍然會繼續執行echo "hello root"命令,這樣判斷就失去 了意義。只有加了exit之后,如果不是root,則到此結束,不要繼續往下執行了
如果是jin?執行此腳本,則判斷成立,打印完警告信息之后,通過exit退出腳本
如果是 root執行此腳本,則判斷不成立,直接執行f后面的命令
使用root用戶執行此腳本的結果如下
[root@redhat8 ~]# /opt/sc1.sh
hello root
[root@redhat8 ~]#
使用jin用戶執行此腳本的結果如下
[jin@redhat8 ~]$ /opt/sc1.sh
只有root才能執行此腳本
[jin@redhat8 ~]$