Bash 函式是提升指令碼效能和組織結構的利器,尤其在需要重複執行特定程式碼區塊時,能有效避免每次建立新程式的開銷。相較於呼叫外部指令碼,函式的程式碼已載入記憶體,執行速度更快,更適用於效能敏感的任務。定義函式的語法簡潔,可使用 function_name()function function_name 的形式,並將程式碼塊包含在大括號內。函式可接受多個引數,使用 $1, $2 等變數表示,而 $@ 則代表所有引數,方便迭代處理。此外,$# 變數可用於驗證引數數量,確保指令碼的正確執行。為函式引數設定預設值也是常見技巧,可簡化函式呼叫,提升程式碼的健壯性。

Bash 函式與指令碼組織

在之前的章節中,玄貓已經介紹瞭如何使用正規表示式來進行實際應用。現在讓我們探討如何將學到的知識應用於 Bash 函式中。函式是 Bash 指令碼中的基本概念,它們允許我們將程式碼組織成可重複使用且模組化的單元。掌握函式可以幫助我們寫出更高效、可維護且易於閱讀的指令碼。

Bash 函式介紹

Bash 函式是 Linux 作業系統中的基本工具之一。它們允許我們將可重複使用的一段程式碼封裝成具有名稱和引數的單元,這些單元可以從指令碼中的任何地方呼叫。

啟動函式:提高程式碼重用性

Bash 函式的一大優點是它們促進了程式碼重用。如果在指令碼中多次重複相同或相似的程式碼片段,這意味著應該將這些程式碼片段提取到可重複使用的函式中。

例如,假設多個指令碼需要以相同方式解析命令列引數。而不是在每個指令碼中複製貼上引數解析邏輯,我們可以定義一個 parse_args 函式:

parse_args() {
    while [[ $# -gt 0 ]]; do
        case "$1" in
            -h|--help)
                usage
                exit 0
                ;;
            -v|--verbose)
                verbose=true
                ;;
            *)
                echo "Invalid argument: $1"
                usage
                exit 1
                ;;
        esac
        shift
    done
}

內容解密:

parse_args() {
    while [[ $# -gt 0 ]]; do # 語法:while [[ 條件 ]];
        case "$1" in # 語法:case 值 in 模式 );
            -h|--help) # 條件:如果引數是 -h 或 --help 則執行後面指令;
                usage # 呼叫使用說明函式;
                exit 0 # 執行完後離開指令碼;
                ;;
            -v|--verbose) # 條件:如果引數是 -v 或 --verbose 則執行後面指令;
                verbose=true # 啟動 verbose 模式;
                ;;
            *) # 條件:如果引數不符合上述條件 則執行後面指令;
                echo "Invalid argument: $1" # 提示無效引數;
                usage # 呼叫使用說明函式;
                exit 1 # 執行完後離開指令碼;
                ;;
        esac # 語法:case 模式 ); ;
        shift # 語法:shift ;移除引數;使得下一次迴圈能夠檢查下一個引數;
    done # 語法:while [[ 條件 ]] ;done ;結束迴圈;
}

此圖示展示了 parse_args 函式如何處理命令列引數:

  graph TD;
    C[C]
    D[D]
    E[E]
    F[F]
    G[G]
    H[H]
    I[I]
    J[J]
    A[開始] --> B{檢查引數};
    B -->|帶有 -h 或 --help 模式| C;
    B -->|帶有 -v 或 --verbose 模式| D;
    B -->|其他模式| E;
    C --> F{離開指令碼};
    D --> G{繼續處理其他引數};
    E --> H{顯示使用說明};
    H --> I{離開指令碼};
    G --> J{檢查下一個引數};

模組化

Bash 函式允許我們將指令碼拆分成較小、自包含且更易管理的一部分。每個函式應該完成一項具體任務且完成得好。透過將指令碼拆分成模組化的函式,程式碼變得更易理解、除錯和維護。

例如:

fetch_data() {
    # 處理資料抓取邏輯
}

parse_response() {
    # 處理資料解析邏輯
}

update_database() {
    # 處理資料函式庫更新邏輯
}

此圖示展示瞭如何透過模組化函式來組織指令碼:

  graph TD;
    B[B]
    C[C]
    D[D]
    A[開始] --> B{呼叫 fetch_data 函式};
    B --> C{呼叫 parse_response 函式};
    C --> D{呼叫 update_database 函式};

加密機制

函式提供了封裝性(Encapsulation),這意味著它們建立了一個獨立範圍內變數和其他資源。在函式中定義的變數預設情況下是區域性變數(local variables),它們不會汙染全域性名稱空間或與指令碼其他部分中的變數發生衝突。

例如:

my_function() {
    local local_var="This is a local variable"
}
my_function()
echo $local_var # 輸出為空或錯誤訊息,因為 local_var 是區域性變數。

內容解密:

