Linux 探索之旅 | 第五部分第六課:一朝 Shell 函式傾,斗轉星移任我行

程式設計師聯盟發表於2017-04-18

Linux 探索之旅 | 第五部分第六課:一朝 Shell 函式傾,斗轉星移任我行

-- 作者 謝恩銘 轉載請註明出處

內容簡介


  1. 前言
  2. 函式的作用
  3. 函式的定義
  4. 傳遞引數
  5. 返回值
  6. 變數作用範圍
  7. 過載命令
  8. 函式的設計
  9. 總結
  10. 第五部分第七課預告

1. 前言


上一課 Linux探索之旅 | 第五部分第五課:迴圈往復,Shell開路 中我們學習了 Shell 中的迴圈語句。

這一課我們來學習對於大多數程式語言來說很重要的內容:函式。

2. 函式的作用


函式到底是什麼呢?

以前在學校裡學習數學的時候,也學過函式。例如:

y = 2x + 1複製程式碼

這個公式也被稱為一個函式,對於每一個給定的 x 值,都會有一個 y 值被計算出來,就是 2 倍的 x ,加上 1。

因為程式設計的基礎是數學,因此程式中的函式也有點數學中函式的原理,給它一定輸入,它會為你產出一定的東西。

函式在英語中是 function,表示 「功能,作用」的意思。因此,你可以這麼理解:函式是實現一定功能的程式碼塊。

我們可以把函式比作一個香腸製造機,在輸入那一頭你把豬裝進去,輸出那一頭就出來香腸了。

Linux 探索之旅 | 第五部分第六課:一朝 Shell 函式傾,斗轉星移任我行
函式就像香腸製造機

函式還是重用程式碼(reuse code)的很好方式。

為什麼這麼說呢?

因為到目前為止,我們寫的所有 Shell 檔案,都是定義一些變數,然後執行一些命令,這些命令還都是逐行依次執行。

目前我們寫的例子程式都比較短小,因此看不出有什麼問題。假如這個檔案的內容一多,達到好幾萬行甚至更多,而且有不少重複的內容,那麼要維護這樣一個龐雜的檔案就極為困難。

這時候,函式就可以出馬幫我們解圍。因為函式可以把一塊程式碼包裹起來,使之成為一個整體,完成某些任務。而且,我們在程式的其他地方,還可以多次使用這塊程式碼。

這樣,我們的程式就會變得有條理,也沒有那麼多重複的相似程式碼。你可以把函式想象成 Shell 指令碼里的小指令碼。

3. 函式的定義


說了這麼多,也許還是不太好理解,我們不如實際來使用一下函式。

如果你學過其他程式語言,比如 C語言,Java,等等,那麼其實 Shell 中的函式機理是與之類似的。但是,Shell 比較「任性」,它的函式形式和主流程式語言有不少區別。

定義(或建立) Shell 函式是非常容易的。有兩種方式:

函式名 () {
    函式體
}複製程式碼

function 函式名 {
    函式體
}複製程式碼
  • 這兩種方式都是可行的。看你個人喜好用哪一種方式。我個人喜歡第一種方式,因為 () 比 function 少好多個字元。要知道,程式設計師是要懂得偷懶的,少輸入一點就節省時間(小編你真的為自己的懶惰找了很高大上的理由呢...)。

  • 函式名後面跟著的圓括號裡不加任何引數:這一點與主流程式語言很不相同。C語言,Java,C++等語言中,函式的圓括號中是可以放置引數的(也就是函式的一部分輸入),但是 Shell 中的函式的圓括號裡不能放置引數。

  • 函式的完整定義必須置於函式的呼叫之前。

我們通過一個小例子來加深理解:

首先,用 vim 或其他文字編輯器來建立一個檔案,叫作 function.sh。

vim function.sh複製程式碼

然後在裡面輸入以下內容:

#!/bin/bash

print_something () {
    echo Hello I am a function
}

print_something
print_something複製程式碼

「 Hello I am a function 」是英語「 你好,我是一個函式」的意思。

執行:

Linux 探索之旅 | 第五部分第六課:一朝 Shell 函式傾,斗轉星移任我行

可以看到程式列印了兩次「 Hello I am a function 」。

我們逐行來解釋這個程式:

  • 第 3 行 : 我們開始了一個函式的定義,給它起了一個名字,叫做 print_something (print 是英語 「列印」的意思,something 是英語 「某些東西」的意思)。
  • 第 4 行:在大括號 {} 中,我們可以寫入多個命令。
  • 第 7 和 8 行:在函式定義之後,我們就可以呼叫它任意多次。這裡連續呼叫了兩次。

4. 傳遞引數


我們上面的函式並沒有處理引數,有時候我們希望函式能為我們處理一些引數,然後輸出一些結果。

在 Shell 函式中,我們給它傳遞引數的方式其實很像給 Shell 指令碼傳遞命令列引數。我們把引數直接置於函式名字後面,然後就像我們之前 Shell 指令碼的引數那樣:$1, $2, $3, 等等。

我們來看一個例子:

