4.Vim編輯器與Shell命令指令碼
第4章 Vim編輯器與Shell命令指令碼
章節簡述:
本章首先講解如何使用Vim編輯器來編寫、修改文件,然後通過逐個配置主機名稱、系統網絡卡以及Yum軟體倉庫引數檔案等實驗,幫助讀者加深Vim編輯器中諸多命令、快捷鍵、模式切換方法的理解。然後把前面章節中講解的Linux命令、命令語法與Shell指令碼中的各種流程控制語句通過Vim編輯器寫到Shell指令碼中結合到一起,實現最終能夠自動化工作的指令碼檔案。本章最後演示了怎樣通過at命令與crond計劃任務服務來分別實現一次性的系統任務設定和長期性的系統任務設定,從而讓日常的工作更加高效,更自動化。
本章目錄結構
4.1 Vim文字編輯器
每當在講課時遇到需要讓學生記住的知識點時,為了能讓他們打起精神來,我都會突然提高嗓門,因此有句話他們記得尤其深刻:“在Linux系統中一切都是檔案,而配置一個服務就是在修改其配置檔案的引數”。而且在日常工作中大家也肯定免不了要編寫文件,這些工作都是通過文字編輯器來完成的。
Vim之所以能得到廣大廠商與使用者的認可,原因在於Vim編輯器中設定了三種模式—命令模式、末行模式和編輯模式,每種模式分別又支援多種不同的命令快捷鍵,這大大提高了工作效率,而且使用者在習慣之後也會覺得相當順手。要想高效率地操作文字,就必須先搞清這三種模式的操作區別以及模式之間的切換方法(見圖4-1)。
命令模式:控制游標移動,可對文字進行復制、貼上、刪除和查詢等工作。
輸入模式:正常的文字錄入。
末行模式:儲存或退出文件,以及設定編輯環境。
圖4-1 Vim編輯器模式的切換方法
在每次執行Vim編輯器時,預設進入命令模式,此時需要先切換到輸入模式後再進行文件編寫工作,而每次在編寫完文件後需要先返回命令模式,然後再進入末行模式,執行文件的儲存或退出操作。在Vim中,無法直接從輸入模式切換到末行模式。Vim編輯器中內建的命令有成百上千種用法,為了能夠幫助讀者更快地掌握Vim編輯器,表4-1總結了在命令模式中最常用的一些命令。
表4-1 Vim中常用的命令
命令 | 作用 |
dd | 刪除(剪下)游標所在整行 |
5dd | 刪除(剪下)從游標處開始的5行 |
yy | 複製游標所在整行 |
5yy | 複製從游標處開始的5行 |
n | 顯示搜尋命令定位到的下一個字串 |
N | 顯示搜尋命令定位到的上一個字串 |
u | 撤銷上一步的操作 |
p | 將之前刪除(dd)或複製(yy)過的資料貼上到游標後面 |
末行模式主要用於儲存或退出檔案,以及設定Vim編輯器的工作環境,還可以讓使用者執行外部的Linux命令或跳轉到所編寫文件的特定行數。要想切換到末行模式,在命令模式中輸入一個冒號就可以了。末行模式中可用的命令如表4-2所示。
表4-2 末行模式中可用的命令
命令 | 作用 |
:w | 儲存 |
:q | 退出 |
:q! | 強制退出(放棄對文件的修改內容) |
:wq! | 強制儲存退出 |
:set nu | 顯示行號 |
:set nonu | 不顯示行號 |
:命令 | 執行該命令 |
:整數 | 跳轉到該行 |
:s/one/two | 將當前游標所在行的第一個one替換成two |
:s/one/two/g | 將當前游標所在行的所有one替換成two |
:%s/one/two/g | 將全文中的所有one替換成two |
?字串 | 在文字中從下至上搜索該字串 |
/字串 | 在文字中從上至下搜尋該字串 |
4.1.1 編寫簡單文件
目前為止,大家已經具備了在Linux系統中編寫文件的理論基礎了,接下來我們一起動手編寫一個簡單的指令碼文件。劉遄老師會盡力把所有操作步驟和按鍵過程都標註出來,如果忘記了某些快捷鍵命令的作用,可以再返回前文進行復習。
編寫指令碼文件的第1步就是給文件取個名字,這裡將其命名為practice.txt。如果存在該文件,則是開啟它。如果不存在,則是建立一個臨時的輸入檔案,如圖4-2所示。
圖4-2 嘗試編寫指令碼文件
開啟practice.txt文件後,預設進入的是Vim編輯器的命令模式。此時只能執行該模式下的命令,而不能隨意輸入文字內容,我們需要切換到輸入模式才可以編寫文件。
在圖4-1中提到,可以分別使用a、i、o三個鍵從命令模式切換到輸入模式。其中,a鍵與i鍵分別是在游標後面一位和游標當前位置切換到輸入模式,而o鍵則是在游標的下面再建立一個空行,此時可敲擊a鍵進入到編輯器的輸入模式,如圖4-3所示。
圖4-3 切換至編輯器的輸入模式
進入輸入模式後,可以隨意輸入文字內容,Vim編輯器不會把您輸入的文字內容當作命令而執行,如圖4-4所示。
圖4-4 在編輯器中輸入文字內容
在編寫完之後,想要儲存並退出,必須先敲擊鍵盤Esc鍵從輸入模式返回命令模式,如圖4-5所示。然後再輸入:wq!切換到末行模式才能完成儲存退出操作,如圖4-6所示。
圖4-5 Vim編輯器的命令模式
圖4-6 Vim編輯器的末行模式
當在末行模式中輸入:wq!命令時,就意味著強制儲存並退出文件。然後便可以用cat命令檢視儲存後的文件內容了,如圖4-7所示。
圖4-7 檢視文件的內容
是不是很簡單?!繼續編輯這個文件。因為要在原有文字內容的下面追加內容,所以在命令模式中敲擊o鍵進入輸入模式更會高效,操作如圖4-8、圖4-9與圖4-10所示。
圖4-8 再次通過Vim編輯器編寫文件
圖4-9 進入Vim編輯器的輸入模式
圖4-10 追加寫入一行文字內容
因為此時已經修改了文字內容,所以Vim編輯器在我們嘗試直接退出文件而不儲存的時候就會拒絕我們的操作了。此時只能強制退出才可以結束本次輸入操作,如圖4-11、圖4-12和圖4-13所示。
圖4-11 嘗試退出文字編輯器
圖4-12 因檔案已被修改而拒絕退出操作
現在大家也算是具有了一些Vim編輯器的實戰經驗了,應該也感覺沒有想象中那麼難吧。現在檢視文字的內容,果然發現追加輸入的內容並沒有被儲存下來,如圖4-14所示。
大家在學完了理論知識之後又自己動手編寫了一個文字,現在是否感覺成就滿滿呢?接下來將會由淺入深為讀者安排三個小任務。為了徹底掌握Vim編輯器的使用,大家一定要逐個完成不許偷懶,如果在完成這三個任務期間忘記了相關命令,可返回前文進一步複習掌握。
圖4-13 強制退出文字編輯器
圖4-14 檢視最終編寫成的文字內容
4.1.2 配置主機名稱
為了便於在區域網中查詢某臺特定的主機,或者對主機進行區分,除了要有IP地址外,還要為主機配置一個主機名,主機之間可以通過這個類似於域名的名稱來相互訪問。在Linux系統中,主機名大多儲存在/etc/hostname檔案中,接下來將/etc/hostname檔案的內容修改為“linuxprobe.com”,步驟如下。
第1步:使用Vim編輯器修改“/etc/hostname”主機名稱檔案。
第2步:把原始主機名稱刪除後追加“linuxprobe.com”。注意,使用Vim編輯器修改主機名稱檔案後,要在末行模式下執行:wq!命令才能儲存並退出文件。
第3步:儲存並退出文件,然後使用hostname命令檢查是否修改成功。
[[email protected] ~]# vim /etc/hostname linuxprobe.com
hostname命令用於檢視當前的主機名稱,但有時主機名稱的改變不會立即同步到系統中,所以如果發現修改完成後還顯示原來的主機名稱,可重啟虛擬機器後再行檢視:
[[email protected] ~]# hostname linuxprobe.com
4.1.3 配置網絡卡資訊
網絡卡IP地址配置的是否正確是兩臺伺服器是否可以相互通訊的前提。在Linux系統中,一切都是檔案,因此配置網路服務的工作其實就是在編輯網絡卡配置檔案,因此這個小任務不僅可以幫助您練習使用Vim編輯器,而且也為您後面學習Linux中的各種服務配置打下了堅實的基礎。當您認真學習完本書後,一定會特別有成就感,因為本書前面的基礎部分非常紮實,而後面內容則具有幾乎一致的網絡卡IP地址和執行環境,從而確保您全身心地投入到各類服務程式的學習上,而不用操心繫統環境的問題。
如果您具備一定的運維經驗或者熟悉早期的Linux系統,則在學習本書時會遇到一些不容易接受的差異變化。在RHEL 5、RHEL 6中,網絡卡配置檔案的字首為eth,第1塊網絡卡為eth0,第2塊網絡卡為eth1;以此類推。而在RHEL 7中,網絡卡配置檔案的字首則以ifcfg開始,加上網絡卡名稱共同組成了網絡卡配置檔案的名字,例如ifcfg-eno16777736;好在除了檔名變化外也沒有其他大的區別。
現在有一個名稱為ifcfg-eno16777736的網絡卡裝置,我們將其配置為開機自啟動,並且IP地址、子網、閘道器等資訊由人工指定,其步驟應該如下所示。
第1步:首先切換到/etc/sysconfig/network-scripts目錄中(存放著網絡卡的配置檔案)。
第2步:使用Vim編輯器修改網絡卡檔案ifcfg-eno16777736,逐項寫入下面的配置引數並儲存退出。由於每臺裝置的硬體及架構是不一樣的,因此請讀者使用ifconfig命令自行確認各自網絡卡的預設名稱。
裝置型別:TYPE=Ethernet
地址分配模式:BOOTPROTO=static
網絡卡名稱:NAME=eno16777736
是否啟動:ONBOOT=yes
IP地址:IPADDR=192.168.10.10
子網掩碼:NETMASK=255.255.255.0
閘道器地址:GATEWAY=192.168.10.1
DNS地址:DNS1=192.168.10.1
第3步:重啟網路服務並測試網路是否聯通。
進入到網絡卡配置檔案所在的目錄,然後編輯網絡卡配置檔案,在其中填入下面的資訊:
[[email protected] ~]# cd /etc/sysconfig/network-scripts/ [[email protected] network-scripts]# vim ifcfg-eno16777736 TYPE=Ethernet BOOTPROTO=static NAME=eno16777736 ONBOOT=yes IPADDR=192.168.10.10 NETMASK=255.255.255.0 GATEWAY=192.168.10.1 DNS1=192.168.10.1
執行重啟網絡卡裝置的命令(在正常情況下不會有提示資訊),然後通過ping命令測試網路能否聯通。由於在Linux系統中ping命令不會自動終止,因此需要手動按下Ctrl-c鍵來強行結束程序。
[[email protected] network-scripts]# systemctl restart network [[email protected] network-scripts]# ping 192.168.10.10 PING 192.168.10.10 (192.168.10.10) 56(84) bytes of data. 64 bytes from 192.168.10.10: icmp_seq=1 ttl=64 time=0.081 ms 64 bytes from 192.168.10.10: icmp_seq=2 ttl=64 time=0.083 ms 64 bytes from 192.168.10.10: icmp_seq=3 ttl=64 time=0.059 ms 64 bytes from 192.168.10.10: icmp_seq=4 ttl=64 time=0.097 ms ^C --- 192.168.10.10 ping statistics --- 4 packets transmitted, 4 received, 0% packet loss, time 2999ms rtt min/avg/max/mdev = 0.059/0.080/0.097/0.013 ms
4.1.4 配置Yum倉庫
本書前面講到,Yum軟體倉庫的作用是為了進一步簡化RPM管理軟體的難度以及自動分析所需軟體包及其依賴關係的技術。可以把Yum想象成是一個碩大的軟體倉庫,裡面儲存有幾乎所有常用的工具,而且只需要說出所需的軟體包名稱,系統就會自動為您搞定一切。
既然要使用Yum軟體倉庫,就要先把它搭建起來,然後將其配置規則確定好才行。鑑於第6章才會講解Linux的儲存結構和裝置掛載操作,所以我們當前還是將重心放到Vim編輯器的學習上。如果遇到看不懂的引數也不要緊,後面章節會單獨講解。搭建並配置Yum軟體倉庫的大致步驟如下所示。
第1步:進入到/etc/yum.repos.d/目錄中(因為該目錄存放著Yum軟體倉庫的配置檔案)。
第2步:使用Vim編輯器建立一個名為rhel7.repo的新配置檔案(檔名稱可隨意,但字尾必須為.repo),逐項寫入下面加粗的配置引數並儲存退出(不要寫後面的中文註釋)。
[rhel-media] :Yum軟體倉庫唯一識別符號,避免與其他倉庫衝突。
name=linuxprobe:Yum軟體倉庫的名稱描述,易於識別倉庫用處。
baseurl=file:///media/cdrom:提供的方式包括FTP(ftp://..)、HTTP(http://..)、本地(file:///..)。
enabled=1:設定此源是否可用;1為可用,0為禁用。
gpgcheck=1:設定此源是否校驗檔案;1為校驗,0為不校驗。
gpgkey=file:///media/cdrom/RPM-GPG-KEY-redhat-release:若上面引數開啟校驗,那麼請指定公鑰檔案地址。
第3步:按配置引數的路徑掛載光碟,並把光碟掛載資訊寫入到/etc/fstab檔案中。
第4步:使用“yum install httpd -y”命令檢查Yum軟體倉庫是否已經可用。
進入/etc/yum.repos.d目錄中後建立Yum配置檔案:
[[email protected] ~]# cd /etc/yum.repos.d/ [[email protected] yum.repos.d]# vim rhel7.repo [rhel7] name=rhel7 baseurl=file:///media/cdrom enabled=1 gpgcheck=0
建立掛載點後進行掛載操作,並設定成開機自動掛載(詳見第6章)。嘗試使用Yum軟體倉庫來安裝Web服務,出現Complete!則代表配置正確:
[[email protected] yum.repos.d]# mkdir -p /media/cdrom [[email protected] yum.repos.d]# mount /dev/cdrom /media/cdrom mount: /dev/sr0 is write-protected, mounting read-only [[email protected] yum.repos.d]# vim /etc/fstab /dev/cdrom /media/cdrom iso9660 defaults 0 0 [[email protected] ~]# yum install httpd Loaded plugins: langpacks, product-id, subscription-manager ………………省略部分輸出資訊……………… Dependencies Resolved ================================================================================ Package Arch Version Repository Size ================================================================================ Installing: httpd x86_64 2.4.6-17.el7 rhel 1.2 M Installing for dependencies: apr x86_64 1.4.8-3.el7 rhel 103 k apr-util x86_64 1.5.2-6.el7 rhel 92 k httpd-tools x86_64 2.4.6-17.el7 rhel 77 k mailcap noarch 2.1.41-2.el7 rhel 31 k Transaction Summary ================================================================================ Install 1 Package (+4 Dependent packages) Total download size: 1.5 M Installed size: 4.3 M Is this ok [y/d/N]: y Downloading packages: -------------------------------------------------------------------------------- ………………省略部分輸出資訊……………… Complete!
出現問題?大膽提問!
因讀者們硬體不同或操作錯誤都可能導致實驗配置出錯,請耐心再仔細看看操作步驟吧,不要氣餒~
Linux技術交流請加A群:560843(滿),B群:340829(推薦),C群:463590(推薦),點此檢視全國群。
*本群特色:通過口令驗證確保每一個群員都是《Linux就該這麼學》的讀者,答疑更有針對性,不定期免費領取定製禮品。
4.2 編寫Shell指令碼
可以將Shell終端直譯器當作人與計算機硬體之間的“翻譯官”,它作為使用者與Linux系統內部的通訊媒介,除了能夠支援各種變數與引數外,還提供了諸如迴圈、分支等高階程式語言才有的控制結構特性。要想正確使用Shell中的這些功能特性,準確下達命令尤為重要。Shell指令碼命令的工作方式有兩種:互動式和批處理。
互動式(Interactive):使用者每輸入一條命令就立即執行。
批處理(Batch):由使用者事先編寫好一個完整的Shell指令碼,Shell會一次性執行指令碼中諸多的命令。
在Shell指令碼中不僅會用到前面學習過的很多Linux命令以及正則表示式、管道符、資料流重定向等語法規則,還需要把內部功能模組化後通過邏輯語句進行處理,最終形成日常所見的Shell指令碼。
檢視SHELL變數可以發現當前系統已經預設使用Bash作為命令列終端直譯器了:
[[email protected] ~]# echo $SHELL /bin/bash
4.2.1 編寫簡單的指令碼
估計讀者在看完上文中有關Shell指令碼的複雜描述後,會累覺不愛吧。但是,上文指的是一個高階Shell指令碼的編寫原則,其實使用Vim編輯器把Linux命令按照順序依次寫入到一個檔案中,這就是一個簡單的指令碼了。
例如,如果想檢視當前所在工作路徑並列出當前目錄下所有的檔案及屬性資訊,實現這個功能的指令碼應該類似於下面這樣:
[[email protected] ~]# vim example.sh #!/bin/bash #For Example BY linuxprobe.com pwd ls -al
Shell指令碼檔案的名稱可以任意,但為了避免被誤以為是普通檔案,建議將.sh字尾加上,以表示是一個指令碼檔案。在上面的這個example.sh指令碼中實際上出現了三種不同的元素:第一行的指令碼宣告(#!)用來告訴系統使用哪種Shell直譯器來執行該指令碼;第二行的註釋資訊(#)是對指令碼功能和某些命令的介紹資訊,使得自己或他人在日後看到這個指令碼內容時,可以快速知道該指令碼的作用或一些警告資訊;第三、四行的可執行語句也就是我們平時執行的Linux命令了。什麼?!你們不相信這麼簡單就編寫出來了一個指令碼程式,那我們來執行一下看看結果:
[[email protected] ~]# bash example.sh /root/Desktop total 8 drwxr-xr-x. 2 root root 23 Jul 23 17:31 . dr-xr-x---. 14 root root 4096 Jul 23 17:31 .. -rwxr--r--. 1 root root 55 Jul 23 17:31 example.sh
除了上面用bash直譯器命令直接執行Shell指令碼檔案外,第二種執行指令碼程式的方法是通過輸入完整路徑的方式來執行。但預設會因為許可權不足而提示報錯資訊,此時只需要為指令碼檔案增加執行許可權即可(詳見第5章)。初次學習Linux系統的讀者不用心急,等下一章學完使用者身份和許可權後再來做這個實驗也不遲:
[[email protected] ~]# ./example.sh bash: ./Example.sh: Permission denied [[email protected] ~]# chmod u+x example.sh [[email protected] ~]# ./example.sh /root/Desktop total 8 drwxr-xr-x. 2 root root 23 Jul 23 17:31 . dr-xr-x---. 14 root root 4096 Jul 23 17:31 .. -rwxr--r--. 1 root root 55 Jul 23 17:31 example.sh
4.2.2 接收使用者的引數
但是,像上面這樣的指令碼程式只能執行一些預先定義好的功能,未免太過死板了。為了讓Shell指令碼程式更好地滿足使用者的一些實時需求,以便靈活完成工作,必須要讓指令碼程式能夠像之前執行命令時那樣,接收使用者輸入的引數。
其實,Linux系統中的Shell指令碼語言早就考慮到了這些,已經內設了用於接收引數的變數,變數之間可以使用空格間隔。例如$0對應的是當前Shell指令碼程式的名稱,$#對應的是總共有幾個引數,$*對應的是所有位置的引數值,$?對應的是顯示上一次命令的執行返回值,而$1、$2、$3……則分別對應著第N個位置的引數值,如圖4-15所示。
圖4-15 Shell指令碼程式中的引數位置變數
理論過後我們來練習一下。嘗試編寫一個指令碼程式示例,通過引用上面的變數引數來看下真實效果:
[[email protected] ~]# vim example.sh #!/bin/bash echo "當前指令碼名稱為$0" echo "總共有$#個引數,分別是$*。" echo "第1個引數為$1,第5個為$5。" [[email protected] ~]# sh example.sh one two three four five six 當前指令碼名稱為example.sh 總共有6個引數,分別是one two three four five six。 第1個引數為one,第5個為five。
4.2.3 判斷使用者的引數
學習是一個登堂入室、由淺入深的過程。在學習完Linux命令、掌握Shell指令碼語法變數和接收使用者輸入的資訊之後,就要踏上新的高度—能夠進一步處理接收到的使用者引數。
在本書前面章節中講到,系統在執行mkdir命令時會判斷使用者輸入的資訊,即判斷使用者指定的資料夾名稱是否已經存在,如果存在則提示報錯;反之則自動建立。Shell指令碼中的條件測試語法可以判斷表示式是否成立,若條件成立則返回數字0,否則便返回其他隨機數值。條件測試語法的執行格式如圖4-16所示。切記,條件表示式兩邊均應有一個空格。
圖4-16 條件測試語句的執行格式
按照測試物件來劃分,條件測試語句可以分為4種:
檔案測試語句;
邏輯測試語句;
整數值比較語句;
字串比較語句。
檔案測試即使用指定條件來判斷檔案是否存在或許可權是否滿足等情況的運算子,具體的引數如表4-3所示。
表4-3 檔案測試所用的引數
操作符 | 作用 |
-d | 測試檔案是否為目錄型別 |
-e | 測試檔案是否存在 |
-f | 判斷是否為一般檔案 |
-r | 測試當前使用者是否有許可權讀取 |
-w | 測試當前使用者是否有許可權寫入 |
-x | 測試當前使用者是否有許可權執行 |
下面使用檔案測試語句來判斷/etc/fstab是否為一個目錄型別的檔案,然後通過Shell直譯器的內設$?變數顯示上一條命令執行後的返回值。如果返回值為0,則目錄存在;如果返回值為非零的值,則意味著目錄不存在:
[[email protected] ~]# [ -d /etc/fstab ] [[email protected] ~]# echo $? 1
再使用檔案測試語句來判斷/etc/fstab是否為一般檔案,如果返回值為0,則代表檔案存在,且為一般檔案:
[[email protected] ~]# [ -f /etc/fstab ] [[email protected] ~]# echo $? 0
邏輯語句用於對測試結果進行邏輯分析,根據測試結果可實現不同的效果。例如在Shell終端中邏輯“與”的運算子號是&&,它表示當前面的命令執行成功後才會執行它後面的命令,因此可以用來判斷/dev/cdrom檔案是否存在,若存在則輸出Exist字樣。
[[email protected] ~]# [ -e /dev/cdrom ] && echo "Exist" Exist
除了邏輯“與”外,還有邏輯“或”,它在Linux系統中的運算子號為||,表示當前面的命令執行失敗後才會執行它後面的命令,因此可以用來結合系統環境變數USER來判斷當前登入的使用者是否為非管理員身份:
[[email protected] ~]# echo $USER root [[email protected] ~]# [ $USER = root ] || echo "user" [[email protected] ~]# su - linuxprobe [[email protected] ~]$ [ $USER = root ] || echo "user" user
第三種邏輯語句是“非”,在Linux系統中的運算子號是一個歎號(!),它表示把條件測試中的判斷結果取相反值。也就是說,如果原本測試的結果是正確的,則將其變成錯誤的;原本測試錯誤的結果則將其變成正確的。
我們現在切換回到root管理員身份,再判斷當前使用者是否為一個非管理員的使用者。由於判斷結果因為兩次否定而變成正確,因此會正常地輸出預設資訊:
[[email protected] ~]$ exit logout [[email protected] root]# [ $USER != root ] || echo "administrator" administrator
就技術圖書的寫作來講,一般有兩種套路:讓讀者真正搞懂技術了;讓讀者覺得自己搞懂技術了。因此市面上很多淺顯的圖書會讓讀者在學完之後感覺進步特別快,這基本上是作者有意為之,目的就是讓您覺得“圖書很有料,自己收穫很大”,但是在步入工作崗位後就露出短板吃大虧。所以劉遄老師決定繼續提高難度,為讀者增加一個綜合的示例,一方面作為前述知識的總結,另一方面幫助讀者夯實基礎,能夠在今後工作中更靈活地使用邏輯符號。
當前我們正在登入的即為管理員使用者—root。下面這個示例的執行順序是,先判斷當前登入使用者的USER變數名稱是否等於root,然後用邏輯運算子“非”進行取反操作,效果就變成了判斷當前登入的使用者是否為非管理員使用者了。最後若條件成立則會根據邏輯“與”運算子輸出user字樣;或條件不滿足則會通過邏輯“或”運算子輸出root字樣,而如果前面的&&不成立才會執行後面的||符號。
[[email protected] ~]# [ $USER != root ] && echo "user" || echo "root" root
整數比較運算子僅是對數字的操作,不能將數字與字串、檔案等內容一起操作,而且不能想當然地使用日常生活中的等號、大於號、小於號等來判斷。因為等號與賦值命令符衝突,大於號和小於號分別與輸出重定向命令符和輸入重定向命令符衝突。因此一定要使用規範的整數比較運算子來進行操作。可用的整數比較運算子如表4-4所示。
表4-4 可用的整數比較運算子
操作符 | 作用 |
-eq | 是否等於 |
-ne | 是否不等於 |
-gt | 是否大於 |
-lt | 是否小於 |
-le | 是否等於或小於 |
-ge | 是否大於或等於 |
接下來小試牛刀。我們先測試一下10是否大於10以及10是否等於10(通過輸出的返回值內容來判斷):
[[email protected] ~]# [ 10 -gt 10 ] [[email protected] ~]# echo $? 1 [[email protected] ~]# [ 10 -eq 10 ] [[email protected] ~]# echo $? 0
在2.4節曾經講過free命令,它可以用來獲取當前系統正在使用及可用的記憶體量資訊。接下來先使用free -m命令檢視記憶體使用量情況(單位為MB),然後通過grep Mem:命令過濾出剩餘記憶體量的行,再用awk '{print $4}'命令只保留第四列,最後用FreeMem=`語句`的方式把語句內執行的結果賦值給變數。
這個演示確實有些難度,但看懂後會覺得很有意思,沒準在運維工作中也會用得上。
[[email protected] ~]# free -m total used free shared buffers cached Mem: 1826 1244 582 9 1 413 -/+ buffers/cache: 830 996 Swap: 2047 0 2047 [[email protected] ~]# free -m | grep Mem: Mem: 1826 1244 582 9 [[email protected] ~]# free -m | grep Mem: | awk '{print $4}' 582 [[email protected] ~]# FreeMem=`free -m | grep Mem: | awk '{print $4}'` [[email protected] ~]# echo $FreeMem 582
上面用於獲取記憶體可用量的命令以及步驟可能有些“超綱”了,如果不能理解領會也不用擔心,接下來才是重點。我們使用整數運算子來判斷記憶體可用量的值是否小於1024,若小於則會提示“Insufficient Memory”(記憶體不足)的字樣:
[[email protected] ~]# [ $FreeMem -lt 1024 ] && echo "Insufficient Memory" Insufficient Memory
字串比較語句用於判斷測試字串是否為空值,或兩個字串是否相同。它經常用來判斷某個變數是否未被定義(即內容為空值),理解起來也比較簡單。字串比較中常見的運算子如表4-5所示。
表4-5 常見的字串比較運算子
操作符 | 作用 |
= | 比較字串內容是否相同 |
!= | 比較字串內容是否不同 |
-z | 判斷字串內容是否為空 |
接下來通過判斷String變數是否為空值,進而判斷是否定義了這個變數:
[[email protected] ~]# [ -z $String ] [[email protected] ~]# echo $? 0
再嘗試引入邏輯運算子來試一下。當用於儲存當前語系的環境變數值LANG不是英語(en.US)時,則會滿足邏輯測試條件並輸出“Not en.US”(非英語)的字樣:
[[email protected] ~]# echo $LANG en_US.UTF-8 [[email protected] ~]# [ $LANG != "en.US" ] && echo "Not en.US" Not en.US
出現問題?大膽提問!
因讀者們硬體不同或操作錯誤都可能導致實驗配置出錯,請耐心再仔細看看操作步驟吧,不要氣餒~
Linux技術交流請加A群:560843(滿),B群:340829(推薦),C群:463590(推薦),點此檢視全國群。
*本群特色:通過口令驗證確保每一個群員都是《Linux就該這麼學》的讀者,答疑更有針對性,不定期免費領取定製禮品。
4.3 流程控制語句
儘管此時可以通過使用Linux命令、管道符、重定向以及條件測試語句來編寫最基本的Shell指令碼,但是這種指令碼並不適用於生產環境。原因是它不能根據真實的工作需求來調整具體的執行命令,也不能根據某些條件實現自動迴圈執行。例如,我們需要批量建立1000位使用者,首先要判斷這些使用者是否已經存在;若不存在,則通過迴圈語句讓指令碼自動且依次建立他們。
接下來我們通過if、for、while、case這4種流程控制語句來學習編寫難度更大、功能更強的Shell指令碼。為了保證下文的實用性和趣味性,做到寓教於樂,我會盡可能多地講解各種不同功能的Shell指令碼示例,而不是逮住一個指令碼不放,在它原有內容的基礎上修修補補。儘管這種修補式的示例教學也可以讓讀者明白理論知識,但是卻無法開放思路,不利於日後的工作。
4.3.1 if條件測試語句
if條件測試語句可以讓指令碼根據實際情況自動執行相應的命令。從技術角度來講,if語句分為單分支結構、雙分支結構、多分支結構;其複雜度隨著靈活度一起逐級上升。
if條件語句的單分支結構由if、then、fi關鍵片語成,而且只在條件成立後才執行預設的命令,相當於口語的“如果……那麼……”。單分支的if語句屬於最簡單的一種條件判斷結構,語法格式如圖4-17所示。
圖4-17 單分支的if語句
下面使用單分支的if條件語句來判斷/media/cdrom檔案是否存在,若存在就結束條件判斷和整個Shell指令碼,反之則去建立這個目錄:
[[email protected] ~]# vim mkcdrom.sh #!/bin/bash DIR="/media/cdrom" if [ ! -e $DIR ] then mkdir -p $DIR fi
由於第5章才講解使用者身份與許可權,因此這裡繼續用“bash 指令碼名稱”的方式來執行指令碼。在正常情況下,順利執行完指令碼檔案後沒有任何輸出資訊,但是可以使用ls命令驗證/media/cdrom目錄是否已經成功建立:
[[email protected] ~]# bash mkcdrom.sh [[email protected] ~]# ls -d /media/cdrom /media/cdrom
if條件語句的雙分支結構由if、then、else、fi關鍵片語成,它進行一次條件匹配判斷,如果與條件匹配,則去執行相應的預設命令;反之則去執行不匹配時的預設命令,相當於口語的“如果……那麼……或者……那麼……”。if條件語句的雙分支結構也是一種很簡單的判斷結構,語法格式如圖4-18所示。
圖4-18 雙分支的if條件語句
下面使用雙分支的if條件語句來驗證某臺主機是否線上,然後根據返回值的結果,要麼顯示主機線上資訊,要麼顯示主機不線上資訊。這裡的指令碼主要使用ping命令來測試與對方主機的網路聯通性,而Linux系統中的ping命令不像Windows一樣嘗試4次就結束,因此為了避免使用者等待時間過長,需要通過-c引數來規定嘗試的次數,並使用-i引數定義每個資料包的傳送間隔,以及使用-W引數定義等待超時時間。
[[email protected] ~]# vim chkhost.sh #!/bin/bash ping -c 3 -i 0.2 -W 3 $1 &> /dev/null if [ $? -eq 0 ] then echo "Host $1 is On-line." else echo "Host $1 is Off-line." fi
我們在4.2.3小節中用過$?變數,作用是顯示上一次命令的執行返回值。若前面的那條語句成功執行,則$?變數會顯示數字0,反之則顯示一個非零的數字(可能為1,也可能為2,取決於系統版本)。因此可以使用整數比較運算子來判斷$?變數是否為0,從而獲知那條語句的最終判斷情況。這裡的伺服器IP地址為192.168.10.10,我們來驗證一下指令碼的效果:
[[email protected] ~]# bash chkhost.sh 192.168.10.10 Host 192.168.10.10 is On-line. [[email protected] ~]# bash chkhost.sh 192.168.10.20 Host 192.168.10.20 is Off-line.
if條件語句的多分支結構由if、then、else、elif、fi關鍵片語成,它進行多次條件匹配判斷,這多次判斷中的任何一項在匹配成功後都會執行相應的預設命令,相當於口語的“如果……那麼……如果……那麼……”。if條件語句的多分支結構是工作中最常使用的一種條件判斷結構,儘管相對複雜但是更加靈活,語法格式如圖4-19所示。
圖 4-19 多分支的if條件語句
下面使用多分支的if條件語句來判斷使用者輸入的分數在哪個成績區間內,然後輸出如Excellent、Pass、Fail等提示資訊。在Linux系統中,read是用來讀取使用者輸入資訊的命令,能夠把接收到的使用者輸入資訊賦值給後面的指定變數,-p引數用於向用戶顯示一定的提示資訊。在下面的指令碼示例中,只有當用戶輸入的分數大於等於85分且小於等於100分,才輸出Excellent字樣;若分數不滿足該條件(即匹配不成功),則繼續判斷分數是否大於等於70分且小於等於84分,如果是,則輸出Pass字樣;若兩次都落空(即兩次的匹配操作都失敗了),則輸出Fail字樣:
[[email protected] ~]# vim chkscore.sh #!/bin/bash read -p "Enter your score(0-100):" GRADE if [ $GRADE -ge 85 ] && [ $GRADE -le 100 ] ; then echo "$GRADE is Excellent" elif [ $GRADE -ge 70 ] && [ $GRADE -le 84 ] ; then echo "$GRADE is Pass" else echo "$GRADE is Fail" fi [[email protected] ~]# bash chkscore.sh Enter your score(0-100):88 88 is Excellent [[email protected] ~]# bash chkscore.sh Enter your score(0-100):80 80 is Pass
下面執行該指令碼。當用戶輸入的分數分別為30和200時,其結果如下:
[[email protected] ~]# bash chkscore.sh Enter your score(0-100):30 30 is Fail [[email protected] ~]# bash chkscore.sh Enter your score(0-100):200 200 is Fail
為什麼輸入的分數為200時,依然顯示Fail呢?原因很簡單—沒有成功匹配指令碼中的兩個條件判斷語句,因此自動執行了最終的兜底策略。可見,這個指令碼還不是很完美,建議讀者自行完善這個指令碼,使得使用者在輸入大於100或小於0的分數時,給予Error報錯字樣的提示。
4.3.2 for條件迴圈語句
for迴圈語句允許指令碼一次性讀取多個資訊,然後逐一對資訊進行操作處理,當要處理的資料有範圍時,使用for迴圈語句再適合不過了。for迴圈語句的語法格式如圖4-20所示。
圖4-20 for迴圈語句的語法格式
下面使用for迴圈語句從列表檔案中讀取多個使用者名稱,然後為其逐一建立使用者賬戶並設定密碼。首先建立使用者名稱稱的列表檔案users.txt,每個使用者名稱稱單獨一行。讀者可以自行決定具體的使用者名稱稱和個數:
[[email protected] ~]# vim users.txt andy barry carl duke eric george
接下來編寫Shell指令碼Example.sh。在指令碼中使用read命令讀取使用者輸入的密碼值,然後賦值給PASSWD變數,並通過-p引數向用戶顯示一段提示資訊,告訴使用者正在輸入的內容即將作為賬戶密碼。在執行該指令碼後,會自動使用從列表檔案users.txt中獲取到所有的使用者名稱稱,然後逐一使用“id 使用者名稱”命令檢視使用者的資訊,並使用$?判斷這條命令是否執行成功,也就是判斷該使用者是否已經存在。
需要多說一句,/dev/null是一個被稱作Linux黑洞的檔案,把輸出資訊重定向到這個檔案等同於刪除資料(類似於沒有回收功能的垃圾箱),可以讓使用者的螢幕視窗保持簡潔。
[[email protected] ~]# vim Example.sh #!/bin/bash read -p "Enter The Users Password : " PASSWD for UNAME in `cat users.txt` do id $UNAME &> /dev/null if [ $? -eq 0 ] then echo "Already exists" else useradd $UNAME &> /dev/null echo "$PASSWD" | passwd --stdin $UNAME &> /dev/null if [ $? -eq 0 ] then echo "$UNAME , Create success" else echo "$UNAME , Create failure" fi fi done
執行批量建立使用者的Shell指令碼Example.sh,在輸入為賬戶設定的密碼後將由指令碼自動檢查並建立這些賬戶。由於已經將多餘的資訊通過輸出重定向符轉移到了/dev/null黑洞檔案中,因此在正常情況下螢幕視窗除了“使用者賬戶建立成功”(Create success)的提示後不會有其他內容。
在Linux系統中,/etc/passwd是用來儲存使用者賬戶資訊的檔案。如果想確認這個指令碼是否成功建立了使用者賬戶,可以開啟這個檔案,看其中是否有這些新建立的使用者資訊。
[[email protected] ~]# bash Example.sh Enter The Users Password : linuxprobe andy , Create success barry , Create success carl , Create success duke , Create success eric , Create success george , Create success [[email protected] ~]# tail -6 /etc/passwd andy:x:1001:1001::/home/andy:/bin/bash barry:x:1002:1002::/home/barry:/bin/bash carl:x:1003:1003::/home/carl:/bin/bash duke:x:1004:1004::/home/duke:/bin/bash eric:x:1005:1005::/home/eric:/bin/bash george:x:1006:1006::/home/george:/bin/bash
您還記得在學習雙分支if條件語句時,用到的那個測試主機是否線上的指令碼麼?既然我們現在已經掌握了for迴圈語句,不妨做些更酷的事情,比如嘗試讓指令碼從文字中自動讀取主機列表,然後自動逐個測試這些主機是否線上。
首先建立一個主機列表檔案ipadds.txt:
[[email protected] ~]# vim ipadds.txt 192.168.10.10 192.168.10.11 192.168.10.12
然後前面的雙分支if條件語句與for迴圈語句相結合,讓指令碼從主機列表檔案ipadds.txt中自動讀取IP地址(用來表示主機)並將其賦值給HLIST變數,從而通過判斷ping命令執行後的返回值來逐個測試主機是否線上。指令碼中出現的$(命令)是一種完全類似於第3章的轉義字元中反引號`命令`的Shell操作符,效果同樣是執行括號或雙引號括起來的字串中的命令。大家在編寫指令碼時,多學習幾種類似的新方法,可在工作中大顯身手:
[[email protected] ~]# vim CheckHosts.sh #!/bin/bash HLIST=$(cat ~/ipadds.txt) for IP in $HLIST do ping -c 3 -i 0.2 -W 3 $IP &> /dev/null if [ $? -eq 0 ] ; then echo "Host $IP is On-line." else echo "Host $IP is Off-line." fi done [[email protected] ~]# ./CheckHosts.sh Host 192.168.10.10 is On-line. Host 192.168.10.11 is Off-line. Host 192.168.10.12 is Off-line.
4.3.3 while條件迴圈語句
while條件迴圈語句是一種讓指令碼根據某些條件來重複執行命令的語句,它的迴圈結構往往