Bash_(Unix_shell)

**Bash**，Unix shell 的一種，在 1987 年由布萊恩・福克斯為了 GNU 計劃而編寫。1989 年釋出第一個正式版本，原先是計劃用在 GNU 作業系統上，但能執行於大多數類 Unix 系統的作業系統之上，包括 Linux 與 Mac OS X v10.4 起至 macOS Mojave 都將它作為預設 shell，而自 macOS Catalina，預設 Shell 以 zsh 取代。

Bash 是 Bourne shell 的後繼相容版本與開放原始碼版本，它的名稱來自 Bourne shell（sh）的一個雙關語（_Bourne again_ / born again）：**B**ourne-**A**gain **SH**ell。

Bash 是一個命令處理器，通常執行於文字窗口中，並能執行使用者直接輸入的命令。Bash 還能從檔案中讀取命令，這樣的檔案稱為指令碼。和其他 Unix shell 一樣，它支援檔名替換（萬用字元匹配）、管道、here 文件、命令替換、變數，以及條件判斷和迴圈遍歷的結構控制語句。包括關鍵字、語法在內的基本特性全部是從 sh 借鑑過來的。其他特性，例如歷史命令，是從 csh 和 ksh 借鑑而來。總的來說，Bash 雖然是一個滿足 POSIX 規範的 shell，但有很多擴充。

一個名為 Shellshock 的安全漏洞在 2014 年 9 月初被發現，並迅速導致網際網路上的一系列攻擊。這個漏洞可追溯到 1989 年釋出的 1.03 版本。

## 歷史

由於理察・斯托曼對於之前一位開發者的進度不滿，布萊恩・福克斯從 1988 年 1 月 10 日開始開發 Bash。斯托曼和自由軟體基金會希望到一個能夠執行已有的 shell 指令碼的自由軟體。他們把這看作是建成一個基於 BSD 和 GNU 的完全自由的作業系統的戰略的重要部分。這是他們自己注資的幾個專案之一。福克斯作為自由軟體基金會的雇員承擔這項工作。1989 年 6 月 8 日，福克斯釋出 Bash 的 beta 版本，版本號為.99。在福克斯離開於 1992 年中期到 1994 年中期的某個時候離開自由軟體基金會之前，他一直擔任 Bash 的主要維護者。之後，他的工作被傳遞給另一個早期貢獻者，切特・雷米（Chet Ramey）。

從那時起，在 Linux 使用者當中 sh 在很大度上成為最流行的 shell，並成為許多 Linux 發行版預設的互動式 shell（不過 Almquist shell 可能是預設的指令碼 shell）。在蘋果公司的 OS X 作業系統上也是如此。Bash 也被移植到 Microsoft Windows（通過 Cygwin 和 MinGW）。通過 DJGPP 專案，Bash 被移植到了 DOS。通過許多終端類比軟體，Bash 被移植到 Novell NetWare 和 Android。微軟在 2016 年的 Build 大會上宣布，Windows 10 添加一個 Linux 子系統，完全支援 Bash 和其他 Ubuntu 下的二進位程式。

2014 年 9 月 24 日，Stephane Chazelas，一位工作於英國，致力於 Unix/Linux 和網路通信方面的專家，發現 Bash 的一個安全漏洞。這個漏洞被命名為 Shellshock，並被分配編號 CVE-2014-6271、CVE-2014-6277、CVE-2014-7169。這個漏洞非常嚴重，因為使用 Bash 的 CGI 指令碼會變得脆弱，使得攻擊者可以執行任意的代碼。這個漏洞與 Bash 通過環境變數把函式定義傳遞給 shell 子行程的方式有關。

## 語法與特性

bash 的命令語法是 Bourne shell 命令語法的超集。數量龐大的 Bourne shell 指令碼大多不經修改即可以在 bash 中執行，只有那些參照了 Bourne 特殊變數或使用了 Bourne 的內建命令的指令碼才需要修改。bash 的命令語法很多來自 Korn shell（ksh）和 C shell（csh），例如命令列編輯，命令歷史，目錄棧，$RANDOM 和 $PPID 變數，以及 POSIX 的命令置換語法：$(...)。作為一個互動式的 shell，按下 TAB 鍵即可自動補全已部分輸入的程式名，檔名，變數名等等。