#!/bin/bash

print_something () {
    echo Hello $1
}

print_something Luke
print_something John複製程式碼

Linux 探索之旅 | 第五部分第六課:一朝 Shell 函式傾,斗轉星移任我行

5. 返回值


大多數主流程式語言都有函式返回值的概念,可以讓函式回傳一些資料。

Shell 的函式卻沒辦法做到。但是 Shell 的函式可以返回一個狀態,有點類似一個程式或命令退出時會有一個退出狀態,表明是否成功。

Shell 函式要返回狀態,也用 return 這個關鍵字( return 是英語 「返回」的意思)。

來看一個例子:

#!/bin/bash

print_something () {
    echo Hello $1
    return 1
}

print_something Luke
print_something John
echo Return value of previous function is $?複製程式碼
  • 第 5 行:返回的狀態不一定要是被硬編碼的(比如上例中的 1 ),也可以是一個變數。
  • 第 10 行:變數 $? 包含前一次被執行的命令或函式的返回狀態。

執行:

Linux 探索之旅 | 第五部分第六課:一朝 Shell 函式傾,斗轉星移任我行

一般來說,返回狀態 0 表示一切順利;一個非零值表示有錯誤。

如果你實在想要函式返回一個數值(比如說一個計算的值),那麼你也可以用命令的執行結果。

如下:

#!/bin/bash

lines_in_file () {
    cat $1 | wc -l
}

line_num=$(lines_in_file $1)

echo The file $1 has $line_num lines複製程式碼

執行:

Linux 探索之旅 | 第五部分第六課:一朝 Shell 函式傾,斗轉星移任我行

6. 變數作用範圍


變數的作用範圍意味著一個 Shell 指令碼的哪些部分可以訪問到這個變數。

預設來說,一個變數是全域性的(global),意味著在指令碼的任何地方都可以訪問它。

我們也可以建立區域性(local)變數。當我們在函式中建立區域性變數時,這個變數就只能在這個函式中被訪問。

要定義一個區域性變數,我們只要在第一次給這個變數賦值時在變數名前加上關鍵字 local 即可( local 是英語「 本地的」的意思)。

定義區域性變數有一個好處,就是可以防止被指令碼的其他地方的程式碼意外改變數值。

來看一個例子:

#!/bin/bash

local_global () {
    local var1='local 1'
    echo Inside function: var1 is $var1 : var2 is $var2
    var1='changed again'
    var2='2 changed again'
}

var1='global 1'
var2='global 2'

echo Before function call: var1 is $var1 : var2 is $var2

local_global

echo After function call: var1 is $var1 : var2 is $var2複製程式碼

執行:

Linux 探索之旅 | 第五部分第六課:一朝 Shell 函式傾,斗轉星移任我行

在函式中,儘量用區域性變數。只有實在不行才用全域性變數,畢竟全域性變數不太安全。

7. 過載命令


我們可以用函式來實現命令的過載,也就是說把函式的名字取成與我們通常在命令列用的命令相同的名字。

例如,也許我們每次在指令碼中呼叫 ls 命令時,其實是想要實現 ls -lh 的效果。那麼我們可以這麼做:

#!/bin/bash

ls () {
    command ls -lh
}

ls複製程式碼

第 4 行如果沒有 command 這個關鍵字(command 是英語 「命令」的意思),那麼程式會陷入無限迴圈。
如果你不小心忘了 command 關鍵字而陷入無限迴圈,可以用 Ctrl + c 的組合快捷鍵來停止程式。

執行:

Linux 探索之旅 | 第五部分第六課:一朝 Shell 函式傾,斗轉星移任我行

8. 函式的設計


我們已經看到:在 Shell 中定義函式是很簡單的。但是要定義容易維護和易於擴充套件的函式卻需要經驗和時間。

有時候,好的設計意味著更少的程式碼;有時候意味著需求變更時最少的改動;有時候意味著不容易引起錯誤。

如果一個任務需要被執行多次,那麼把它放到一個函式裡是不錯的選擇。

設計模式中有一個原則是 單一職責原則 ( Single Responsibility Principle )。這種原則也適用於函式的設計,儘量不要讓一個函式執行很多個任務。

9. 總結


  1. 函式使我們可以輕鬆地重用程式碼,使程式更有條理,更易理解和擴充套件。

  2. 我們有兩種定義函式的方式:「 function 函式名 」 或 「 函式名 () 」。

  3. 關鍵字 return 可以用於返回狀態值。

  4. 關鍵字 local 可以用於定義區域性變數。

  5. 關鍵字 command 使得函式可以過載命令。

10. 第五部分第七課預告


今天的課就到這裡,一起加油吧!

下一課我們學習:Linux探索之旅 | 第五部分第七課:Shell實現圖片展示網頁


微信公眾號「程式設計師聯盟」ProgrammerLeague
我是謝恩銘,在巴黎奮鬥的軟體工程師。
我的簡介
我的經歷
熱愛生活,喜歡游泳,略懂烹飪。
人生格言:“向著標杆直跑”

相關文章