平常工做中編寫shell腳本是再日常不過的了,那編寫過這麼多的shell,當咱們碰見曾經寫過的相關的處理函數時,如何來複用?直接拷貝相應的代碼仍是「引用過來」,或者是在編寫一個功能相對複雜的shell時,如何作到模塊化,如何組織複雜的函數調用?docker
如何導入複用和模塊化,就是這裏要一塊兒學習的。shell
咱們會按照如下幾個部分來學習: 編程
一、shell執行方式不一樣的效果不一樣bash
二、導入模塊編程語言
一、shell執行方式不一樣的效果不一樣ide
第一種:sh test.sh模塊化
這種方式會在咱們當前進程中,再新建一個子進行去執行這個test.sh 腳本。咱們能獲取到的東西,也就是這個test.sh執行完的結果或者輸出。函數
root@docker-host-03:~#more test.sh 學習 #!/bin/bash測試 s="thisis a test file" root@docker-host-03:~#sh test.sh root@docker-host-03:~#echo $s root@docker-host-03:~#sh test.sh root@docker-host-03:~#echo $? 0 |
第二種:sourcetest.sh 或者. Test.sh
這種方式至關因而將test.sh的內容拷貝到當前進程中來執行。這樣咱們能獲取到的內容就不只僅是這個test.sh執行完成的結果。咱們還能獲取到在這個腳本中定義的全局變量,定義的功能函數。由於咱們是將腳本的內容拷貝到當前文件中。
那這種方式就和咱們不少編程語言中的模塊導入相似。咱們來驗證下是否是。
root@docker-host-03:~#. test.sh root@docker-host-03:~#echo $s this is atest file root@docker-host-03:~#s=0 root@docker-host-03:~#echo $s 0 root@docker-host-03:~#source test.sh root@docker-host-03:~#echo $s this is a test file #咱們能夠看到這裏,能夠獲取到變量s的內容。 |
二、導入模塊
2.1)先導入一個最簡單的
基於上面第一點咱們提到的兩種shell的執行方式中的第二種。咱們利用這種「拷貝」的特性就可以實現將之前寫過的功能,進行導入
好比咱們寫了很簡單格式化輸出log的函數:
root@docker-host-03:~# cat test1.sh #!/bin/bash
MODULENAME=$(basename $0) LOGFILE=/tmp/logfile-`date +%Y%m%d`
log_info() { #[2017-03-31 12:00:00 ] - TextName - The log message Localdatetime=`date "+%Y-%m-%d %H:%M:%S"` if[ "$1" ];then echo "[ ${datetime} ] - ${MODULENAME} - $1 " | tee -a${LOGFILE} else return 1 fi } |
上面這個log_info 函數會格式化刷出咱們的日誌內容。咱們能夠在多處複用。
咱們這裏的Main.sh腳本和test1.sh在同一個目錄裏面。咱們main.sh先導入test1.sh的內容,而後使用test1.sh中已有的變量,功能函數。咱們來看下main.sh的內容。
root@docker-host-03:~# cat main.sh #!/bin/bash
#咱們在這裏經過. Test1.sh 或是source test1.sh的方式將test1.sh導入進來。 . test1.sh main() { #咱們在這裏就能夠直接使用log_info函數了。 log_info 'This is the main shell' } main root@docker-host-03:~# ./main.sh [ 2017-03-31 15:11:23 ] - main.sh - This isthe main shell root@docker-host-03:~# more/tmp/logfile-20170331 [ 2017-03-31 15:11:39 ] - main.sh - This isthe main shell
|
2.2)若是咱們須要從其餘地方導入其餘函數
上面咱們的main和test1是在同一個目錄中,因此咱們在main 中使用的是相對路徑。若是咱們須要導入的「模塊」test1.sh 是放在其餘的目錄?或者說咱們的模塊存在層級關係?咱們有應該如何來引入呢?
1)因咱們在引入的時候,系統默認會在環境變量$PATH中去尋找咱們要導入的文件。一種作法是咱們將咱們須要用到的公共模塊放到$PATH包含的路徑中。或者咱們添加一個公用的路徑到$PATH環境變量中,而後將咱們須要用到的公有模塊放到公用文件夾下。
如:
root@docker-host-03:~/test_shell# cat main.sh #!/bin/bash
. test1.sh . Config.sh main() { log_info 'This is the main shell' readConf echo${conf} } main root@docker-host-03:~/test_shell# echo$PATH /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games #在這裏咱們能夠看到咱們將兩個模塊放在了PATH的路徑下面。 root@docker-host-03:~/test_shell# ls/usr/local/bin/test1.sh /usr/local/bin/test1.sh root@docker-host-03:~/test_shell# ls -l/usr/local/bin/Config.sh -rw-r--r-- 1 root root 59 Mar 31 15:41/usr/local/bin/Config.sh
|
這裏我直接將文件拷貝到了$PATH有包含的目錄中。
2) 咱們將模塊所在的絕對路徑export到PATH中:
如:
root@docker-host-03:~/test_shell/module#pwd /root/test_shell/module root@docker-host-03:~/test_shell/module#tree . ├── Config.sh └── test1.sh
0 directories, 2 files root@docker-host-03:~/test_shell/module# cd../ root@docker-host-03:~/test_shell# moremain.sh #!/bin/bash
#這裏咱們給了一個絕對路徑 MODUPATH=/root/test_shell/module export PATH=$PATH:${MODUPATH}
. test1.sh . Config.sh main() { log_info 'This is the main shell' readConf echo${conf} } Main
|
3) 動態的加載模塊
固然若是咱們在編寫一個功能相對複雜的shell的時候,咱們將會將這個一個整個複雜的帶有結構關係的shell打包,拷貝到任何一個地方去執行。這樣咱們就沒有辦法事先知道咱們的絕對路徑是怎樣的。好比這樣的目錄結構:
root@docker-host-03:~/test_shell#pwd /root/test_shell root@docker-host-03:~/test_shell#tree . ├── main.sh ├── module │ ├── Config.sh │ └── test1.sh └── module2 └── mail.sh
2directories, 4 files root@docker-host-03:~/test_shell#more main.sh #!/bin/bash
#這裏動態的獲取到咱們的絕對路徑,並把模塊的路徑拼湊出來。 MODUPATH=$(dirname$(readlink -f $0))/module echo${MODUPATH} exportPATH=$PATH:${MODUPATH}
.test1.sh .Config.sh main() { log_info 'This is the main shell' readConf echo ${conf} } Main
|
4)如何避免重複屢次的導入某個模塊
咱們要說source,其實就是將內容拷貝到同一個地方執行,那咱們能夠在模塊中加入一個判斷條件。若是發現咱們模塊中的某個變量已經存在,咱們就return出去,再也不導入後續的內容。
好比這樣:
root@docker-host-03:~/test_shell#more module/test1.sh #!/bin/bash
#這裏我註釋掉的內容就是。並且這個變量名稱,儘可能按照某種規則確保是惟一的變量名稱。 #if [${m_log} ];then # return 0 #fi #m_log="m_log" #這一行輸出是爲了測試是否屢次導入了。 echo"TEST IS" MODULENAME=$(basename$0) LOGFILE=/tmp/logfile-`date+%Y%m%d`
log_info() { #[ 2017-03-31 12:00:00 ] - TextName - The logmessage datetime=`date "+%Y-%m-%d%H:%M:%S"` if [ "$1" ];then echo "[ ${datetime} ] - ${MODULENAME}- $1 " | tee -a ${LOGFILE} else return 1 fi }
|
這裏咱們在註釋掉的狀況下,看看屢次導入是什麼反應。
root@docker-host-03:~/test_shell# more main.sh #!/bin/bash
MODUPATH=$(dirname $(readlink -f$0))/module echo ${MODUPATH} export PATH=$PATH:${MODUPATH}
. test1.sh . Config.sh main() { log_info 'This is the main shell' readConf echo${conf} } main root@docker-host-03:~/test_shell# moremodule/Config.sh #!/bin/bash
. test1.sh readConf() { conf="Read the config file" } root@docker-host-03:~/test_shell# ./main.sh /root/test_shell/module TEST IS TEST IS [ 2017-03-31 16:51:09 ] - main.sh - This isthe main shell Read the config file #咱們能夠看到這裏屢次輸出了TEST IS內容。 |
#將咱們上面註釋掉的內容去除,如今雖屢次導入,但實質上只會導入一次。這樣也會避免屢次循環引用。
root@docker-host-03:~/test_shell# ./main.sh /root/test_shell/module TEST IS [ 2017-03-31 16:56:31 ] - main.sh - This isthe main shell Read the config file #當咱們去掉了註釋部分,這裏就不會出現屢次重複載入了。 |
-----------------------------小結-----------------
其實這裏的模塊複用,其實就是經過source 或則. 的方式將本來的模塊內容「拷貝過來」,只是在使用的過程中,須要注意模塊路徑的問題,還有層級,和屢次調用的問題。
至於功能複雜的shell,我想基本也不會複雜到沒法接受,基本按照必定的組織發方式就能變得清晰。而且提升複用。
你們能夠本身積累一些比較經常使用的函數,須要用到的時候,導入,不用重複編寫。好比日誌,郵件告警等等。
----------------------------------------------------