跳轉到內容

Bourne Shell 指令碼/模組化

來自華夏公益教科書,開放書籍,開放世界

如果你曾在 shell 之外的其他環境中進行過程式設計,你可能熟悉以下場景:你在編寫程式,愉快地在鍵盤上敲打,直到你注意到

  • 你必須重複之前鍵入的一些程式碼,因為你的程式必須在兩個不同的位置執行完全相同的操作;或者
  • 你的程式太了,已經無法理解了。

換句話說,你已經到了需要將程式分成可以作為獨立子程式執行並根據需要呼叫的模組的點。在這方面,在 Bourne Shell 中工作與在其他任何語言中工作沒有什麼不同。遲早你會發現自己寫了一個 shell 指令碼,它太長了,已經不實用。到了將你的指令碼分成模組的時候了。

命名函式

[編輯 | 編輯原始碼]

當然,將指令碼分成模組的簡單而明顯的方法是建立幾個不同的 shell 指令碼——只需幾個帶有可執行許可權的獨立文字檔案即可。但是,使用獨立的檔案並不總是最實用的解決方案。將指令碼分散到多個檔案可能會使維護變得困難。尤其是當你最終得到了一些 shell 指令碼,這些指令碼只有在從另一個特定 shell 指令碼中呼叫時才有意義。

特別是對於這種情況,Bourne Shell 包含命名函式的概念:將名稱與命令列表相關聯,並使用名稱作為命令來執行命令列表。它看起來是這樣的

name () command group
* 其中name 是一個文字字串
command group 是任何分組的命令列表(使用花括號或圓括號)。

此功能在整個 shell 中可用,並在多種情況下很有用。首先,你可以使用它將一個長 shell 指令碼分成多個模組。但其次,你可以使用它在你自己的環境中定義你自己的小宏,你不希望為其建立完整的指令碼。許多現代 shell 都包含一個用於此的內建命令,稱為alias,但老式的 shell(如原始的 Bourne Shell)沒有;你可以使用命名函式來實現相同的結果。

建立命名函式

[編輯 | 編輯原始碼]

具有簡單命令組的函式

[編輯 | 編輯原始碼]

讓我們從簡單地建立一個列印“Hello World!!”的函式開始。讓我們稱它為“hw”。它看起來是這樣的

Hello world 作為命名函式
hw() {
>  echo 'Hello World!!';
>}


我們可以在 shell 指令碼或互動式 shell 中使用完全相同的程式碼——上面的示例來自互動式 shell。關於這個示例,有幾點需要注意。首先,我們不需要單獨的關鍵字來定義函式,圓括號就可以了。對於 shell 來說,函式定義就像擴充套件的變數定義。它們是環境的一部分;你只需透過定義名稱和含義來設定它們。

第二點需要注意的是,一旦你越過了圓括號,所有正常的規則都適用於命令組。在我們的例子中,我們使用了一個帶花括號的命令組,因此我們需要在 echo 命令之後加一個分號。我們想要列印的字串包含感嘆號,因此我們必須對其進行引用(像往常一樣)。而且,我們允許在多行上拆分命令組,即使在互動模式下也是如此,這與普通命令一樣。

以下是使用新函式的方法

呼叫我們的函式

程式碼:

$ hw

輸出:

Hello World!!

在獨立程序中執行的函式

[編輯 | 編輯原始碼]

函式的定義採用一個命令組。任何命令組。包括使用圓括號而不是花括號的命令組。因此,如果我們願意,我們可以定義一個在自己的環境中作為子程序執行的函式。以下是 hello world 再次在子程序中執行

Hello world 作為命名函式
hw() ( echo 'Hello World!!' )


這次所有內容都放在一行上,以保持簡短,但與之前相同的規則適用。當然,相同的環境規則也適用,因此函式中定義的任何變數在函式結束時將不再可用。

帶引數的函式

[編輯 | 編輯原始碼]

