shell腳本學習八(函數)

創建函數:

 1、基本的腳本函數

      在開始編寫較複雜的shell腳本時,你會發現自己重複使用了部分能夠執行特定任務的代碼。 這些代碼有時很簡單,比如顯示一條文本消息,或者從腳本用戶那裏獲得一個答案;有時則會比 較複雜,需要作爲大型處理過程中的一部分被多次使用。        在後一類情況下,在腳本中一遍又一遍地編寫同樣的代碼會很煩人。如果能只寫一次,隨後 在腳本中可多次引用這部分代碼就好了。

       bash shell提供了這種功能。函數是一個腳本代碼塊,你可以爲其命名並在代碼中任何位置重 用。要在腳本中使用該代碼塊時,只要使用所起的函數名就行了(這個過程稱爲調用函數)。

     1.1  創建函數

          有兩種格式可以用來在bash shell腳本中創建函數。第一種格式採用關鍵字function,後跟分配給該代碼塊的函數名。 ( name 與 { } 之間應該有空格,否則會報錯
                  function name {    

                             commands

                                         }

           name屬性定義了賦予函數的唯一名稱。腳本中定義的每個函數都必須有一個唯一的名稱。commands是構成函數的一條或多條bash shell命令。在調用該函數時,bash shell會按命令在 函數中出現的順序依次執行,就像在普通腳本中一樣。  

          第二種格式更接近於其他編程語言中定義函數的方式。 (  name( ) 與{ }之間要有空格,否則會報錯
                   name() {

                           commands

                                }

           函數名後的空括號表明正在定義的是一個函數。這種格式的命名規則和之前定義shell腳本函數的格式一樣

       1.2  使用函數

            要在腳本中使用函數,在行中指定函數名就行了。 

            

            

            每次引用函數名func1時,bash shell會找到func1函數的定義並執行你在那裏定義的命令。 函數定義不一定非得是shell腳本中首先要做的事,但一定要小心。如果在函數被定義前使用函數,會收到一條錯誤消息

           函數引用要在函數體後邊 ( 因爲bash shell腳本是一行一行的執行的)

           也必須注意函數名。函數名必須是唯一的,否則也會有問題。如果重定義了函數, 新定義會覆蓋原來函數的定義,這一切不會產生任何錯誤消息

      1.3  返回值

             bash shell會把函數當作一個小型腳本,運行結束時會返回一個退出狀態碼。 有3種不同的方法來爲函數生成退出狀態碼

        1.3.1  默認退出狀態碼

             默認情況下,函數的退出狀態碼是函數中最後一條命令返回的退出狀態碼。在函數執行結束後,可以用標準變量$?來確定函數的退出狀態碼。  

             ( 在命令行中顯示 退出狀態碼得用echo   例如:echo   $? )

            

           

           函數的退出狀態碼是2,這是因爲函數中的最後一條命令ls -l   badfile 沒有成功運行。但無法知道函數中其他命令中是否成功運行

          更換最後一條運行的命令,如下

         

         

          這次,由於函數最後一條語句echo運行成功,該函數的退出狀態碼就是0,儘管其中有一條命令並沒有正常運行。

          這表明使用函數的默認退出狀態碼是很危險的,可以使用幾種辦法來解決這個問題

       1.3.2  使用return命令

           bash shell使用return命令來退出函數並返回特定的退出狀態碼。return命令允許指定一個整數值來定義函數的退出狀態碼,從而提供了一種簡單的途徑來編程設定函數退出狀態碼

          

          

          函數會將$value變量中用戶輸入的值翻倍,然後用return命令返回結果。腳本用$?變量顯示了該值。

          但當用這種方法從函數中返回值時,要小心了。記住下面兩條技巧來避免問題:

                       記住,函數一結束就取返回值;

                       記住,退出狀態碼必須是0~255

          如果在用$?變量提取函數返回值之前執行了其他命令,函數的返回值就會丟失。記住,$? 變量會返回執行的最後一條命令的退出狀態碼

          第二條規定就限制了return 返回值的取值範圍,所有超過255的數值都會出錯,所以要返回較大的整數值或者字符串值的話,就不能使用return了

        1.3.3  使用函數輸出

使用return返回的是退出狀態碼,要用$?查看,使用變量存儲函數結果輸出要用echo等  

             正如可以將命令的輸出保存到shell變量中一樣,也可以對函數的輸出採用同樣的處理辦 法。可以用這種技術來獲得任何類型的函數輸出,並將其保存到變量中: 

               result=$(doublevalue)

             這個命令會將doublevalue函數的輸出賦給$result變量,例子如下

             

            

           新函數會用echo語句來顯示計算的結果。該腳本會獲取doublevalue函數的輸出,而不是查看退出狀態碼。

           ( 通過這種技術,你還可以返回浮點值和字符串值。這使它成爲一種獲取函數返回值的強大方法 )

           這個例子中演示了一個不易察覺的技巧。你會注意到doublevalue函數實際上輸出了兩條消息。read 命令輸出了一條簡短的消息來向用戶詢問輸入值。bash shell腳本非常聰明,並不將read作爲STDOUT 輸出的一部分,並且忽略掉它。如果你用echo語句生成這條消息來向用戶查詢,那麼它會與輸出值一起被讀進shell變量中。 

          與下面直接輸出的例子對比一下,可能你會有所思考:

          

          

          上述腳本中函數fun1使用的第一條echo語句也被讀進shell變量,與上一個例子的read -p “ ” 作對比

      1.4  在函數中使用變量

         1.4.1  向函數傳遞參數

             bash shell會將函數當作小型腳本來對待。這意味着你可以像普通腳本那樣向函數傳遞參數

             函數可以使用標準的參數環境變量來表示命令行上傳給函數的參數。例如,函數名會在$0 變量中定義,函數命令行上的任何參數都會通過$1、$2等定義。也可以用特殊變量$#來判斷傳給函數的參數數目。

            在腳本中指定函數時,必須將參數和函數放在同一行,像這樣: 

                   func1 $value 10

            然後函數可以用參數環境變量來獲得參數值,這裏有個使用此方法向函數傳值的例子:

           在腳本中向函數傳遞參數,

           

          

          腳本中的addem函數首先會檢查腳本傳給它的參數數目。如果沒有任何參數,或者參數多於兩個,addem會返回值-1。如果只有一個參數,addem會將參數與自身相加。如果有兩個參數,addem會將它們進行相加。

          由於函數使用特殊參數環境變量作爲自己的參數值,因此它無法直接獲取腳本在命令行中的參數值。

          ( 腳本中的函數不能獲取到運行腳本時所攜帶的參數)正如下例,嘗試使用函數獲取命令行參數將會報錯

          

         儘管函數也使用了$1和$2變量,但它們和腳本主體中的$1和$2變量並不相同。要在函數中使用這些值,必須在調用函數時手動將它們傳過去

         

         

         通過將$1和$2變量傳給函數,它們就能跟其他變量一樣供函數使用了

      1.4.2  在函數中處理變量   

           給shell腳本程序員帶來麻煩的原因之一就是變量的作用域。作用域是變量可見的區域。函數 中定義的變量與普通變量的作用域不同

           函數使用兩種類型的變量:

                全局變量

                局部變量

             ① 全局變量

             全局變量是在shell腳本中任何地方都有效的變量。如果你在腳本的主體部分定義了一個全局 變量,那麼可以在函數內讀取它的值。如果你在函數內定義了一個全局變量,也可以在腳本的主體部分讀取它的值。 默認情況下,你在腳本中定義的任何變量都是全局變量。在函數外定義的變量可在函數內正常訪問

          

          

          $value變量在函數外定義並被賦值。當fun1函數被調用時,該變量及其值在函數中都依然有效。如果變量在函數內被賦予了新值,那麼在腳本中引用該變量時,新值也依然有效。

          但這其實很危險,尤其是如果你想在不同的shell腳本中使用函數的話。它要求你清清楚楚地知道函數中具體使用了哪些變量,包括那些用來計算非返回值的變量。這裏有個例子可說明事情是如何搞砸的

          

          

          最初的想法是拿着temp=4與value=6相比較,但是由於在函數中使用了temp變量,導致其值發生變化,所以產生了意外的結果。

          ② 局部變量

          其實根本無需在函數中使用全局變量,函數內部使用的任何變量都可以被聲明成局部變量。要實現這 一點,只要在變量聲明的前面加上local關鍵字就可以了

           local  temp   

           也可以在變量賦值語句中使用local關鍵字: 

           local temp=$[ $value + 5 ] 

          local關鍵字保證了變量只侷限在該函數中。如果腳本中在該函數之外有同樣名字的變量, 那麼shell將會保持這兩個變量的值是分離的。現在你就能很輕鬆地將函數變量和腳本變量隔離開 了,只共享需要共享的變量

          

          

          在函數func1中使用了local關鍵字之後,func1函數中去使用$temp變量時,並不會影響在腳本主體中賦給$temp變量的值

    1.5  數組變量和函數  

          Shell 數組用括號來表示,元素用"空格"符號分割開,語法格式如下:

           array_name=(value1 ... valuen)

           讀取數組元素值的一般格式是:

           ${array_name[index]}

            使用@ 或 * 可以獲取數組中的所有元素,例如:

                     ${my_array[*]}

                     ${my_array[@]}

       1.5.1  向函數傳數組參數

           向腳本函數傳遞數組變量的方法會有點不好理解。將數組變量當作單個參數傳遞的話,它不會起作用

           

           $@變量將所有變量都保存爲單獨的詞,$#變量設爲命令行輸入的參數總數,$* 變量會將所有參數保存爲一個字符串。

          腳本解析:

                    第9行定義myarray變量是一個數組,第10行echo打印數組全部元素,11行引用函數,函數體第4行將參數$myarray的數組元素傳入$@(但是隻能傳入第一個元素值),緊接着定義變量thisarray接受數組第一個元素

           

          如果你試圖將該數組變量作爲函數參數,函數只會取數組變量的第一個值。

          要解決這個問題,你必須將該數組變量的值分解成單個的值,然後將這些值作爲函數參數使用。在函數內部,可以將所有的參數重新組合成一個新的變量。下面是個具體的例子

           

           

           該腳本用$myarray變量來保存所有的數組元素,然後將它們都放在函數的命令行上作爲參數傳入函數。該函數隨後從命令行參數中重建數組變量,定義局部變量newarray數組進行接收所有元素。在函數內部,數組仍然可以像其他數組一樣使用。

           

          

          addarray函數會遍歷所有的數組元素,將它們累加在一起。你可以在myarray數組變量中放置任意多的值,addarry函數會將它們都加起來

       1.5.2  從函數返回數組

            從函數裏向shell腳本傳回數組變量也用類似的方法。函數用echo語句來按正確順序輸出單個 數組值,然後腳本再將它們重新放進一個新的數組變量中

            

            

            該腳本用$arg1變量將數組值傳給funct1函數。func1函數將該數組重組到新的數組變量中,生成該輸出數組變量的一個副本。然後對數據元素進行遍歷,將每個元素值翻倍,並將結果存入函數中該數組變量的副本。

            func1函數使用echo語句來輸出每個數組元素的值。腳本用func1函數的輸出來重新生成一個新的數組變量。

      1.6  函數遞歸

           局部函數變量的一個特性是自成體系。除了從腳本命令行處獲得的變量,自成體系的函數不 需要使用任何外部資源 

           這個特性使得函數可以遞歸地調用,也就是說,函數可以調用自己來得到結果。通常遞歸函數都有一個最終可以迭代到的基準值。許多高級數學算法用遞歸對複雜的方程進行逐級規約,直到基準值定義的那級。

           遞歸算法的經典例子是計算階乘。一個數的階乘是該數之前的所有數乘以該數的值。因此,要計算5的階乘,可以執行如下方程:   

                   5! = 1 * 2 * 3 * 4 * 5 = 120

          使用遞歸,方程可以簡化成以下形式: 
                      x! = x * (x-1)!

           就是說,x的階乘等於x乘以x1的階乘。這可以用簡單的遞歸腳本表達爲: 

                function factorial {    

                       if [ $1 -eq 1 ]    

                       then      

                                 echo 1    

                        else      

                               local temp=$[ $1 - 1 ]      

                               local result=' factorial $temp '      

                               echo $[ $result * $1 ]    

                        fi

                    }

              階乘函數用它自己來計算階乘的值: 

              

      1.7  創建庫      

               source命令用法:

                    source FileName

             source特點: 在當前bash環境下讀取並執行FileName中的命令。該filename文件可以執行權限

              source 命令通常用命令“.”來替代          

               ./的命令用法:

                        ./FileName

              ./  特點:打開一個子shell來讀取並執行FileName中命令,該filename文件不可以無執行權限

                

-----------------------------------------------------------------------------------------------------------------------------------------------------------------

             使用函數可以在腳本中省去一些輸入工作,這一點是顯而易見的。但如果你碰巧要在多個腳 本中使用同一段代碼呢?顯然,爲了使用一次而在每個腳本中都定義同樣的函數太過麻煩

             有個方法能解決這個問題!bash shell允許創建函數庫文件,然後在多個腳本中引用該庫文件
             這個過程的第一步是創建一個包含腳本中所需函數的公用庫文件。這裏有個叫作myfuncs的庫文件,它定義了3個簡單的函數   

              $ cat myfuncs

                  # my script functions 
 
                  function addem {    

                        echo $[ $1 + $2 ]

                         } 
 
                  function multem {    

                         echo $[ $1 * $2 ]

                         } 
 
                  function divem {  

                        if [ $2 -ne 0 ]    

                        then      

                             echo $[ $1 / $2 ]    

                        else      

                              echo -1    

                        fi

                          } 

              

             下一步是在用到這些函數的腳本文件中包含myfuncs庫文件。從這裏開始,事情就變複雜了。
             問題出在shell函數的作用域上。和環境變量一樣,shell函數僅在定義它的shell會話內有效。 如果你在shell命令行界面的提示符下運行myfuncs shell腳本,shell會創建一個新的shell並在其中 運行這個腳本。它會爲那個新shell定義這三個函數,但當你運行另外一個要用到這些函數的腳本時,它們是無法使用的。

             這同樣適用於腳本。如果你嘗試像普通腳本文件那樣運行庫文件,函數並不會出現在腳本中,如下例腳本

             

            

           使用函數庫的關鍵在於source命令。source命令會在當前shell上下文中執行命令,而不是創建一個新shell。可以用source命令來在shell腳本中運行庫文件腳本。這樣腳本就可以使用庫中的函數了

           source命令有個快捷的別名,稱作點操作符(dot operator)。要在shell腳本中運行myfuncs 庫文件,只需添加下面這行: 

                .   ./myfuncs.sh         ( 執行當前文件夾下的myfuncs.sh)

           這個例子假定myfuncs庫文件和shell腳本位於同一目錄。如果不是,你需要使用相應路徑訪問該文件。這裏有個用myfuncs庫文件創建腳本的例子

          

         

          如果不在同一路徑下就要用絕對路徑

         

          

           該腳本成功使用了myfuncs庫文件中定義的函數

    1.8  在命令行上使用函數

          可以用腳本函數來執行一些十分複雜的操作。有時也很有必要在命令行界面的提示符下直接 使用這些函數。

         和在shell腳本中將腳本函數當命令使用一樣,在命令行界面中你也可以這樣做。這個功能很不錯,因爲一旦在shell中定義了函數,你就可以在整個系統中使用它了,無需擔心腳本是不是在 PATH環境變量裏。重點在於讓shell能夠識別這些函數。有幾種方法可以實現。

 
       1.8.1  在命令行上創建函數

             因爲shell會解釋用戶輸入的命令,所以可以在命令行上直接定義一個函數。

             有兩種方法

                ①  第一種方法是採用單行方式定義函數

             

            當在命令行上定義函數時,必須記得在每個命令後面加個分號,這樣shell就能知道在哪裏是命令的起止了

            

            ②  第二種方法是採用多行方式來定義函數,在定義時,bash shell會使用次提示符來提示輸入更多命令。用這種方法,你不用在每條命令的末尾放一個分號,只要按下回車鍵就行

           

          在函數的尾部使用花括號,shell就會知道你已經完成了函數的定義                

---------------------------------------------------------------------------------------------------------------------警告       

          在命令行上創建函數時要特別小心。如果你給函數起了個跟內建命令或另一個命令相同 的名字,函數將會覆蓋原來的命令

---------------------------------------------------------------------------------------------------------------------

        1.8.2  在.bashrc文件中定義函數

              在命令行上直接定義shell函數的明顯缺點是退出shell時,函數就消失了。對於複雜的函數來說,這可是個麻煩事。 一個非常簡單的方法是將函數定義在一個特定的位置,這個位置在每次啓動一個新shell的時 候,都會由shell重新載入。 最佳地點就是.bashrc文件。bash shell在每次啓動時都會在主目錄下查找這個文件,不管是交互式shell還是從現有shell中啓動的新shell 

          ①  直接定義函數

               可以直接在主目錄下的.bashrc文件中定義函數。許多Linux發行版已經在.bashrc文件中定義了 一些東西,所以注意不要誤刪了。把你寫的函數放在文件末尾就行了。

          ②  讀取函數文件

               只要是在shell腳本中,都可以用source命令(或者它的別名點操作符)將庫文件中的函數 添加到你的.bashrc腳本中

                要確保庫文件的路徑名正確,以便bash shell能夠找到該文件。下次啓動shell時,庫中的所有 函數都可在命令行界面下使用了

                

                

                修改完配置文件需要讓其生效

                

               當你將函數寫進了.bascrc 文件。shell還會將定義好的函數傳給子shell進程,這樣一來,這些函數就自動能夠用於該shell會話中的任何shell腳本了。你可以寫個腳本,試試在不定義或使用source的情況下, 直接使用這些函數

               

               

              上例證明,如果將函數定義在.bashrc中,你可以在別的腳本中直接使用

       1.9  實例

               函數的應用絕不僅限於創建自己的函數自娛自樂。在開源世界中,共享代碼纔是關鍵,而這 一點同樣適用於腳本函數。你可以下載大量各式各樣的函數,並將其用於自己的應用程序中

             1.9.1 下載及安裝

               首先是將GNU shtool庫下載並安裝到你的系統中,這樣你才能在自己的shell腳本中使用這些 庫函數。要完成這項工作,可以使用FTP客戶端或者圖像化桌面中的瀏覽器。shtool軟件包的下載 地址是:

                ftp://ftp.gnu.org/gnu/shtool/shtool-2.0.8.tar.gz 

              可以直接使用wget 命令進行下載

             

             將其挪到主目錄並進行解壓

              

        1.9.2  構建庫

               shtool文件必須針對特定的Linux環境進行配置。配置工作必須使用標準的configure和 make命令,這兩個命令常用於C編程環境。要構建庫文件,只要輸入: 

                $ ./confifgure

                $ make

               

               

             configure命令會檢查構建shtool庫文件所必需的軟件。一旦發現了所需的工具,它會使用 工具路徑修改配置文件。

             make命令負責構建shtool庫文件。最終的結果(shtool)是一個完整的庫軟件包。你也可以使用make命令測試這個庫文件

            

           測試模式會測試shtool庫中所有的函數。如果全部通過測試,就可以將庫安裝到Linux系統中的公用位置,這樣所有的腳本就都能夠使用這個庫了。要完成安裝,需要使用make命令的 install選項。不過你得以root用戶的身份運行該命令

            

          現在就能在自己的shell腳本中使用這些函數了

     本人學習到這裏的時候還用centos7嘗試了一下

     

    

 

  1.9.3  shtool 庫函數

      

  1.9.4  使用庫

       可以在命令行或自己的shell腳本中直接使用shtool函數。下面是一個在命令行中使用 platform函數的例子

       (當然你可以在腳本中使用該庫函數)

       

       

       platform函數會返回Linux發行版以及系統所使用的CPU硬件的相關信息。我喜歡的一個函數prop函數。它可以使用\、|、/和-字符創建一個旋轉的進度條。這是一個非常漂亮的工具,可 以告訴shell腳本用戶目前正在進行一些後臺處理工作

       要使用prop函數,只需要將希望監看的輸出管接到shtool腳本就行了 

       

       prop函數會在處理過程中不停地變換進度條字符。在本例中,輸出信息來自於ls命令。你 能看到多少進度條取決於CPU能以多快的速度列出/usr/bin中的文件!-p選項允許你定製輸出文 本,這段文本會出現在進度條字符之前

小結:

           shell腳本函數允許你將腳本中多處用到的代碼放到一個地方。可以創建一個包含該代碼塊的 函數,然後在腳本中通過函數名來引用這塊代碼,而不用一次次地重寫那段代碼。bash shell只要 看到函數名,就會自動跳到對應的函數代碼塊處。            

           甚至可以創建能返回值的函數。這樣你的函數就能夠同腳本進行交互,返回數字和字符串數 據。腳本函數可以用函數中最後一條命令的退出狀態碼或return命令來返回數值。return命令 可以基於函數的結果,通過編程的方式將函數的退出狀態碼設爲特定值。

           函數也可以用標準的echo語句來返回值。可以跟其他shell命令一樣用反引號來獲取輸出的 數據。這樣你就能從函數中返回任意類型的數據了(包括字符串和浮點數) 。

可以在函數中使用shell變量,對其賦值以及從中取值。這樣你就能將任何類型的數據從主體 腳本程序的腳本函數中傳入傳出。函數也支持定義只能在函數內部訪問的局部變量。局部變量使 得用戶可以創建自成體系的函數,這樣就不會影響到shell腳本主體中變量或處理過程了。

          函數也可以調用包括它自身在內的其他函數。函數的自調用行爲稱爲遞歸。遞歸函數通常有 個作爲函數終結條件的基準值。函數在調用自身的同時會不停地減少參數值,直到達到基準值。
        如果需要在shell腳本中使用大量函數,可以創建腳本函數庫文件。庫文件可以用source命 令(或該命令的別名)在任何shell腳本文件中引用,這也稱爲sourcing。shell不會運行庫文件, 但會使這些函數在運行該腳本的shell中生效。可以用同樣的方法創建在普通shell命令行上使用的 函數。你可以直接在命令行上定義函數,或者將它們加到.bashrc文件中,這樣每次啓動新的shell 會話時就可以使用這些函數了。這是一種創建實用工具的簡便方法,不管PATH環境變量設置成 什麼,都可以直接拿來使用

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章