my_function() {   # 基礎語法:定義一個名為 my_function 的函式;
    local local_var="This is a local variable"   # 基礎語法:在 my_function 函式內部宣告一個名為 local_var 的區域性變數,並指定"This is a local variable";
}
my_function   # 基礎語法:呼叫 my_function 函式;執行 my_function 函式內部宣告變數;
echo $local_var   # 基礎語法:輸出區域性變數local_var;陳述式輸出空值或者報錯資訊;因為local_var 是區域性變數,只存在於 my_function 函式內部,不能被外部存取.

此圖示展示了局部變數和全域性變數之間關係:

  graph TD;
    B[B]
    C[C]
    D[D]
    E[E]
    F[F]
    A[開始]-->B{定義 my_function 函式};
    B-->C{宣告一個區域性變數 local_var=This is a local variable};
    C-->D{呼叫 my_function 函式,執行內部操作};
    D-->E{輸出 global_var 值=undefined或報錯資訊,因為沒有 global_var};
    C-->F{Echo 輸出 local_var 值=undefined或報錯資訊,因為 local_var 是區域性變數};

測試性

另一大優點是函式使程式碼更易測試。為單個函式編寫單元測試比為整個指令碼編寫測試要容易得多。這樣可以確保你對函式產生預期結果或副作用有信心。

例如:

test_fetch_data() {
    assert_equal "$(fetch_data)" "expected_output"
}

下章預告

在下一章節中,玄貓會探討如何將我們在這一章節中學到關於正規表示式概念與常見文字解析工具相結合以便專注於常見網路安全及測試任務

Bash 函式及指令碼組織

使用 Bash 函式提升指令碼效能

在 Bash 指令碼開發中,函式的使用不僅能提升程式碼的可讀性和可維護性,還能顯著改善指令碼的效能。特別是當你需要多次呼叫相同的程式碼時,這一點尤為重要。當你呼叫一個函式時,Bash 不需要每次都重新產生新的程式或解析函式定義,因為函式的程式碼已經載入記憶體中,因此呼叫函式的開銷非常低。

相反地,如果你將相同的程式碼放在一個獨立的指令碼中並使用 bash myscript.sh 來呼叫它,Bash 就需要每次都分叉一個新的程式並從磁碟解析指令碼。對於在緊密迴圈中被呼叫的程式碼來說,這樣的開銷會逐漸累積。

雖然函式帶來的效能提升在絕對值上通常較小,因為 Bash 中產生程式已經相當快速。但在一些強調效能的指令碼中,使用函式取代獨立指令碼可以提供額外的效能提升。

定義與呼叫函式

要在 Bash 中定義一個函式,可以使用以下語法:

function_name() {
    # 命令放在此處
}

或者你也可以在函式名稱前使用 function 關鍵字:

function function_name {
    # 命令放在此處
}

以下是函式定義的各個部分:

  • function_name:這是你給函式取的名稱,應該具有描述性且符合變數命名規則(字母、數字和底線,且必須以字母或底線開頭)。
  • ():函式名稱後面必須跟隨一對括號。
  • {}:大括號包含了函式的主體,即由多條命令組成的部分。

以下是一個簡單的範例,展示如何定義一個輸出問候語的函式:

greet() {
    echo "Hello, world!"
}

內容解密:

  1. greet 是這個函式的名稱。根據 Bash 的語法規則,函式名稱後面必須跟隨一對括號。
  2. 大括號 {} 內包含了函式主體。這裡我們放入了 echo 命令。
  3. echo 命令在函式主體中會將字串 “Hello, world!” 輸出到控制檯。

定義好這個函式後,我們可以透過簡單地呼叫其名稱來執行它。以下是如何呼叫這個函式:

greet

執行結果如下:

Hello, world!

傳遞引數給函式

Bash 函式是一種強大的工具,能夠自動化重複任務並建立可重複使用的程式碼塊。透過傳遞引數給函式,可以使其更加靈活和多功能。傳遞引數是指向函式提供動態輸入,使其能夠根據具體情況調整行為。

以下是幾個傳遞引數給 Bash 函式有益之處:

  1. 靈活性:接受引數的函式可以在多種情境下使用。你不需要為每一種微小變化建立多個函式,只需建立一個根據傳遞引數來調整行為的單一函式。這促進了程式碼重用並減少了重複。
  2. 引數化:引數使你能夠為函式提供不同值以控制其行為。這樣就可以根據特定需求或輸入來自訂化函式行為,使其適用於不同情境。
  3. 模組化:透過接受引數,使得這些功能成為獨立自主模組並可以在周圍程式碼中獨立操作。它們可以輕易移動或被其他指令碼重用而不需要顯著修改。這個模組化增強了程式碼組織和維護性。
  4. 可讀性:當接受引數時,它使程式碼更加清晰和自解釋性強。這些引數給出明確指示表示期望值和它們將如何使用這些值。這增強了程式碼理解能力並讓其他開發人員更容易理解和維護指令碼。
  5. 效率:透過向功能傳遞引數可以幫助最佳化程式碼並避免需要全域性變數或複雜邏輯內部功能。功能可以透過引數直接獲得必要資料而不是依賴外部變數。

以下是傳遞引數給 Bash 函式的一個簡單範例:

