在Linux開發中,常常會編寫shell腳原本執行一些任務,一般是一個腳本只作一件事,隨着任務的增長,腳本會愈來愈多,可複用的地方也會逐漸增長,這時就須要提取出腳本中的公共的功能放到一個通用的腳本中,其餘腳本都能複用它shell
本篇文章介紹shell腳本中如何執行外部腳本,如何調用外部腳本中的函數,以及腳本複用相關的方法centos
假如在當前目錄有 a.sh
腳本,內容以下bash
#!/bin/bash echo "a.sh..."
在一個腳本中執行外部腳本主要有如下幾種方式函數
在當前目錄下的 b.sh
腳本,內容以下:調試
#!/bin/bash source a.sh echo "b.sh..."
執行 ./b.sh
,結果以下日誌
[root@ecs-centos-7 ~]# ./b.sh a.sh... b.sh...
腳本中 source a.sh
命令 會先執行當前目錄下的 a.sh
腳本,因此結果會先輸出 a.sh...
再輸出 b.sh
腳本自己的打印code
把 b.sh
腳本中執行a.sh
腳本的語句修改爲 點號 + 空格 + a.sh
,修改以後的腳本內容以下:blog
注意:點號和a.sh
之間必定要加上空格,不然執行的時候會出錯繼承
#!/bin/bash . a.sh echo "b.sh..."
執行 ./b.sh
,結果以下進程
[root@ecs-centos-7 ~]# ./b.sh a.sh... b.sh...
在上述腳本中, . a.sh
會先執行a.sh
腳本, 結果會先輸出 a.sh...
再輸出 b.sh...
sh 外部腳本名字
和 ./外部腳本名字
兩種方式是同樣的,選擇哪種方式都沒問題,下面是之前面一種方式爲例說明的
把 b.sh
腳本中 source a.sh
修改爲 sh a.sh
,修改以後的腳本內容以下:
#!/bin/bash sh a.sh echo "b.sh..."
執行 ./b.sh
命令, 結果以下
[root@ecs-centos-7 ~]# ./b.sh a.sh... b.sh...
能夠看出,結果輸出和上面兩種方式是同樣的
調用外部腳本有 source 外部腳本
、點號 外部腳本
、sh 外部腳本
三種方式,它們之間有什麼區別呢?
其中,source 外部腳本
和 點號 外部腳本
兩種方式是相同的,當前腳本繼承了外部腳本的全局變量和函數, 至關於把外部腳本的函數和全局變量導入了當前腳本中
修改 a.sh
和 b.sh
腳本, 內容以下
a.sh腳本
#!/bin/bash VAR_A=10 func_a() { echo "a.sh...pid:$$,param:$1" }
b.sh腳本
#!/bin/bash source a.sh func_a $1 echo "vara:$VAR_A" echo "b.sh...pid:$$"
執行 ./b.sh 5
命令,結果以下
[root@ecs-centos-7 ~]# ./b.sh 5 a.sh...pid:21485,param:5 vara:10 b.sh...pid:21485
兩個腳本中的 $$ 是指執行腳本的進程ID,從結果能夠看出,a.sh
和 b.sh
都是在同一個進程內執行的,因此在 b.sh
腳本中執行 source a.sh
命令,會把 a.sh
腳本中的全局變量 VAR_A
和函數 func_a
導入到 b.sh
中
在 b.sh
中打印變量 VAR_A
,輸出的值和 a.sh
中相同,調用 func_a
函數,輸出也說明了調用的是 a.sh
中的函數
source 外部腳本
、點號 外部腳本
兩種方式是相同的, 因此, 把 b.sh
中 source a.sh
修改爲 . a.sh
, 執行 ./b.sh 5
, 結果依然是相同的
因爲 sh 外部腳本
的方式是當前腳本和外部腳本在兩個不一樣的進程中執行,因此當前腳本不能直接使用外部腳本中的函數和全局變量
修改 a.sh
和 b.sh
腳本, 內容以下
a.sh腳本
#!/bin/bash test_a() { echo "a.sh...test_a" } echo "a.sh...pid:$$"
b.sh腳本
#!/bin/bash sh a.sh echo "b.sh...pid:$$" test_a
執行 ./b.sh
命令,結果以下
[root@ecs-centos-7 ~]# ./b.sh a.sh...pid:21818 b.sh...pid:21817 ./b.sh:行7: test_a: 未找到命令
從結果能夠看出,執行 a.sh
和 b.sh
的進程ID是不一樣的,b.sh
腳本進程找不到test_a
函數,因此在b.sh
中調用test_a
函數會提示 未找到命令
上一節講到 sh 外部腳本
的方式沒法直接使用外部腳本中函數和全局變量,下面提供幾種方法能夠解決這個問題
這種方法相似於程序代碼中的 switch case 語句,經過switch 選擇不一樣的分支從而執行不一樣的邏輯,shell腳本中是使用case關鍵字來實現的
a.sh腳本
#!/bin/bash VAR_A=10 test_a() { echo "test_a..pid:$$,p1:$1,p2:$2" } get_var() { echo ${VAR_A} } case "$1" in ta) test_a $2 $3 ;; var) get_var ;; *) echo "parameter err..." esac
b.sh腳本
#!/bin/bash echo "b.sh...pid:$$" sh a.sh ta 3 5 ret=$(sh a.sh var) echo "ret:$ret"
執行 ./b.sh
命令,結果以下
[root@ecs-centos-7 ~]# ./b.sh b.sh...pid:24813 test_a..pid:24814,p1:3,p2:5 ret:10
腳本b.sh
一開始打印了調用自身的進程ID
sh a.sh ta 3 5
語句是調用a.sh
腳本,傳入的三個參數分別是ta
, 3
, 5
,執行a.sh
時,傳入的第一個參數 ta
通過case匹配以後調用 test_a
函數,並把剩下的兩個參數 3
和5
做爲參數傳入函數
ret=$(sh a.sh var)
語句時調用a.sh
腳本,傳入一個var
參數,通過case匹配以後調用get_var
函數,該函數的做用輸出腳本中全局變量VAR_A
的值,語句中$()
的做用是獲取()
中命令的返回值,這裏是把a.sh
腳本中 get_var
函數的返回值賦值給 ret
變量,因此該變量的值是 a.sh
腳本中全局變量VAR_A
的值
說明:若是想要獲取函數的返回值,能夠在函數中用 echo
打印相應的輸出值,而後使用$(函數名 參數列表)
能夠獲取到函數中打印的值,如上面b.sh
腳本中 ret=$(sh a.sh var)
語句,變量ret
的值是 a.sh
腳本中 get_var
函數輸出的值10
這裏須要注意的是, 若是函數中有echo
調試日誌,那麼調試日誌也會一塊兒返回
上面介紹的用 case 關鍵字去匹配調用不一樣的函數有一個缺點,每次a.sh
腳本中增長一個函數的時候,case 就須要添加一個分支,分支裏調用不一樣的函數,還須要注意函數是否有參數傳入以及參數數量是否正確
咱們能夠在每一個供外部調用腳本的尾部加上如下的語句,就能夠解決上述問題, 具體語句以下
if [ $# -ge 1 ]; then name="$1" shift 1 $name "$@" fi
上述語句首先判斷調用腳本時傳入的參數數量,只有參數數量大於等於1纔有效,傳入的第一個參數表示函數名字,從第二個參數到最後一個參數都會做爲參數傳入到函數中
這裏的 shift 1
是把傳入腳本的參數左移一個位置,好比:傳入腳本參數有 $1 $2 $3
三個參數,左移一個位置以後, $2
移動到 $1
的位置,$3
移動到 $2
的位置,參數數量變爲2了
緣由: 傳入腳本的參數中,第一個參數是函數名字,從第二個參數起纔是函數的參數,若是不作左移處理,第一個參數函數名字也會做爲參數傳入到函數中
下面是完整的腳本內容
a.sh腳本
#!/bin/bash VAR_A=10 test_a() { echo "test_a..pid:$$,p1:$1,p2:$2" } get_var() { echo ${VAR_A} } if [ $# -ge 1 ]; then name="$1" shift 1 $name "$@" fi
b.sh腳本
#!/bin/bash echo "b.sh...pid:$$" sh a.sh test_a 3 5 ret=$(sh a.sh get_var)
執行 ./b.sh
命令,結果以下
[root@ecs-centos-7 ~]# ./b.sh b.sh...pid:25086 test_a..pid:25087,p1:3,p2:5 ret:10
能夠看出,結果和上面 case 的方法是同樣的
如今其餘腳本中均可以經過 sh a.sh 函數名 參數列表
這樣的方式調用 a.sh
腳本中的函數了,經過 $(sh a.sh 函數名 參數列表)
的方式獲取 a.sh
腳本函數的返回值
與case分支選擇的方式相比,函數調用模板的優勢是調用者只須要關心複用的腳本中函數名、函數傳入參數、函數返回值就能夠直接使用
缺點是若是有多個腳本都調用了複用腳本中的函數,當複用腳本中函數名變動時,須要修改全部調用了它的地方
函數調用模板方式的缺點偏偏是case分支選擇方式的有點,case分支選擇的方式時根據傳入的字符串參數調用不一樣的函數,這裏的字符串參數至關於函數的別名,只要這個參數保持不變,腳本中的函數名字能夠任意變動
上述的優缺點比較只是一個相對的比較,實際應用中下不會很明顯,大部分狀況兩種方式均可以使用
在編寫shell腳本的過程當中,常常會遇到一些莫名奇妙的問題,有些問題就算撓破頭皮都不知道如何解決,腳本複用能夠把一些公共功能提取出來,造成一個個的功能模塊,不只有助於減小咱們編寫腳本時犯的錯誤,並且對後期的腳本維護頗有幫助