使用 'function' 關鍵字時，Bash 的函式聲明與 Bourne/Korn/POSIX 指令碼不相容（Korn shell 有同樣的問題）。不過 Bash 也接受 Bourne/Korn/POSIX 的函式聲明語法。因為許多不同，Bash 指令碼很少能在 Bourne 或 Korn 直譯器中執行，除非編寫指令碼時刻意保持相容性。然而，隨著 Linux 的普及，這種方式正變得越來越少。不過在 POSIX 模式下，Bash 更加符合 POSIX。

bash 的語法針對 Bourne shell 的不足做了很多擴充。其中的一些列舉在這裡。

### 花括號擴充

花括號擴充是一個從 C shell 借鑑而來的特性，它產生一系列指定的字串（按照原先從左到右的順序）。這些字串不需要是已經存在的檔案。

花括號擴充不應該被用在可移植的 shell 指令碼中，因為 Bourne shell 產生的結果不同。

當花括號擴充和萬用字元一起使用時，花括號擴充首先被解析，然後正常解析萬用字元。因此，可以用這種方法獲得當前目錄的一系列 JPEG 和 PEG 檔案。

除了列舉備選項，還可以用「..」在花括號擴充中指定字元或數字範圍。較新的 Bash 版本接受一個整數作為第三個參數，指定增量。

當花括號擴充和變數擴充一起使用時，變數擴充解析於花括號擴充之後。有時有必要使用內建的 `eval` 函式

### 使用整數

與 Bourne shell 不同的是 bash 不用另外生成行程即能進行整數運算。bash 使用 ((...)) 命令和 $[...]變數語法來達到這個目的：

((...)) 命令可以用於條件語句，因為它的登出狀態是 0 或者非 0（大多數情況下是 1），可以用於是與非的條件判斷：

((...)) 命令支援下列比較運算子：'==', '!=', '>', '<', '>='，和'<='。

bash 不能在自身行程內進行浮點數運算。當前有這個能力的 unix shell 只有 Korn shell 和 Z shell。

### 輸入輸出重新導向

bash 擁有傳統 Bourne shell 缺乏的 I/O 重新導向語法。bash 可以同時重新導向標準輸出和標準錯誤，這需要使用下面的語法：

這比等價的 Bourne shell 語法 "command > file 2>&1" 來的簡單。2.05b 版本以後，bash 可以用下列語法重新導向標準輸入至字串（稱為 here string）：

如果字串包括空格就需要用引號包裹字串。

**例子**:
重新導向標準輸出至檔案，寫資料，關閉檔案，重設標準輸出。

打開及關閉檔案

抓取外部命令的輸出

### 行程內的正規表示式

bash 3.0 支援行程內的正規表示式，使用下面的語法：

正規表示式語法同 regex (7) man page 所描述的一致。正規表示式匹配字串時上述命令的登出狀態為 0，不匹配為 1。正規表示式中用圓括號括起的子表達式可以存取 shell 變數 BASH\_REMATCH，如下：

使用這個語法的效能要比生成一個新的行程來執行 grep 命令優越，因為正規表示式匹配在 bash 行程內完成。如果正規表示式或者字串包括空格或者 shell 關鍵字，（諸如'\*' 或者'?'），就需要用引號包裹。Bash 4 開始的版本已經不需要這麼做了。

### 跳脫字元

$'string' 形式的字串會被特殊處理。字串會被展開成 string，並像 C 語言那樣將反斜槓及緊跟的字元進行替換。反斜槓跳脫序列的轉換方式如下：

擴充後的結果將被單引號包裹，就好像美元符號一直就不存在一樣。

雙引號包裹的字串前若有一個美元符號（$"..."）將會使得字串被翻譯成符合當前 locale 的語言。如果當前 locale 是 C 或者 POSIX，美元符號會被忽略。如果字串被翻譯並替換，替換後的字串仍被雙引號包裹。

### 關聯陣列

Bash 4.0 開始支援關聯陣列，通過類似 AWK 的方式，對於多維陣列提供了偽支援。