greet() {
    echo "Hello, $1!"
}

內容解密:

  1. greet 是我們要定義的一個新函式。
  2. $1 是第一個傳遞給 greet 函式的一個變數名稱。
  3. 當你呼叫 greet "Alice" 的時候,「Alice」會被作為第一個參量傳遞給 $1
greet Alice

執行結果如下:

Hello, Alice!

透過這樣設計的一致格式與具體案例展示,玄貓希望每位讀者都能深刻理解Bash語法之設計理念及其實際應用價值

函式傳遞引數

在 Bash 指令碼中,函式是一種封裝重複使用程式碼的方式,並且可以接受引數來增強其靈活性。讓我們從一個簡單的範例開始,瞭解如何在函式中傳遞引數。

基本範例

以下是一個簡單的 Bash 函式範例,它接受一個引數:

greet() {
    echo "Hello, $1!"
}
greet "John"

當我們執行這個指令碼並呼叫 greet 函式時,輸出會是:

Hello, John!

在這個範例中,$1 代表第一個傳遞給函式的引數。當我們呼叫 greet "John" 時,$1 的值就是 "John"

內容解密:

  • greet():定義了一個名為 greet 的函式。
  • echo "Hello, $1!":使用 echo 命令列印一句問候語,其中 $1 代表第一個傳遞給函式的引數。
  • greet "John":呼叫 greet 函式並傳遞 "John" 作為引數。

處理多個引數

Bash 函式也可以接受多個引數。讓我們修改前面的範例,使其能夠處理多個引數:

greet() {
    echo "Hello, $1 $2!"
}
greet "John" "Doe"

這段程式碼的說明如下:

  • 函式 greet 現在期望接收兩個引數。
  • 在函式內部,$1 代表第一個引數,$2 代表第二個引數。
  • echo 命令已更新以包含兩個引數在問候語中。
  • 我們呼叫 greet 函式並傳遞兩個引數:JohnDoe

輸出會是:

Hello, John Doe!

處理可變數量的引數

有時候,我們希望建立一個能夠處理可變數量引數的函式。Bash 提供了一個特別的變數 $@,它代表傳遞給函式的所有引數。以下是一個使用這個概念來迭代使用者名稱的範例:

print_arguments() {
    for arg in "$@"
    do
        echo "Argument: $arg"
    done
}
print_arguments "tsmith" "sjones" "mknight"

此圖示展示瞭如何使用 $@ 來迭代所有傳遞給函式的引數:

  graph TD;
    A[print_arguments] --> B["for arg in \"$@\""];
    B --> C["echo Argument: $arg"];
    C --> D[done];

此圖示解說:

  • A[print_arguments]:表示呼叫 print_arguments 函式。
  • B[for arg in "$@"];:表示迭代所有傳遞給函式的引數。
  • C[echo "Argument: $arg"];:表示列印每個引數。
  • D[done];:表示完成迭代。

說明如下:

  • 函式 print_arguments 定義為處理可變數的引數。
  • 在函式內部,使用 for loop 迭代所有傳遞給函式的引數 $@,它代表引數陣列。
  • 使用 echo 命令將每個引數分行列印。
  • 呼叫 print_arguments 函式並傳遞三個引數:tsmith、sjones 和 mknight。

輸出會是:

Argument: tsmith
Argument: sjones
Argument: mknight

驗證引數數量

除了 $@ 變數代表引數陣列外,$# 變數也很有用,它代表傳入指令碼或函式的引數數量。當需要特定數量引數時,我們需要確保使用者輸入正確。以下程式碼展示了這種做法:

if [ "$#" -ne 2 ]; then
    echo "Usage: $0 <arg1> <arg2>"
    exit 1
fi

這段程式碼說明如下:

  • 這段程式碼檢查傳入引數是否不等於2。如果測試結果為真,則列印幫助資訊並離開。變數 $0 代表指令碼名稱。

引數預設值

你可以為函式引數設定預設值以防未提供值時呼叫函式。以下是一個例子:

greet() {
    local name=${1:-"Guest"}
    echo "Hello, $name!"
}
greet
greet "John"

內容解密:

  • 函式 greet():定義了一個名為 greet() 的函式。
  • local name=${1:-"Guest"}:在函式內部宣告了一個名為 name 的區域性變數,並將其值設為第一個引數 $1。若未提供第一個引數,$1 的預設值為 "Guest"
    • local:宣告區域性變數,僅在當前函式中有效。
    • name:變數名稱。
    • ${1:-“Guest”}:預設值運算元 -: 用來指定當 $1 未提供時 name 的預設值為 "Guest"
    • “Guest”:預設值。
  • echo "Hello, $name!": 用於列印問候語與 name 變數。
  • 呼叫兩次函式 greet() 一次不傳遞引數(使用預設值),另一次傳遞 引數 John 。

輸出將是:

Hello, Guest!
Hello, John!

透過為函式變數設定預設值,你可以編寫更少的程式碼來覆寫未傳遞任何引數到你的函式的情景。