如果你曾在其他程式語言中進行過程式設計,你就會知道,最有用的函式是那些帶引數的函式。換句話說,那些不總是嚴格地做同樣的事情,而是可以受呼叫函式時傳入的值影響的函式。因此,這裡有一個有趣的問題:我們可以將引數傳遞給函式嗎?我們可以像這樣建立定義嗎?

functionWithParams (ARG0, ARG1) { 使用 ARG0 和 ARG1 做一些事情 }


然後進行類似“functionWithParams(Hello, World)”的呼叫?好吧,答案很簡單:不行。圓括號只是為了讓 shell 知道前面的名稱是函式的名稱而不是變數的名稱,並沒有為引數留出空間。

或者說,更確切地說是上面的答案很簡單,而不是答案很簡單。你看,當你執行函式時,你實際上是在執行一個命令。對於 shell 來說,執行命名函式和執行“ls”之間幾乎沒有區別。它就像任何其他命令一樣。它可能無法擁有引數,但就像任何其他命令一樣,它當然可以擁有命令列引數。因此,我們可能無法像上面那樣定義帶引數的函式,但我們當然可以這樣做

帶命令列引數的函式

程式碼:

$ repeatOne () { echo $1; }
$ repeatOne 'Hello World!'

輸出:

Hello World!

你也可以使用環境中的任何其他變數。當然,這是你在互動式 shell 中從命令列呼叫函式時的不錯技巧。但在 shell 指令碼中呢?命令列引數的位置變數已經被 shell 指令碼的引數佔用,對吧?啊,等等! shell 中執行的每個命令(無論如何執行)都有自己的一套命令列引數!因此,不存在干擾,你可以使用相同的機制。例如,如果我們像這樣定義一個指令碼

function.sh:shell 指令碼中的函式
#!/bin/sh

myFunction() {
  echo $1
}

echo $1
myFunction
myFunction "Hello World"
echo $1


然後它按我們想要的方式執行

執行 function.sh 指令碼

程式碼:

$ . function.sh 'Goodbye World!!'

輸出:

Goodbye World!


Hello World

Goodbye World!

環境中的函式

[編輯 | 編輯原始碼]

我們之前已經提到過,但現在讓我們更深入地探討一下:函式到底是什麼?我們已經暗示過它們是命令列表的別名或宏,並且它們是環境的一部分。但函式到底是什麼

從 Shell 的角度來看,函式只是一個非常詳細的變數定義。它實際上就是:一個名稱(一個文字字串)與一個值(更多文字)相關聯,並且當使用該名稱時可以被該值替換。就像 Shell 變數一樣。我們也可以證明這一點:只需在互動式 Shell 中定義一個函式,然後執行“set”命令(列出當前環境中的所有變數定義)。您的函式將出現在列表中。

因為函式實際上是一種特殊的 Shell 變數定義,所以它們的行為與“普通”變數完全相同。

  • 函式透過列出其名稱、定義運算子和函式的值來定義。但函式使用不同的定義運算子:'()' 而不是 '='。這告訴 Shell 對函式新增一些特殊注意事項(例如,在使用函式時不需要 '$' 字元)。
  • 函式是環境的一部分。這意味著當從 Shell 發出命令時,函式也會被複制到傳遞給已發出命令的環境副本中。
  • 如果函式被標記為匯出,使用 'export' 命令,它們也可以傳遞給新的子程序。一些 Shell 需要對函式使用特殊的命令列引數才能進行 'export'(例如,bash 需要你執行 'export -f' 來匯出函式)。
  • 您可以使用 'unset' 命令刪除函式定義。

當然,當你使用它們時,函式的行為就像命令一樣(畢竟它們被擴充套件成一個命令列表)。我們已經看到你可以將命令列引數與函式一起使用,以及使用位置變數來匹配。但是你也可以重定向輸入和輸出到命令和從命令中,以及將命令管道在一起。


下一頁: 除錯和訊號處理 | 上一頁: 檔案和流
主頁: Bourne Shell 指令碼
華夏公益教科書