### 移植性

呼叫 Bash 時指定 `--posix` 或者在指令碼中聲明 `set -o posix` ，可以使得 Bash 幾乎遵循 POSIX 1003.2 標準。若要保證一個 Bash 指令碼的移植性，至少需要考慮到 Bourne shell，即 Bash 取代的 shell。Bash 有一些傳統的 Bourne shell 所沒有的特性，包括以下這些：

* 某些擴充的呼叫選項
* 命令替換（即 `$()`）（儘管這是 POSIX 1003.2 標準的一部分）
* 花括號擴充
* 某些陣列操作、關聯陣列
* 擴充的雙層方括號判斷語句
* 某些字串生成操作
* 行程替換
* 正規表示式匹配符
* Bash 特有的內建工具
* 協行程

### 鍵盤快速鍵

Bash 預設使用 Emacs 的快速鍵，可以通過 `set -o vi` 讓它使用 Vi 的快速鍵

### 行程管理

Bash 有兩種執行命令的模式：批次處理模式、併發模式。

#### 批次處理模式

要以 批次處理模式 執行命令（即按照順序），必須用 `;` 分隔，例子如下：

在這個例子中，當 `command1` 執行完畢，即執行 `command2`。

#### 併發模式

使 `command1` 背景執行，則單獨在結尾處使用 `&`，例子如下：

要並行執行兩個命令，它們必須用 `&` 分隔，例子如下：

在這種情況下，`command1` 在後台執行（通過 `&`），從而立即將控制返回到 shell，以執行 `command2`。

通過 `Control`+`Z` 可以將當前行程掛起（放置後台並暫停執行），可通過 `fg` 命令恢復至前台，也通過 `bg` 將掛起的行程背景執行。

#### 檢視行程狀態

所有行程的情況，我們可以通過 `jobs` 命令檢視，包含正在背景執行和停止了的。

上例的輸出結果中，中括號包圍的數字（如： **"**[1]"和"[2]" ）為 job 的 ID 號。加號 (+) 用於指定 `fg` 和 `bg` 命令的預設物件 job。減號 (-) 用於指定，若當前的預設物件 job 登出後，下一個預設物件 job 是誰。"Running" 和 "Stopped" 表示行程狀態。 最後一個欄位為命令。

 
總結：

* 一般命令在前台執行（`fg`），執行完畢後，控制返回給使用者。
* 在命令後面加上 `&`，它會在後台執行（`bg`），並將特殊的環境變數 `$!` 設定為該任務的行程 ID。這時 shell 可以並行執行其他命令。
* 後台程式試圖寫入資料到終端裝置時（與寫入標準輸出不同）可能被阻塞。
* shell 可以等待一個後台任務執行完成，只需使用 `wait` 命令，加上行程 ID 或者任務序號；也可以等待所有的後台任務，只需使用不加參數的 `wait`

## 啟動指令碼

bash 啟動的時候會執行各種不同的指令碼。

當 bash 作為一個登入的互動 shell 被呼叫，或者作為非互動 shell 但帶有--login 參數被呼叫時，它首先讀入並執行檔案 /etc/profile。然後它會依次尋找~/.bash\_profile，~/.bash\_login，和~/.profile，讀入並執行第一個存在且可讀的檔案。--noprofile 參數可以阻止 bash 啟動時的這種行為。

當一個登入 shell 登出時，bash 讀取並執行~/.bash\_logout 檔案，如果此檔案存在。

當一個互動的非登入 shell 啟動後，bash 讀取並執行~/.bashrc 檔案。這個行為可以用--norc 參數阻止。--rcfile file 參數強制 bash 讀取並執行指定的 file 而不是預設的~/.bashrc。

如果用 sh 來呼叫 bash，bash 在啟動後進入 _posix_ 模式，它會儘可能模仿 sh 歷史版本的啟動行為，以便遵守 POSIX 標準。用 sh 名字呼叫的非互動 shell 不會去讀取其他啟動指令碼，--rcfile 參數無效。

當 bash 以 POSIX 模式啟動時（例如帶有--posix 參數）它使用 POSIX 標準來讀取啟動檔案。在此模式下，互動 shells 擴充變數 ENV，從以此為檔名的檔案中讀取命令並執行。

