Bash

歷史

由於理察·斯托曼對於之前一位開發者的進度不滿，布萊恩·福克斯從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借鑑而來的特性，它產生一系列指定的字串（按照原先從左到右的順序）。這些字串不需要是已經存在的檔案。
$ 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和PEG檔案。
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

使用整數

與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只有Korn shell和Z shell。

輸入輸出重新導向

bash擁有傳統Bourne shell缺乏的I/O重新導向語法。bash可以同時重新導向標準輸出和標準錯誤，這需要使用下面的語法：
 command &> file
這比等價的Bourne shell語法"command > file 2>&1"來的簡單。2.05b版本以後，bash可以用下列語法重新導向標準輸入至字串（稱為here string）：
 command <<< "string to be read as standard input"
如果字串包括空格就需要用引號包裹字串。
例子:
重新導向標準輸出至檔案，寫資料，關閉檔案，重設標準輸出。
 # 生成标准输出（文件描述符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<test.data
 # 读文件直到文件尾
 while read -u 6 dta
 do
   echo "$dta"
 done
 # 关闭文件test.data
 exec 6<&-
抓取外部命令的輸出
  # 运行'find'并且将结果存于VAR
  # 搜索以"h"结尾的文件名
  VAR=$(find . -name "*h")

行程內的正規表示式

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_REMATCH[1]} -- outputs: bar
         echo ${BASH_REMATCH[2]} -- outputs: etch
 fi
使用這個語法的效能要比生成一個新的行程來執行grep命令優越，因為正規表示式匹配在bash行程內完成。如果正規表示式或者字串包括空格或者shell 關鍵字，（諸如'*'或者'?'），就需要用引號包裹。Bash 4 開始的版本已經不需要這麼做了。

關聯陣列

Bash 4.0 開始支援關聯陣列，通過類似AWK的方式，對於多維陣列提供了偽支援。
$ declare -A a         # 声明一个名为a的伪二位数组
$ i=1; j=2
$ a[$i,$j]=5           # 将键 "$i,$j" 与值 5 对应
$ echo ${a[$i,$j]}

鍵盤快速鍵

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

啟動指令碼

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保持相容性。
. ~/.profile

別名和函式

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

登入與登出時執行的命令

最初登入時，csh 執行 ~/.login ，可以執行一些只在登入時執行的操作，例如顯示系統負載、硬碟狀態、是否收到新郵件、在紀錄檔中記錄登入時間，等等。Bourne shell 可以在 ~/.profile 檔案中類比這種行為，但並沒有預先定義檔名。可以在 ~/.bash_profile 檔案的環境變數設定和函式定義的後面添加這樣一行：
. ~/.bash_login
與之類似，csh還有一個檔案 ~/.logout ，這個檔案只在登入式shell退出時執行。Bash與之對應的檔案是 ~/.bash_logout ，並且不需要專門的設定。在 Bourne shell 中，trap 這個內建工具可以實現類似的效果。

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

下面這個 ~/.bash_profile 的框架與 Bourne shell 相容，並且為 ~/.bashrc 和 ~/.bash_login 提供類似於 csh 的語意。[ -r 文件名] 測試指定檔案是否存在，如果不存在，跳過 && 後面的部分
[ -r ~/.profile ] && . ~/.profile             # 只使用Bourne shell的语法设置环境变量if [ -n "$PS1" ] ; then                       # 判断是否是交互式shell
   [ -r ~/.bashrc     ] && . ~/.bashrc        # 加载~/.bashrc（tty、prompt、函数设置等）
   [ -r ~/.bash_login ] && . ~/.bash_login    # 执行登录式shell登录时的任务fi                                            # if块的结束标志

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

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