Bash

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）：Bourne-Again SHell。

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 的不足做了很多擴充套件。其中的一些列舉在這裡。

2.1 花括號擴充套件

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

$ echo a {p,c,d,b} e

ape ace ade abe

$ echo {a,b,c}{d,e,f}

ad ae af bd be bf cd ce cf

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

! /bin/sh
傳統的 shell 並不產生相同結果

echo a {p,c,d,b} e # a {p,c,d,b} e

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

ls .{jpg,jpeg,png} # 首先擴充套件為 .jpg .jpeg .png，然後解析萬用字元

echo *.{png,jp {e,} g} # echo 顯示擴充套件結果；花括號擴充套件可以巢狀。

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

$ echo {1..10}

1 2 3 4 5 6 7 8 9 10

$ echo file {1..4}.txt

file1.txt file2.txt file3.txt file4.txt

$ echo {a..e}

a b c d e

$ echo {1..10..3}

1 4 7 10

$ echo {a..j..3}

a d g j

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

$ start=1; end=10

$ echo {$start..$end} # 由於解析順序，無法得到想要的結果

{1..10}

$ eval echo {$start..$end} # 首先進行變數擴充套件的解析

1 2 3 4 5 6 7 8 9 10

2.2 使用整數

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

VAR=55 # 將整數 55 賦值給變數 VAR

((VAR = VAR + 1)) # 變數 VAR 加 1。注意這裡沒有 '$'

((++VAR)) # 另一種方法給 VAR 加 1。使用 C 語言風格的字首自增

((VAR++)) # 另一種方法給 VAR 加 1。使用 C 語言風格的字尾自增

echo $((VAR * 22)) # VAR 乘以 22 並將結果送入命令

echo $VAR * 22 # 同上，但為過時用法

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

if ((VAR == Y 3 + X 2))

then

echo Yes

fi

((Z> 23)) && echo Yes

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

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

2.3 輸入輸出重定向

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

command &> file

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

command

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

例子:

重定向標準輸出至檔案，寫資料，關閉檔案，重置標準輸出。

生成標準輸出（檔案描述符 1）的複製檔案描述符 6

exec 6>&1

開啟檔案 "test.data" 以供寫入

exec 1>test.data

產生一些內容

echo "data:data:data"

關閉檔案 "test.data"

exec 1>&-

使標準輸出指向 FD 6（重置標準輸出）

exec 1>&6

關閉 FD6

exec 6>&-

開啟及關閉檔案

開啟檔案 test.data 以供讀取

exec 6

抓取外部命令的輸出

執行 'find' 並且將結果存於 VAR
搜尋以 "h" 結尾的檔名

VAR=$(find . -name "*h")

2.4 程序內的正則表示式

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

string =~ regex

正則表示式語法同 regex (7) man page 所描述的一致。正則表示式匹配字串時上述命令的退出狀態為 0，不匹配為 1。正則表示式中用圓括號括起的子表示式可以訪問 shell 變數 BASH_REMATCH，如下：

if abcfoobarbletch =~ 'foo (bar) bl (.*)'

then

echo The regex matches!

echo $BASH_REMATCH -- outputs: foobarbletch

echo ${BASH_REMATCH1} -- outputs: bar

echo ${BASH_REMATCH2} -- outputs: etch

fi

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

2.5 跳脫字元

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

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

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

2.6 關聯陣列

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

$ declare -A a # 宣告一個名為 a 的偽二位陣列

$ i=1; j=2

$ a$i,$j=5 # 將鍵 "$i,$j" 與值 5 對應

$ echo ${a$i,$j}

2.7 移植性

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

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

2.8 鍵盤快捷鍵

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

2.9 程序管理

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

2.9.1 批處理模式

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

command1 ; command2

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

2.9.2 併發模式

使 command1 後臺執行，則單獨在結尾處使用 &，例子如下：

command1 &

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

command1 & command2

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

透過 可以將當前程序掛起（放置後臺並暫停執行），可透過 fg 命令恢復至前臺，也透過 bg 將掛起的程序後臺執行。

2.9.3 檢視程序狀態

所有程序的情況，我們可以透過 jobs 命令檢視，包含正在後臺執行和停止了的。

$ jobs

1- Running command1 &

2+ Stopped command2

上例的輸出結果中，中括號包圍的數字（如： "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。然後它會依次尋找～/.bashprofile，~/.bashlogin，和～/.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，也不會允許指定這些引數。

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

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

3.1.1 設定可繼承的環境變數

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

. ~/.profile

3.1.2 別名和函式

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

. ~/.bashrc

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

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

. ~/.bash_login

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

3.1.4 相容舊環境的 Bash 啟動指令碼示例

下面這個～/.bashprofile 的框架與 Bourne shell 相容，並且為～/.bashrc 和～/.bashlogin 提供類似於 csh 的語義。 -r 檔名 測試指定檔案是否存在，如果不存在，跳過 && 後面的部分

-r ~/.profile && . ~/.profile # 只使用 Bourne shell 的語法設定環境變數

if -n "$PS1" ; then # 判斷是否是互動式 shell

-r ~/.bashrc && . ~/.bashrc # 載入～/.bashrc（tty、prompt、函式設定等）

-r ~/.bashlogin && . ~/.bashlogin # 執行登入式 shell 登入時的任務

fi # if 塊的結束標誌

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

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