bash 會探測自己是不是被遠端 shell 守護程式執行（通常是 rshd）。如果是，它會讀取並執行~/.bashrc 中的命令。但是 rshd 一般不會用 rc 相關參數呼叫 shell，也不會允許指定這些參數。

### Bash 與 Bourne shell 和 csh 啟動指令碼的比較

Bash 的特性是從 Bourne shell 和 csh 發展而來，因此一定程度上允許同 Bourne shell 的啟動檔案分享，並提供一些 csh 使用者熟悉的啟動特性。

#### 設定可繼承的環境變數

Bourne shell 登陸時使用 `~/.profile` 來設定環境變數，這些環境變數可以被子行程繼承。Bash 可以以相容的方式使用 `~/.profile` ，只需在 Bash 自有的指令碼中顯式執行下面這行代碼。通過在 `~/.profile` 中避免使用 Bash 特有的語法，就可以和 Bourne shell 保持相容性。

#### 別名和函式

更通用的函式以及借鑑自 csh 的「別名 (alias)」很大程度上取代了 Bourne shell 的別名 (alias) 和函式。然而這兩個特性一般不能從登入式 shell 中繼承，在該登入式 shell 的子 shell 中，它們必須被重新定義。儘管有個環境變數 `ENV` 可以被用於這個問題，不過 csh 和 Bash 都可以用子 shell 的啟動指令碼直接處理。在 Bash 當中，`~/.bashrc` 是互動式子 shell 啟動時執行的指令碼。如果想要在登入式 shell 中使用 `~/.bashrc` 定義的函式，可以在 `~/.bash_login` 的環境變數後面加上這樣一行：

#### 登入與登出時執行的命令

最初登入時，csh 執行 `~/.login` ，可以執行一些只在登入時執行的操作，例如顯示系統負載、硬碟狀態、是否收到新郵件、在紀錄檔中記錄登入時間，等等。Bourne shell 可以在 `~/.profile` 檔案中類比這種行為，但並沒有預先定義檔名。可以在 `~/.bash_profile` 檔案的環境變數設定和函式定義的後面添加這樣一行：

與之類似，csh 還有一個檔案 `~/.logout` ，這個檔案只在登入式 shell 登出時執行。Bash 與之對應的檔案是 `~/.bash_logout` ，並且不需要專門的設定。在 Bourne shell 中，`trap` 這個內建工具可以實現類似的效果。

#### 相容舊環境的 Bash 啟動指令碼範例

下面這個 `~/.bash_profile` 的框架與 Bourne shell 相容，並且為 `~/.bashrc` 和 `~/.bash_login` 提供類似於 csh 的語意。`[ -r _文件名_]` 測試指定檔案是否存在，如果不存在，跳過 `&&` 後面的部分

### Bash 啟動指令碼與作業系統相關的問題

一些 Unix 和 Linux 版本常在 `/etc` 放置 Bash 的系統級啟動指令碼。Bash 在其標準的初始化過程中執行它們，不過其他啟動指令碼可以按照不同於 Bash 啟動序列文件所述的順序來讀取這些檔案。root 使用者的檔案預設內容，以及新使用者被建立時系統提供的預設檔案可能有問題。啟動 X Window 系統 的啟動指令碼可能使用使用者的 Bash 啟動指令碼嘗試在 視窗管理員 啟動之前設定使用者的環境變數。這些問題常常可以通過使用 `~/.xsession` 或者 `~/.xprofile` 來讀取 `~/.profile` 而解決。

## 參見

* Unix shell

## 註腳

## 外部連結

* GNU Bash 首頁
* GNU Bash 在 GNU 的首頁（頁面存檔備份，存於網際網路檔案館）
* bashdb（頁面存檔備份，存於網際網路檔案館），帶有除錯器的 Bash
* Bash 入門指南（頁面存檔備份，存於網際網路檔案館）（英文）
* Bash 同標準 unix shell 的不同（頁面存檔備份，存於網際網路檔案館）
* Bash by example（頁面存檔備份，存於網際網路檔案館） Bash 基礎編程