Shell指令碼程式設計初體驗
通常,當人們提到“shell指令碼語言”時,浮現在他們腦海中是bash,ksh,sh或者其它相類似的linux/unix指令碼語言。指令碼語言是與計算機交流的另外一種途徑。使用圖形化視窗介面(不管是windows還是linux都無所謂)使用者可以移動滑鼠並點選各種物件,比如按鈕、列表、選框等等。但這種方式在每次使用者想要計算機/伺服器完成相同任務時(比如說批量轉換照片,或者下載新的電影、mp3等)卻是十分不方便。要想讓所有這些事情變得簡單並且自動化,我們可以使用shell指令碼。
某些程式語言,像pascal、foxpro、C、java之類,在執行前需要先進行編譯。它們需要合適的編譯器來讓我們的程式碼完成某個任務。
而其它一些程式語言,像php、javascript、visualbasic之類,則不需要編譯器,因此它們需要直譯器,而我們不需要編譯程式碼就可以執行程式。
shell指令碼也像直譯器一樣,但它通常用於呼叫外部已編譯的程式。然後,它會捕獲輸出結果、退出程式碼並根據情況進行處理。
Linux世界中最為流行的shell指令碼語言之一,就是bash。而我認為(這是我自己的看法)原因在於,預設情況下bash shell可以讓使用者便捷地通過歷史命令(先前執行過的)導航,與之相反的是,ksh則要求對.profile進行一些調整,或者記住一些“魔術”組合鍵來查閱歷史並修正命令。
好了,我想這些介紹已經足夠了,剩下來哪個環境最適合你,就留給你自己去判斷吧。從現在開始,我將只講bash及其指令碼。在下面的例子中,我將使用CentOS 6.6和bash-4.1.2。請確保你有相同版本,或者更高版本。
Shell指令碼流
shell指令碼語言就跟和幾個人聊天類似。你只需把所有命令想象成能幫你做事的那些人,只要你用正確的方式來請求他們去做。比如說,你想要寫文件。首先,你需要紙。然後,你需要把內容說給某個人聽,讓他幫你寫。最後,你想要把它存放到某個地方。或者說,你想要造一所房子,因而你需要請合適的人來清空場地。在他們說“事情幹完了”,那麼另外一些工程師就可以幫你來砌牆。最後,當這些工程師們也告訴你“事情幹完了”的時候,你就可以叫油漆工來給房子粉飾了。如果你讓油漆工在牆砌好前就來粉飾,會發生什麼呢?我想,他們會開始發牢騷了。幾乎所有這些像人一樣的命令都會說話,如果它們完成了工作而沒有發生什麼問題,那麼它們就會告訴“標準輸出”。如果它們不能做你叫它們做的事——它們會告訴“標準錯誤”。這樣,最後,所有的命令都通過“標準輸入”來聽你的話。
快速例項——當你開啟linux終端並寫一些文字時——你正通過“標準輸入”和bash說話。那麼,讓我們來問問bash shell who am i(我是誰?)吧。
root@localhost ~]# who am i <--- 你通過標準輸入對 bash shell 說 root pts/0 2015-04-22 20:17 (192.168.1.123) <--- bash shell通過標準輸出回答你
現在,讓我們說一些bash聽不懂的問題:
[root@localhost ~]# blablabla <--- 哈,你又在和標準輸入說話了 -bash: blablabla: command not found <--- bash通過標準錯誤在發牢騷了
“:”之前的第一個單詞通常是向你發牢騷的命令。實際上,這些流中的每一個都有它們自己的索引號(LCTT 譯註:檔案控制程式碼號):
- 標準輸入(stdin) – 0
- 標準輸出(stdout) – 1
- 標準錯誤(stderr) – 2
如果你真的想要知道哪個輸出命令說了些什麼——你需要將那次發言重定向到(在命令後使用大於號“>”和流索引)檔案:
[root@localhost ~]# blablabla 1> output.txt -bash: blablabla: command not found
在本例中,我們試著重定向流1(stdout)到名為output.txt的檔案。讓我們來看對該檔案內容所做的事情吧,使用cat命令可以做這事:
[root@localhost ~]# cat output.txt [root@localhost ~]#
看起來似乎是空的。好吧,現在讓我們來重定向流2(stderr):
[root@localhost ~]# blablabla 2> error.txt [root@localhost ~]#
好吧,我們看到牢騷話沒了。讓我們檢查一下那個檔案:
[root@localhost ~]# cat error.txt -bash: blablabla: command not found [root@localhost ~]#
果然如此!我們看到,所有牢騷話都被記錄到errors.txt檔案裡頭去了。
有時候,命令會同時產生stdout和stderr。要重定向它們到不同的檔案,我們可以使用以下語句:
command 1>out.txt 2>err.txt
要縮短一點語句,我們可以忽略“1”,因為預設情況下stdout會被重定向:
command >out.txt 2>err.txt
好吧,讓我們試試做些“壞事”。讓我們用rm命令把file1和folder1給刪了吧:
[root@localhost ~]# rm -vf folder1 file1 > out.txt 2>err.txt
現在來檢查以下輸出檔案:
[root@localhost ~]# cat out.txt removed `file1' [root@localhost ~]# cat err.txt rm: cannot remove `folder1': Is a directory [root@localhost ~]#
正如我們所看到的,不同的流被分離到了不同的檔案。有時候,這也不是很方便,因為我們想要檢視出現錯誤時,在某些操作前面或後面所連續發生的事情。要實現這一目的,我們可以重定向兩個流到同一個檔案:
command >>out_err.txt 2>>out_err.txt
注意:請注意,我使用“>>”替代了“>”。它允許我們附加到檔案,而不是覆蓋檔案。
我們也可以重定向一個流到另一個:
command >out_err.txt 2>&1
讓我來解釋一下吧。所有命令的標準輸出將被重定向到out_err.txt,錯誤輸出將被重定向到流1(上面已經解釋過了),而該流會被重定向到同一個檔案。讓我們看這個例項:
[root@localhost ~]# rm -fv folder2 file2 >out_err.txt 2>&1 [root@localhost ~]# cat out_err.txt rm: cannot remove `folder2': Is a directory removed `file2' [root@localhost ~]#
看著這些組合的輸出,我們可以將其說明為:首先,rm命令試著將folder2刪除,而它不會成功,因為linux要求-r鍵來允許rm命令刪除資料夾,而第二個file2會被刪除。通過為rm提供-v(詳情)鍵,我們讓rm命令告訴我們每個被刪除的檔案或資料夾。
這些就是你需要知道的,關於重定向的幾乎所有內容了。我是說幾乎,因為還有一個更為重要的重定向工具,它稱之為“管道”。通過使用|(管道)符號,我們通常重定向stdout流。
比如說,我們有這樣一個文字檔案:
[root@localhost ~]# cat text_file.txt This line does not contain H e l l o word This lilne contains Hello This also containd Hello This one no due to HELLO all capital Hello bash world!
而我們需要找到其中某些帶有“Hello”的行,Linux中有個grep命令可以完成該工作:
[root@localhost ~]# grep Hello text_file.txt This lilne contains Hello This also containd Hello Hello bash world! [root@localhost ~]#
當我們有個檔案,想要在裡頭搜尋的時候,這用起來很不錯。當如果我們需要在另一個命令的輸出中查詢某些東西,這又該怎麼辦呢?是的,當然,我們可以重定向輸出到檔案,然後再在檔案裡頭查詢:
[root@localhost ~]# fdisk -l>fdisk.out [root@localhost ~]# grep "Disk /dev" fdisk.out Disk /dev/sda: 8589 MB, 8589934592 bytes Disk /dev/mapper/VolGroup-lv_root: 7205 MB, 7205814272 bytes Disk /dev/mapper/VolGroup-lv_swap: 855 MB, 855638016 bytes [root@localhost ~]#
如果你打算grep一些雙引號引起來帶有空格的內容呢!
注意:fdisk命令顯示關於Linux作業系統磁碟驅動器的資訊。
就像我們看到的,這種方式很不方便,因為我們不一會兒就把臨時檔案空間給搞亂了。要完成該任務,我們可以使用管道。它們允許我們重定向一個命令的stdout到另一個命令的stdin流:
[root@localhost ~]# fdisk -l | grep "Disk /dev" Disk /dev/sda: 8589 MB, 8589934592 bytes Disk /dev/mapper/VolGroup-lv_root: 7205 MB, 7205814272 bytes Disk /dev/mapper/VolGroup-lv_swap: 855 MB, 855638016 bytes [root@localhost ~]#
如你所見,我們不需要任何臨時檔案就獲得了相同的結果。我們把fdisk stdout重定向到了grep stdin。
注意 : 管道重定向總是從左至右的。
還有幾個其它重定向,但是我們將把它們放在後面講。
在shell中顯示自定義資訊
正如我們所知道的,通常,與shell的交流以及shell內的交流是以對話的方式進行的。因此,讓我們建立一些真正的指令碼吧,這些指令碼也會和我們講話。這會讓你學到一些簡單的命令,並對指令碼的概念有一個更好的理解。
假設我們是某個公司的總服務檯經理,我們想要建立某個shell指令碼來註冊呼叫資訊:電話號碼、使用者名稱以及問題的簡要描述。我們打算把這些資訊儲存到普通文字檔案data.txt中,以便今後統計。指令碼它自己就是以對話的方式工作,這會讓總服務檯的工作人員的小日子過得輕鬆點。那麼,首先我們需要顯示提問。對於顯示資訊,我們可以用echo和printf命令。這兩個都是用來顯示資訊的,但是printf更為強大,因為我們可以通過它很好地格式化輸出,我們可以讓它右對齊、左對齊或者為資訊留出專門的空間。讓我們從一個簡單的例子開始吧。要建立檔案,請使用你慣用的文字編輯器(kate,nano,vi,……),然後建立名為note.sh的檔案,裡面寫入這些命令:
echo "Phone number ?"
如何執行/執行指令碼?
在儲存檔案後,我們可以使用bash命令來執行,把我們的檔案作為它的引數:
[root@localhost ~]# bash note.sh Phone number ?
實際上,這樣來執行指令碼是很不方便的。如果不使用bash命令作為字首來執行,會更舒服一些。要讓指令碼可執行,我們可以使用chmod命令:
[root@localhost ~]# ls -la note.sh -rw-r--r--. 1 root root 22 Apr 23 20:52 note.sh [root@localhost ~]# chmod +x note.sh [root@localhost ~]# ls -la note.sh -rwxr-xr-x. 1 root root 22 Apr 23 20:52 note.sh [root@localhost ~]#
注意 : ls命令顯示了當前資料夾內的檔案。通過新增-la鍵,它會顯示更多檔案資訊。
如我們所見,在chmod命令執行前,指令碼只有讀(r)和寫(w)許可權。在執行chmod +x後,它就獲得了執行(x)許可權。(關於許可權的更多細節,我會在下一篇文章中講述。)現在,我們只需這麼來執行:
[root@localhost ~]# ./note.sh Phone number ?
在指令碼名前,我新增了 ./ 組合。.(點)在unix世界中意味著當前位置(當前資料夾),/(斜線)是資料夾分隔符。(在Windows系統中,我們使用反斜線 / 表示同樣功能)所以,這整個組合的意思是說:“從當前資料夾執行note.sh指令碼”。我想,如果我用完整路徑來執行這個指令碼的話,你會更加清楚一些:
[root@localhost ~]# /root/note.sh Phone number ? [root@localhost ~]#
它也能工作。
如果所有linux使用者都有相同的預設shell,那就萬事OK。如果我們只是執行該指令碼,預設的使用者shell就會用於解析指令碼內容並執行命令。不同的shell的語法、內部命令等等有著一丁點不同,所以,為了保證我們的指令碼會使用bash,我們應該新增#!/bin/bash到檔案首行。這樣,預設的使用者shell將呼叫/bin/bash,而只有在那時候,指令碼中的命令才會被執行:
[root@localhost ~]# cat note.sh #!/bin/bash echo "Phone number ?"
直到現在,我們才100%確信bash會用來解析我們的指令碼內容。讓我們繼續。
讀取輸入
在顯示資訊後,指令碼會等待使用者回答。有個read命令用來接收使用者的回答:
#!/bin/bash echo "Phone number ?" read phone
在執行後,指令碼會等待使用者輸入,直到使用者按[ENTER]鍵結束輸入:
[root@localhost ~]# ./note.sh Phone number ? 12345 <--- 這兒是我輸入的內容 [root@localhost ~]#
你輸入的所有東西都會被儲存到變數phone中,要顯示變數的值,我們同樣可以使用echo命令:
[root@localhost ~]# cat note.sh #!/bin/bash echo "Phone number ?" read phone echo "You have entered $phone as a phone number" [root@localhost ~]# ./note.sh Phone number ? 123456 You have entered 123456 as a phone number [root@localhost ~]#
在bash shell中,一般我們使用$(美元)符號來表明這是一個變數,除了讀入到變數和其它為數不多的時候才不用這個$(將在今後說明)。
好了,現在我們準備新增剩下的問題了:
#!/bin/bash echo "Phone number?" read phone echo "Name?" read name echo "Issue?" read issue [root@localhost ~]# ./note.sh Phone number? 123 Name? Jim Issue? script is not working. [root@localhost ~]#
使用流重定向
太完美了!剩下來就是重定向所有東西到檔案data.txt了。作為欄位分隔符,我們將使用/(斜線)符號。
注意 : 你可以選擇任何你認為是最好的分隔符,但是確保檔案內容不會包含這些符號在內,否則它會導致在文字行中產生額外欄位。
別忘了使用“>>”來代替“>”,因為我們想要將輸出內容附加到檔案末!
[root@localhost ~]# tail -2 note.sh read issue echo "$phone/$name/$issue">>data.txt [root@localhost ~]# ./note.sh Phone number? 987 Name? Jimmy Issue? Keybord issue. [root@localhost ~]# cat data.txt 987/Jimmy/Keybord issue. [root@localhost ~]#
注意 : tail命令顯示了檔案的最後的n行。
搞定。讓我們再來執行一次看看:
[root@localhost ~]# ./note.sh Phone number? 556 Name? Janine Issue? Mouse was broken. [root@localhost ~]# cat data.txt 987/Jimmy/Keybord issue. 556/Janine/Mouse was broken. [root@localhost ~]#
我們的檔案在增長,讓我們在每行前面加個日期吧,這對於今後擺弄這些統計資料時會很有用。要實現這功能,我們可以使用date命令,並指定某種格式,因為我不喜歡預設格式:
[root@localhost ~]# date Thu Apr 23 21:33:14 EEST 2015 <---- date命令的預設輸出 [root@localhost ~]# date "+%Y.%m.%d %H:%M:%S" 2015.04.23 21:33:18 <---- 格式化後的輸出
有幾種方式可以讀取命令的輸出到變數,在這種簡單的情況下,我們將使用`(是反引號,不是單引號,和波浪號~在同一個鍵位):
[root@localhost ~]# cat note.sh #!/bin/bash now=`date "+%Y.%m.%d %H:%M:%S"` echo "Phone number?" read phone echo "Name?" read name echo "Issue?" read issue echo "$now/$phone/$name/$issue">>data.txt [root@localhost ~]# ./note.sh Phone number? 123 Name? Jim Issue? Script hanging. [root@localhost ~]# cat data.txt 2015.04.23 21:38:56/123/Jim/Script hanging. [root@localhost ~]#
嗯…… 我們的指令碼看起來有點醜啊,讓我們來美化一下。如果你要手動讀取read命令,你會發現read命令也可以顯示一些資訊。要實現該功能,我們應該使用-p鍵加上資訊:
[root@localhost ~]# cat note.sh #!/bin/bash now=`date "+%Y.%m.%d %H:%M:%S"` read -p "Phone number: " phone read -p "Name: " name read -p "Issue: " issue echo "$now/$phone/$name/$issue">>data.txt
你可以直接從控制檯查詢到各個命令的大量有趣的資訊,只需輸入:man read, man echo, man date, man ……
同意嗎?它看上去是舒服多了!
[root@localhost ~]# ./note.sh Phone number: 321 Name: Susane Issue: Mouse was stolen [root@localhost ~]# cat data.txt 2015.04.23 21:38:56/123/Jim/Script hanging. 2015.04.23 21:43:50/321/Susane/Mouse was stolen [root@localhost ~]#
游標在訊息的後面(不是在新的一行中),這有點意思。(LCTT 譯註:如果用 echo 命令輸出顯示的話,可以用 -n 引數來避免換行。)
迴圈
是時候來改進我們的指令碼了。如果使用者一整天都在接電話,如果每次都要去執行,這豈不是很麻煩?讓我們讓這些活動都永無止境地迴圈去吧:
[root@localhost ~]# cat note.sh #!/bin/bash while true do read -p "Phone number: " phone now=`date "+%Y.%m.%d %H:%M:%S"` read -p "Name: " name read -p "Issue: " issue echo "$now/$phone/$name/$issue">>data.txt done
我已經交換了read phone和now=date行的位置。這是因為我想要在輸入電話號碼後再獲得時間。如果我把它放在迴圈的首行,那麼迴圈一次後,變數 now 就會在資料儲存到檔案中後馬上獲得時間。而這並不好,因為下一次呼叫可能在20分鐘後,甚至更晚。
[root@localhost ~]# ./note.sh Phone number: 123 Name: Jim Issue: Script still not works. Phone number: 777 Name: Daniel Issue: I broke my monitor Phone number: ^C [root@localhost ~]# cat data.txt 2015.04.23 21:38:56/123/Jim/Script hanging. 2015.04.23 21:43:50/321/Susane/Mouse was stolen 2015.04.23 21:47:55/123/Jim/Script still not works. 2015.04.23 21:48:16/777/Daniel/I broke my monitor [root@localhost ~]#
注意: 要從無限迴圈中退出,你可以按[Ctrl]+[C]鍵。Shell會顯示^表示 CTRL 鍵。
使用管道重定向
讓我們新增更多功能到我們的“弗蘭肯斯坦(Frankenstein)”,我想要指令碼在每次呼叫後顯示某個統計資料。比如說,我想要檢視各個號碼呼叫了我幾次。對於這個,我們應該cat檔案data.txt:
[root@localhost ~]# cat data.txt 2015.04.23 21:38:56/123/Jim/Script hanging. 2015.04.23 21:43:50/321/Susane/Mouse was stolen 2015.04.23 21:47:55/123/Jim/Script still not works. 2015.04.23 21:48:16/777/Daniel/I broke my monitor 2015.04.23 22:02:14/123/Jimmy/New script also not working!!! [root@localhost ~]#
現在,所有輸出我們都可以重定向到cut命令,讓cut來把每行切成一塊一塊(我們使用分隔符“/”),然後列印第二個欄位:
[root@localhost ~]# cat data.txt | cut -d"/" -f2 123 321 123 777 123 [root@localhost ~]#
現在,我們可以把這個輸出重定向打另外一個命令sort:
[root@localhost ~]# cat data.txt | cut -d"/" -f2|sort 123 123 123 321 777 [root@localhost ~]#
然後只留下唯一的行。要統計唯一條目,只需新增-c鍵到uniq命令:
[root@localhost ~]# cat data.txt | cut -d"/" -f2 | sort | uniq -c 3 123 1 321 1 777 [root@localhost ~]#
只要把這個新增到我們的迴圈的最後:
#!/bin/bash while true do read -p "Phone number: " phone now=`date "+%Y.%m.%d %H:%M:%S"` read -p "Name: " name read -p "Issue: " issue echo "$now/$phone/$name/$issue">>data.txt echo "===== We got calls from =====" cat data.txt | cut -d"/" -f2 | sort | uniq -c echo "--------------------------------" done
執行:
[root@localhost ~]# ./note.sh Phone number: 454 Name: Malini Issue: Windows license expired. ===== We got calls from ===== 3 123 1 321 1 454 1 777 -------------------------------- Phone number: ^C
當前場景貫穿了幾個熟知的步驟:
- 顯示訊息
- 獲取使用者輸入
- 儲存值到檔案
- 處理儲存的資料
但是,如果使用者有點責任心,他有時候需要輸入資料,有時候需要統計,或者可能要在儲存的資料中查詢一些東西呢?對於這些事情,我們需要使用switches/cases,並知道怎樣來很好地格式化輸出。這對於在shell中“畫”表格的時候很有用。
相關文章
- 初識shell指令碼程式設計指令碼程式設計
- Shell 指令碼程式設計陷阱指令碼程式設計
- shell指令碼程式設計筆記指令碼程式設計筆記
- 7.shell指令碼程式設計指令碼程式設計
- shell指令碼程式設計基礎指令碼程式設計
- XD to Flutter 設計圖轉程式碼 初體驗Flutter
- 初識shell指令碼指令碼
- BASH Shell的指令碼程式設計(轉)指令碼程式設計
- 初識shell程式設計程式設計
- Shell程式設計-01-Shell指令碼初步入門程式設計指令碼
- Linux Shell程式設計(3)——執行shell指令碼Linux程式設計指令碼
- Linux命令列與shell指令碼程式設計入門經驗Linux命令列指令碼程式設計
- 【學習】Linux Shell指令碼程式設計Linux指令碼程式設計
- 高階bash/shell指令碼程式設計指南指令碼程式設計
- 記錄shell指令碼程式設計相關指令碼程式設計
- shell高效程式設計:shell指令碼從未如此美麗程式設計指令碼
- Linux Shell指令碼程式設計-基礎1Linux指令碼程式設計
- 10分鐘入門Shell指令碼程式設計指令碼程式設計
- Shell指令碼程式設計30分鐘入門指令碼程式設計
- html程式碼初體驗HTML
- shell指令碼程式設計之選擇控制結構指令碼程式設計
- shell指令碼程式設計學習筆記-運算子指令碼程式設計筆記
- shell指令碼程式設計學習筆記——變數指令碼程式設計筆記變數
- Linux Shell指令碼程式設計while語句案例Linux指令碼程式設計While
- Shell指令碼程式設計總結及速查手冊指令碼程式設計
- 史上最全shell指令碼程式設計語法上冊指令碼程式設計
- Shell指令碼程式設計規範與變數(shell指令碼必須要知道的規矩!)指令碼程式設計變數
- 實驗1 現代C++程式設計初體驗C++程式設計
- 運維之shell指令碼初識運維指令碼
- 【Python】物件導向程式設計初體驗Python物件程式設計
- Linux shell程式設計(一)shell指令碼中的變數詳解Linux程式設計指令碼變數
- 利用shell指令碼統計程式碼行數指令碼
- Linux系統程式設計(15)——shell指令碼語法Linux程式設計指令碼
- 兩道shell指令碼的程式設計題(sed與awk)指令碼程式設計
- Linux Bash Shell學習(七):shell程式設計基礎——執行Shell指令碼、functionLinux程式設計指令碼Function
- shell 指令碼如何編寫-致初學者指令碼
- LINUX Shell指令碼程式設計例項詳解(一)上Linux指令碼程式設計
- Linux程式設計:將PHP作為Shell指令碼使用(轉)Linux程式設計PHP指令碼