本章主要介紹如何使用bash寫腳本。
- 了解通配符
- 了解變量
- 了解返回值和數值運算
- 數值的對比
- 判斷語句
- 循環語句
grep的用法是“grep 關鍵字 file”,意思是從file中過濾出含有關鍵字的行。?
? 例如,grep root /var/log/messages,意思是從/var/log/messages 中過濾出含有root 的行。這里很明確的是過濾含有“root”的行。???
? 如果想在/var/log/messages 中過濾出含有IP地址的行呢?IP地址就是一類字符,例如, 1.1.1.1是一個IP,192.168.26.100也是一個IP,那么用什么能表示出來這一類字符呢??
? 不管是通配符還是正則表達式,都是為了模糊匹配,為了匹配某一類內容,而不是具體的 某個關鍵字。通配符一般用在shell語言中,正則表達式一般用在其他語言中。?
? 不管是通配符還是正則表達式,主要是理解它們的元字符,然后用元字符來組合成我們想 要的那一類字符,本章主要講解通配符的使用。?
? 像我們平時說的張某某,這個某就是一個元字符,不是一個定值。指的是姓張,名字含有 2個字。張某某可能匹配到張二狗,也可能匹配到張阿貓,但是無法匹配到李阿三,也匹配 不了張三,因為張某某匹配的是姓名為3個字的,但是張三這個姓名只有2個字。?
? 如果說有一個人姓“張”名“某”,那么需要匹配“張某”這個人,而不是要匹配張三、 張四,可以用張\某,某前加個“\”表示轉義的意思。
通配符?
通配符一般用在shell語言中,通配符中常見的元字符如下。
- [ ]:匹配一個字符,匹配的是出現在中括號中的字符。
- ?[abc]:匹配一個字符,且只能是a或b或c。
- [a-z]:“-”有特殊意義,表示“到”的意思,這里表示a~z,即匹配任一字母。
- [0-9]:表示匹配任一數字。
如果想去除含有特殊意義的字符,前面加“\”表示轉義,即去除此字符的特殊意義。
- [a\-z]:這里的“-”就沒有“到”的意思了,匹配的是“a”或“-”或“z”這三個 中的一個。
如果想表示“除了”的意思,則在第一個中括號后面加“!”或“^”。
- [!a-z]、[^a-z]:表示除字母外的其他字符。
- ?:表示一個任意字符,這里強調是一個,不是0個也不是多個,但不能匹配表示隱藏 文件的點。
- *:表示任意多個任意字符,可以是0個,也可以是1個或多個,但不能匹配表示隱藏 文件的點。
練習:先創建目錄xx并在目錄中創建如下幾個測試文件,命令如下。
[root@rhel03 ~]# mkdir xx
[root@rhel03 ~]# cd xx
[root@rhel03 xx]# touch 1_aa aa11 Aa11 _aaa aa.txt f1aa u_12
[root@rhel03 xx]#
找出首字符是字母、第二個字符是數字的文件,命令如下。?
[root@rhel03 xx]# ls [a-z][0-9]*
f1aa
[root@rhel03 xx]#
找出首字符是字母、第二個字符不是數字的文件,命令如下。?
[root@rhel03 xx]# ls [a-z][^0-9]*
aa11 Aa11 aa.txt u_12
[root@rhel03 xx]#
找出首字符不是字母、第二個字符不是數字的文件,命令如下。
[root@rhel03 xx]# ls [^a-z][^0-9]*
1_aa _aaa
[root@rhel03 xx]#
? 可以看到,找出來的文件完全符合我們的需求。下面找出首字符是大寫字母、第二個字符是非數字的文件,命令如下。?
[root@rhel03 xx]# ls [A-Z][!0-9]*
Aa11 u_12
[root@rhel03 xx]#
? 可以看到,首字符是大寫字母的文件列出來了,首字符是小寫字母的文件有的列出來了, 有的沒有列出來,所以[a-z]或[A-Z]有時并不精確。如果要更精確,可以用如下元字符。?
- [[:upper:]]:純大寫。
- [[:lower:]]:小寫。
- [[:alpha:]]:字母。
- [[:alnum:]:字母和數字。
- [[:digit:]]:數字。?
列出首字符是小寫字母、第二個字符是數字的文件,命令如下。?
[root@rhel03 xx]# ls [[:lower:]][0-9]*
f1aa
[root@rhel03 xx]#
列出首字符是大寫字母、第二個字符是數字或字母的文件,命令如下。?
[root@rhel03 xx]# ls [[:upper:]][[:alnum:]]*
Aa11
[root@rhel03 xx]#
如果想在yum 源中列出所有以 vsftpd開頭的包,可以用如下命令。?
[root@rhel03 ~]# yum list vsftpd*
正在更新 Subscription Management 軟件倉庫。
無法讀取客戶身份本系統尚未在權利服務器中注冊。可使用 subscription-manager 進行注冊。aa 1.2 MB/s | 3.2 kB 00:00
bb 2.3 MB/s | 2.8 kB 00:00
可安裝的軟件包
vsftpd.x86_64 3.0.3-34.el8 aa
[root@rhel03 ~]#
在當前目錄中創建一個文件vsftpdxxx,命令如下。?
[root@rhel03 ~]# touch vsftpdxxx
[root@rhel03 ~]#
?然后再執行yum list vsftpd*命令,命令如下。
[root@rhel03 ~]# yum list vsftpd*
正在更新 Subscription Management 軟件倉庫。
無法讀取客戶身份本系統尚未在權利服務器中注冊。可使用 subscription-manager 進行注冊。上次元數據過期檢查:0:02:14 前,執行于 2023年12月10日 星期日 14時55分36秒。
錯誤:沒有匹配的軟件包可以列出
[root@rhel03 ~]#
? 此處顯示沒有匹配的包,為什么呢?因為yum是 bash的一個子進程,vsftpd在 bash中首先被解析成了vsftpdxxx,然后再經過yum。所以,本質上執行的是yum list vsftpdxx命 令,而yum源中是沒有vsftpdxxx 這個包的,所以報錯。?
為了防止 bash 對這里的*進行解析,可以加上轉義符“\”,所以下面的命令是正確的。?
[root@rhel03 ~]# yum list vsftpd\*
正在更新 Subscription Management 軟件倉庫。
無法讀取客戶身份本系統尚未在權利服務器中注冊。可使用 subscription-manager 進行注冊。上次元數據過期檢查:0:03:11 前,執行于 2023年12月10日 星期日 14時55分36秒。
可安裝的軟件包
vsftpd.x86_64 3.0.3-34.el8 aa
[root@rhel03 ~]#
變量?
? 所謂變量,指的是可變的值,并非具體的值。例如,我自己嘴中發出的“我”,指的是我自己,張三嘴中發出的“我”,指的是張三,那么這個“我”就是一個變量。?
? 變量可以分為本地變量、環境變量、位置變量和預定義變量。?
(1)本地變量?
定義本地變量的格式如下。
- 變量名=值?
定義變量有以下幾點需要注意。?
- 變量名可以包含_、數字、大小寫字母,但不能以數字開頭。
- “=”兩邊不要有空格。
- “值”如果含有空格,要使用單引號''或雙引號""引起來。
- “值”如果含有空格,要使用單引號''或雙引號""引起來。?
本章實驗都放在~/yy中練習,命令如下。?
[root@rhel03 ~]# mkdir yy ; cd yy
[root@rhel03 yy]#
下面開始練習定義變量,命令如下。?
[root@rhel03 yy]# 1aa=123
bash: 1aa=123: 未找到命令...
[root@rhel03 yy]#
這里定義變量不正確,因為變量名不能以數字開頭,命令如下。?
[root@rhel03 yy]# aa-1=123
bash: aa-1=123: 未找到命令...
[root@rhel03 yy]#
這里定義變量不正確,因為變量名只能是字母、數字、下劃線的組合,命令如下。?
[root@rhel03 yy]# aa =123
bash: aa: 未找到命令...
[root@rhel03 yy]#
這里的錯誤是因為等號左邊有空格。?
[root@rhel03 yy]# aa=1 2
bash: 2: 未找到命令...
[root@rhel03 yy]#
這里的錯誤是因為“值”部分有空格沒有用引號引起來。?
[root@rhel03 yy]# aa=123
[root@rhel03 yy]#
這里正確地定義了一個變量。
在使用本地變量時,變量名前需要加$,命令如下。?
[root@rhel03 yy]# echo $aa
123
[root@rhel03 yy]#
本地變量的特點是只能影響當前shell,不能影響子shell。?
[root@rhel03 yy]# echo $aa
123
[root@rhel03 yy]# echo $$
2092
[root@rhel03 yy]#
當前shell的PID是2092。下面打開一個子shell。?
[root@rhel03 yy]# bash
[root@rhel03 yy]# echo $$
2452
[root@rhel03 yy]#
這個子shell 的PID是2452。
[root@rhel03 yy]# echo $aa[root@rhel03 yy]#
可以看到,沒有aa變量。?
[root@rhel03 yy]# exit
exit
[root@rhel03 yy]# echo $$
2092
[root@rhel03 yy]# echo $aa
123
[root@rhel03 yy]#
再次退回到原來的bash,又有了aa變量,情形如下圖所示。
?
定義變量除剛才顯式的定義外,還可以使用如下兩種方法。?
方法1:把一個命令的結果賦值給一個變量,這個變量要使用$()括起來,或者用反引號“引起來。這里是反引號,與波浪號~是同--個鍵,不是 單引號。?
例如,定義一個名稱是ip的變量,對應的值是ens160的IP,命令如下。?
[root@rhel03 yy]# ip=$(ifconfig ens160 | awk '/inet /{print $2}')
[root@rhel03 yy]# echo $ip
192.168.23.33
[root@rhel03 yy]#
方法2:通過read命令來獲取變量。
read的用法如下。
- read ‐p "提示信息" 變量?
? 當遇到read命令時,系統會等待用戶輸入,用戶所輸入的值會賦值給read后面的變量, 命令如下。?
[root@rhel03 yy]# read -p "請輸入您的名字:" aa
請輸入您的名字:tom
[root@rhel03 yy]# echo $aa
tom
[root@rhel03 yy]#
? 當執行read這條命令時,系統會提示用戶輸人一些內容,所輸入的內容會賦值給aa變量。這里我們輸入的是 tom,所以打印aa變量時,看到的值是tom。?
這樣的用法比較適合寫需要和用戶交互的腳本。
(2)環境變量?
? 定義環境變量的注意點和本地變量是一樣的。在定義環境變量時,前面加上export 即可, 命令如下。?
[root@rhel03 yy]# export bb=123
[root@rhel03 yy]#
或者先定義為本地變量,然后再通過export轉變為環境變量,命令如下。?
[root@rhel03 yy]# bb=123
[root@rhel03 yy]# export bb
[root@rhel03 yy]#
要想查看所有的環境變量,可以執行env命令。
環境變量的特點是可以影響子shell,這里強調的是子shell,不能影響父shell。?
[root@rhel03 yy]# echo $$
2092
[root@rhel03 yy]# echo $bb
123
[root@rhel03 yy]#
當前shell的PID是2092,里面有一個環境變量 bb。?
[root@rhel03 yy]# bash
[root@rhel03 yy]# echo $$
2555
[root@rhel03 yy]# echo $bb
123
[root@rhel03 yy]#
打開一個子shell,PID為2555,里面可以看到bb變量的值,說明環境變量已經影響到子shell 了?
[root@rhel03 yy]# export bb=456
[root@rhel03 yy]# exit
exit
[root@rhel03 yy]#
在子 shell中重新給bb賦值為456,然后退回到父shell。?
[root@rhel03 yy]# echo $$
2092
[root@rhel03 yy]# echo $bb
123
[root@rhel03 yy]#
可以看到,在父shell 中,bb的值仍然是123,說明在子shell 中定義的變量不會影響到父 shell,如下圖所示。?
?
系統中默認已經存在很多個變量,如下所示。
- UID:表示當前用戶的uid。
- USER:表示當前用戶名。
- HOME:表示當前用戶的家目錄。
分別顯示這些變量的值,命令如下。?
[root@rhel03 yy]# echo $UID
0
[root@rhel03 yy]# echo $USER
root
[root@rhel03 yy]# echo $HOME
/root
[root@rhel03 yy]#
? 有一個很重要的環境變量PATH,當我們執行命令時,一定要指定這個命令的路徑,如果沒有寫路徑,則會到PATH變量所指定的路徑中進行查詢。先查看當前用戶的PATH變量,命令如下。?
[root@rhel03 yy]# echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin
[root@rhel03 yy]#
? PATH變量由多個目錄組成,每個目錄之間用冒號“:”分隔,我們把寫好的腳本放在 PATH變量指定的目錄中之后,運行此腳本時就不需要指定路徑了。?
查看tom用戶的PATH變量,命令如下。?
[tom@rhel03 ~]$ echo $PATH
/home/tom/.local/bin:/home/tom/bin:/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin
[tom@rhel03 ~]$
? 這里展示了tom用戶的PATH 變量,如果tom寫了一個腳本之后,就可以把這個腳本放在 自己家目錄下的.local/bin或bin目錄下(~/.local/bin或~/bin)。如果這兩個目錄不存在, 創建出來即可。?
? 以上定義的環境變量也只是在當前終端中生效,關閉終端之后這個變量也就消失了。如果 想讓定義的變量永久生效,可以寫人家目錄的.bash_profile中。因為打開終端時,首先會運行家目錄下的一個隱藏文件.bash_profile。?
(3)位置變量和預定義變量?
? 運行腳本時,有時后面是需要加上參數的。但是我們在寫腳本時并不能預知后期在腳本后面跟上什么參數,這時就能用到位置變量了,位置變量如下。?
- $0:表示腳本的名稱。
- $1:表示第1個參數。
- $2:表示第2個參數。
- .........?
- ${10}:表示第10個參數。?
這里$后面的數字如果不是個位數,則要用{}括起來。?
系統中還內置了一些預定義變量。?
- $#:表示參數的個數。
- $*:表示所有的參數。?
例1:寫一個帶參數的腳本,內容如下。?
[root@rhel03 yy]# cat scl.yaml
#/bin/bash
echo "這是我的第一個腳本,腳本名稱是$0"
echo "第1個參數是:$1"
echo "第2個參數是:$2"
echo "第3個參數是:$3"
echo "此腳本一共有$#個參數,它們分別是:$*"[root@rhel03 yy]#
給這個腳本加上可執行權限,并加參數運行,命令如下。?
[root@rhel03 yy]# chmod +x scl.yaml
[root@rhel03 yy]# ./scl.yaml tom bob mary
這是我的第一個腳本,腳本名稱是./scl.yaml
第1個參數是:tom
第2個參數是:bob
第3個參數是:mary
此腳本一共有3個參數,它們分別是:tom bob mary
[root@rhel03 yy]#
? 運行這個腳本時,共指定了3個參數:tom、bob、mary,它們分別賦值給了 $1、$2、$3。這里S#被自動賦值為3,因為總共有3個參數,所有的參數被賦值給$*。?
例2:運行如下命令。
[root@rhel03 yy]# set a b c d e f g h i j k
[root@rhel03 yy]#
查看此命令的第1個和第9個參數,命令如下。?
[root@rhel03 yy]# echo $1
a
[root@rhel03 yy]# echo $9
i
[root@rhel03 yy]#
第1個參數是a,第9個參數是i。下面查看第10個參數,命令如下。?
[root@rhel03 yy]# echo $10
a0
[root@rhel03 yy]#
? 第9個參數是i,那么第10個參數應該是j才對,這里顯示為a0,為什么呢?因為這里先把 $10當成了$1+0,$1的值是a,所以$10的值為a0。?
所以,在位置變量中數字超過10時,要用{}括起來,下面的命令才是正確的。?
[root@rhel03 yy]# echo ${10}
j
[root@rhel03 yy]#
另外,在引用變量時,雙引號和單引號是有區別的,直接看一個例子。?
[root@rhel03 yy]# xx=tom
[root@rhel03 yy]# echo "my name is $xx"
my name is tom
[root@rhel03 yy]# echo 'my name is $xx'
my name is $xx
[root@rhel03 yy]#
? 這里先定義一個變量xx=tom,如果變量在雙引號中引用,則會被解析成具體的值;如果變量出現在單引號中,則不被解析。?
返回值?
? 執行某命令之后,結果不是正確的就是錯誤的。命令正確執行了,返回值為0,如果沒有正確執行則返回值為非零。返回值為非零,不一定是語法錯誤,執行結果如果有“否定”的 意思,返回值也為非零。例如, ping 192.168.26.3,語法沒有錯誤,但是沒有ping通,返回值 也為非零。?
? 返回值記錄在$?中,且$?只記錄剛剛執行過命令的返回值。因為$?的值會被新執行命令的返回值覆蓋。?
練習:先執行一個 xxx命令,命令如下。?
[root@rhel03 yy]# xxxx
bash: xxxx: 未找到命令...
[root@rhel03 yy]# echo $?
127
[root@rhel03 yy]# echo $?
0
[root@rhel03 yy]#
? 先執行一個xxx命令,這個命令是錯誤的命令,$?記錄的是剛剛執行過xxx命令的返回值。 所以,查看$?的值是127,是一個非零的值。再次查看$?的值時,卻變成了0,因為這個$? 記錄的不再是xxx命令的返回值,而是它前面執行過的echo $?命令的返回值。?
邏輯上“否定”的意思也是可以體現出來的。例如,下面的例子。?
[root@rhel03 yy]# grep ^root /etc/passwd
root:x:0:0:root:/root:/bin/bash
[root@rhel03 yy]# echo $?
0
[root@rhel03 yy]#
這里在/etc/passwd過濾行開頭為root的行,結果找到了,所以返回值為0。?
[root@rhel03 yy]# grep ^rootxxx /etc/passwd
[root@rhel03 yy]# echo $?
1
[root@rhel03 yy]#
這里在/etc/passwd過濾行開頭為rootxxx的行,結果沒有找到,即使語法沒有錯誤,但是邏輯上有“否定”的意思,所以返回值為非零。
數值運算?
在寫腳本時,有時我們經常要做一些數學運算。數學運算的符號如下。?
- +:表示加。
- -:表示減。
- *:表示乘。
- /:表示除。
- **:表示次方。?
進行數學運算的表達式有$(())、$[]、let等,命令如下。?
[root@rhel03 yy]# echo $((2+3))
5
[root@rhel03 yy]# echo $((2*3))
6
[root@rhel03 yy]# echo $((2**3))
8
[root@rhel03 yy]# echo $[2**3]
8
[root@rhel03 yy]#
其中$(O)和$[]的用法是一樣的,如果不用這樣的表達式,看如下代碼。?
[root@rhel03 yy]# echo 2**3
2**3
[root@rhel03 yy]#
這里并不是計算的2的3次方,而是直接把這4個字符打印出來了。?
let也可以用于數學運算,命令如下。
[root@rhel03 yy]# let aa=1+2
[root@rhel03 yy]# echo $aa
3
[root@rhel03 yy]#
這里aa的值就是為3。
下面來看不使用let的情況,命令如下。?
[root@rhel03 yy]# aa=1+2
[root@rhel03 yy]# echo $aa
1+2
[root@rhel03 yy]#
這里并沒有把aa的值1+2當成數字,而是當成了3個字符:“1”“+”“2”,所以結果顯示的也是1+2。?
可以實現定義aa為整數類型,然后再做數學運算,命令如下。
[root@rhel03 yy]# declare -i aa
[root@rhel03 yy]# aa=1+2
[root@rhel03 yy]# echo $aa
3
[root@rhel03 yy]#
首先declare -i aa把aa定義為一個整數,所以1+2等于3,然后賦值給aa.所以aa的值為3。 以上表達式不能求得小數,如果要得到小數需要使用 bc 命令,用法如下。?
- echo "scale=N ; 算法 | bc"?
這里N是一個數字,表示小數點后面保留幾位。
計算2/3,小數點后面保留3位,命令如下。?
[root@rhel03 yy]# echo "scale=3 ; 2/3" | bc
.666
[root@rhel03 yy]#
這里得到的結果是0.666,整數部分的0沒有顯示。
計算7/6,小數點后面保留3位,命令如下。
[root@rhel03 yy]# echo "scale=3 ; 7/6" | bc
1.166
[root@rhel03 yy]#
比較、對比、判斷?
? 在寫腳本時,有時需要做一些比較,例如,兩個數字誰大誰小,兩個字符串是否相同等。 做對比的表達式有[]、[[]]、test,其中[]和 test這兩種表達式的作用是相同的。[[]]和[]的不同 在于,[[]]能識別通配符和正則表達式中的元字符,[]卻不能。?
需要注意的是,在比較時,中括號和后續提及的比較符兩邊都要留有空格。
(1)數字的比較?
數字的比較,主要是比較兩個數字誰大誰小,或者是否相同。能用到的比較符有以下幾種。
- -eq:相等。
- -ne:不相等。
- -gt:大于。
- -ge:大于等于。
- -lt:小于。
- -le:小于等于。?
做完比較之后,通過返回值來判斷比較是否成立。?
練習1:判斷1等于2,命令如下。
[root@rhel03 yy]# [ 1 -eq 2 ]
[root@rhel03 yy]# echo $?
1
[root@rhel03 yy]#
1是不能等于2的,所以判斷不成立,返回值為非零。注意中括號和比較符兩邊的空格。?
練習2:判斷1不等于2,命令如下。
[root@rhel03 yy]# [ 1 -ne 2 ]
[root@rhel03 yy]# echo $?
0
[root@rhel03 yy]#
1不等于2,所以判斷成立,返回值為0。?
(2)字待串的比較?
字符串的比較,一般是比較兩個字符串是否相同,用得較多的比較符有以下兩種。
- ==:相同。
- !=:不相同。?
做完比較之后,通過返回值來判斷比較是否成立。?
練習1:定義一個變量bb=tom,然后做判斷,命令如下。?
[root@rhel03 yy]# bb=tom
[root@rhel03 yy]# [ $bb == tom ]
[root@rhel03 yy]# echo $?
0
[root@rhel03 yy]#
變量bb的值和 tom完全相同,所以判斷成立,返回值為0。?
練習2:在判斷中匹配通配符,命令如下。
[root@rhel03 yy]# bb=tom
[root@rhel03 yy]# [ $bb == to? ]
[root@rhel03 yy]# echo $?
1
[root@rhel03 yy]#
? 這里定義bb=tom,按照前面講過的通配符,to?匹配的應該是前兩個字符為to,第三個可以是任意字符,所以 tom應該會被to?匹配到,為什么返回值為非零呢??
? 原因在于在這一對中括號[]中是不能識別通配符的,bb的值是t、o、m三個字符,而等號后面是t、o、?這三個字符,并沒有把問號當成通配符,所以判斷不成立。?
如果想識別通配符,那么就要用雙中括號[[]],看下面的判斷。
[root@rhel03 yy]# bb=tom
[root@rhel03 yy]# [[ $bb == to? ]]
[root@rhel03 yy]# echo $?
0
[root@rhel03 yy]#
在[[]]中能識別通配符“?”,所以這里判斷成立,返回值為0。?
注意:
- ==后面跟的是通配符,如果想跟正則表達式,比較符就不能使用==了,要換成=~。
- 一定要注意中括號和比較符兩邊的空格。?
(3)屬性的判斷?
屬性的判斷,用于判斷一個文件是否具備某個屬性,常見的屬性包括以下7種。
- -r:具備讀權限。
- -w:具備寫權限。
- -x:具備可執行權限。
注意:以上三個屬性,不管是出現在u、g還是o上,只要有就算判斷成立。?
- -d:一個目錄。
- -l:一個軟鏈接。
- -f:一個普通文件,且要存在。
- -e:不管什么類型的文件,只要存在就算判斷成立。?
練習1:判斷/etc/hosts具備r權限,命令如下。
[root@rhel03 yy]# ls -l /etc/hosts
-rw-r--r--. 1 root root 185 12月 4 12:17 /etc/hosts
[root@rhel03 yy]# [ -r /etc/hosts ]
[root@rhel03 yy]# echo $?
0
[root@rhel03 yy]#
? 通過第一條命令可以看到/etc/hosts是具備r權限的,判斷/etc/hosts具備r權限,自然成立,所以返回值為0。?
練習2:判斷/etc/hosts具備x權限,命令如下。
[root@rhel03 yy]# [ -x /etc/hosts ]
[root@rhel03 yy]# echo $?
1
[root@rhel03 yy]#
? 這里判斷/etc/hosts具備x權限,但是/etc/hosts不管是u、g還是o都不具備x權限,所以判斷不成立,返回值為非零。?
如果做一個否定判斷,在前面加上嘆號“!”即可。
練習3:判斷/etc/hosts沒有x權限,命令如下。?
[root@rhel03 yy]# [ ! -x /etc/hosts ]
[root@rhel03 yy]# echo $?
0
[root@rhel03 yy]#
這里判斷/etc/hosts沒有x權限,判斷是成立的,所以返回值為0。
練習4:判斷/etc是一個普通文件,命令如下。
[root@rhel03 yy]# [ -f /etc/ ]
[root@rhel03 yy]# echo $?
1
[root@rhel03 yy]#
我們知道/etc是一個目錄而不是一個文件,所以這個判斷是不成立的,返回值為非零。?
練習5:判斷/etc不管是什么類型的,只判斷存在還是不存在,命令如下。
[root@rhel03 yy]# [ -e /etc/ ]
[root@rhel03 yy]# echo $?
0
[root@rhel03 yy]#
這里/etc是存在的目錄,-e用于判斷存在不存在,不判斷文件類型,所以返回值為0。
(4)使用連接符?
前面講的判斷只是單個判斷,如果要同時做多個判斷,那么就需要使用連接符了。能用的連接符包括“&&”和“||”。???
先看一下使用&&作為連接符,用法如下。
- 判斷1 && 判斷2?
? 只有兩個判斷都為真(返回值為0),整體才為真,只要有一個為假,整體就為假。判斷1如果為假,判斷2還有必要執行嗎?沒有,因為整體已經確定為假了。判斷1為真,整體是真是假在于判斷2,所以判斷2肯定是要執行的。?
[root@rhel03 yy]# [ 1 -eq 2 ] && [ 2 -ge 3 ]
[root@rhel03 yy]# echo $?
1
[root@rhel03 yy]#
? 這里有兩個判斷,第一個判斷是1小于等于2,這個判斷成立,第二個判斷是2大于等于3,這個 判斷不成立。使用&&作為連接符,需要兩邊的判斷都成立,整體才成立,所以整個判斷為 假,返回值為非零。?
[root@rhel03 yy]# [ 1 -le 2 ] && [ 2 -le 3 ]
[root@rhel03 yy]# echo $?
0
[root@rhel03 yy]#
? 這里有兩個判斷,第一個判斷是1小于等于2,這個判斷成立,第二個判斷是2小于等于3,這 個判斷也成立。使用&&作為連接符,需要兩邊的判斷都成立,整體才成立,所以整個判斷 為真,返回值為0。?
下面看使用||作為連接符,用法如下。
- 判斷1 || 判斷2?
兩個判斷只要有一個為真(返回值為0),整體就為真,只有全都為假,整體才為假。
判斷1為真,整體已經確定為真,所以判斷2沒有必要執行。
判斷1為假,整體是真是假在于判斷2,所以判斷2肯定是要執行的。?
[root@rhel03 yy]# [ 1 -le 2 ] || [ 2 -ge 3 ]
[root@rhel03 yy]# echo $?
0
[root@rhel03 yy]#
? 這里有兩個判斷,第一個判斷是1小于等于2,這個判斷成立,整體已經確定為真,所以整個判斷為真,返回值為0。?
[root@rhel03 yy]# [ 1 -ge 2 ] || [ 2 -ge 3 ]
[root@rhel03 yy]# echo $?
1
[root@rhel03 yy]#
? 這里有兩個判斷,第一個判斷是1大于等于2,第二個判斷是2大于等于3,這兩個判斷都為假,所以整個判斷為假,返回值為非零。?
if判斷語句?
? 在腳本中執行某條命令需要滿足一定的條件,如果不滿足就不能執行。此時我們就要用到判斷語句了。?
先看if判斷,if判斷的語法如下。
if 條件1 ; then
命令1
elif 條件2 ; then
命令2
else 命令3
fi?
先判斷if后面的判斷是不是成立。
如果成立,則執行命令1,然后跳到f后面,執行6后面的命令。
如果不成立,則不執行命令1,然后判斷elif后面的條件2是不是成立。
如果成立,則執行命令2,然后跳到f后面,執行f后面的命令。
如果不成立,則不執行命令2,進行下一輪的elif 判斷,以此類推。
如果所有if和elif都不成立,則執行clse中的命令3。
練習1:寫一個腳本/opt/sc1.sh,要求只有root用戶才能執行此腳本,其他用戶不能執行,命令如下。
[root@rhel03 opt]# cat scl.sh
#/bin/bash
if [ $UID -ne 0 ]; thenecho "只有root才能執行此腳本"exit 1
fiecho "hello root"
[root@rhel03 opt]# chmod +x /opt/scl.sh
[root@rhel03 opt]#
腳本分析如下。
? root的uid是0,其他用戶的uid不為0。第一個判斷,如果uid不等于0,則打印警告信息“只有root才能執行此腳本”,然后exit退出腳本。?
? 如果這里不加 exit,判斷之后仍然會繼續執行echo "hello root"命令,這樣判斷就失去了意義。只有加了exit之后,如果不是root,則到此結束,不要繼續往下執行了。
如果是tom執行此腳本,則判斷成立,打印完警告信息之后,通過exit退出腳本。
如果是 root執行此腳本,則判斷不成立,直接執行f后面的命令。
使用root用戶執行此腳本的結果如下。
[root@rhel03 opt]# ./scl.sh
hello root
[root@rhel03 opt]#
使用tom用戶執行此腳本的結果如下。
[tom@rhel03 opt]$ ./scl.sh
只有root才能執行此腳本
[tom@rhel03 opt]$
練習2:寫一個腳本 lopt/sc2.sh,運行腳本時,后面必須跟一個參數,參數是系統中的一 個文件。?
如果這個文件不存在,則顯示該文件不存在;如果存在,則顯示該文件的行數,命令如下。?
[root@rhel03 opt]# cat sc2.sh
#!/bin/bash
if [ $# -eq 0 ] ; thenecho "腳本后面必須跟一個參數"exit 1
fi
if [ -f $1 ] ; thenwc -l $1
elseecho "$1不存在"
fi
[root@rhel03 opt]# chmod +x /opt/sc2.sh
[root@rhel03 opt]#
腳本分析如下。
$#表示參數的個數,第一個判斷中,$#的值如果等于0,則說明腳本后面沒有跟任何參數,打印“腳本后面必須跟一個參數”,然后退出腳本。?
如果后面跟了參數,則第一個判斷不成立,然后進行下一個if判斷。
第一個參數用$1來表示,[ -f $1 ]用于判斷所跟的參數是不是存在,如果存在則執行wc -1 $1命令,如果不存在則執行else 中的命令。
運行腳本,效果如下。
[root@rhel03 opt]# ./sc2.sh
腳本后面必須跟一個參數
[root@rhel03 opt]#
這次運行沒有跟任何參數,則提示必須跟一個參數。
[root@rhel03 opt]# ./sc2.sh /etc/hostsxxx
/etc/hostsxxx不存在
[root@rhel03 opt]#
這里跟了一個不存在的文件/etc/hostsxxx,腳本提示這個文件不存在。
[root@rhel03 opt]# ./sc2.sh /etc/hosts
3 /etc/hosts
[root@rhel03 opt]#
這次腳本后面跟了一個存在的文件/etc/hosts,腳本會顯示該文件的行數,為3行。?
for循環語句?
? 有時我們需要做多次重復的操作,例如,創建100個用戶,創建一個用戶需要兩條命 令:useradd和 passwd。那么,創建100個用戶就要重復執行100次,總共執行200條命令, 此時我們就可以利用for循環簡化操作,讓系統自動幫我們重復運行即可。?
?for循環的語法如下。
for 變量 in 值‐1 值‐2 值‐3 值‐4 ; do
命令 $變量
done
? 這里首先把值-1賦值給變量,執行do和done之間的命令,所有命令執行完成之后,再把 值-2賦值給變量,執行do和done之間的命令,執行完所有命令之后,再把值-3賦值給變 量,以此類推,直到把所有的值都賦值給變量。?
看一個簡單的例子,如下所示。
[root@rhel03 opt]# for i in 1 2 3 4 ; do
> let i=$i+10
> echo $i
> done
11
12
13
14
[root@rhel03 opt]#
這里for后面定義了一個變量i,在in后面指定了4個值,分別是1、2、3、4。在do和done 之間定義了兩個命令,第一個是在變量i的原有值的基礎上加上10,然后打印i的值。
先把1賦值給i,此時i的值為1,執行do和 done之間的命令。i加上10之后,i的值變為了 11,然后打印i,得到11,第一次循環結束。
然后把2賦值給i,此時i的值為2,執行do和done之間的命令。i加上10之后,i的值變為了 12,然后打印i,得到12,第二次循環結束。
while 循環語句?
while也可以循環,while循環的語法如下。
while 判斷 ; do
命令1
命令2
done?
? 如果while后面的判斷成立,則執行do和 done之間的命令,在最后一個命令執行完成之 后,會回頭再次判斷一下while后面的判斷是不是成立。如果不成立,則跳出循環執行done后 面的命令;如果成立,則繼續執行do和 done之間的命令,就這樣循環下去。?
先看一個簡單的例子,寫一個腳本/opt/sc3.sh,命令如下。
[root@rhel03 opt]# cat sc3.sh
#!/bin/bash
declare -i n=1
while [ $n -le 4 ] ; doecho $nlet n=$n+1
done
[root@rhel03 opt]# chmod +x sc3.sh
[root@rhel03 opt]#
腳本分析如下。
? 這里先通過declare -i n=1定義了一個整數類型的變量n,初始值為1。然后進入 while進 行循環,先判斷$n的值是不是小于等于4,如果成立,則執行do和 done之間的命令。???
? 一開始$n的值為1,[ $n -le 4 ]這個判斷成立,則進人 do和done之間執行命令。首先打 印Sn的值,然后在此基礎上給n 加上1,所以n的值變為了2,這樣do和done之間的命令就 執行完成了。然后再次到while后面進行判斷,此時$n的值為2,依然滿足小于等于4,再次 執行do 和 done之間的命令。?
? 如此反復,當$n的值最終能增加到4時打印,然后加1,此時n的值變為了5。當Sn的值變 為5之后,while后面的判斷就不再成立了,此時會跳出 while循環。?
用while也可以用于循環一個文件的內容,用法如下。
?while read aa ; do
命令
done < file
? 這里read后面的變量aa是可以隨意指定的,整體的意思是首先讀取file的第一行內容賦值 給aa,執行do和 done之間的命令。然后讀取file的第二行內容賦值給aa,執行do和done 之間的命令,直到讀取到file的最后一行。?
有時while需要一直循環下去(死循環),語法如下。
- while true ; do
- 命令
- done
或者
- while ((1)) ; do
- 命令
- done
或者
- while : ; do
- 命令
- done
?
下面寫一個腳本,來實時判斷vsftpd是否啟動,如果沒有啟動,則將vsftpd啟動,命令如下。?
[root@rhel03 opt]# cat sc4.sh
#!/bin/bash
while : ; dosystemctl is-active vsftpd &> /dev/null
if [ $? -ne 0 ]; thensystemctl start vsftpd
fisleep 1
done
[root@rhel03 opt]# chmod +x /opt/sc4.sh
[root@rhel03 opt]#
? 這里寫了一個 while循環,可以一直循環下去,循環中先判斷vsftpd是否啟動,如果啟動 了則返回值為0,如果沒有啟動則返回值為非零。?
? 下面開始根據返回值來進行判斷,如果$?不等于0,說明vsftpd沒有啟動,則啟動vsftpd 服務。sleep 1的意思是暫停1秒,這樣就實現了每隔1秒來判斷一次vsfilpd是否啟動。
下面開始測試這個腳本,先把腳本放在后臺運行,命令如下。
[root@rhel03 opt]# ./sc4.sh &
[2] 4079
[root@rhel03 opt]#
測試當前vsftpd 的狀態,命令如下。
[root@rhel03 opt]# systemctl is-active vsftpd
active
[root@rhel03 opt]#
關閉vsftpd服務之后,再次檢測vsftpd 的狀態,命令如下。
[root@rhel03 opt]# systemctl stop vsftpd
[root@rhel03 opt]# systemctl is-active vsftpd
active
[root@rhel03 opt]#
可以看到,vsftpd 仍然是啟動的,說明我們的腳本生效了。?