複製鏈接
請複製以下鏈接發送給好友

Bash

鎖定
BashUnix shell的一種,在1987年由布萊恩·福克斯為了GNU計劃而編寫。1989年發佈第一個正式版本,原先是計劃用在GNU操作系統上,但能運行於大多數類Unix系統的操作系統之上,包括LinuxMac OS X v10.4都將它作為默認shell。
Bash是Bourne shell的後繼兼容版本與開放源代碼版本,它的名稱來自Bourne shell(sh)的一個雙關語Bourne again / born again):Bourne-Again SHell。
Bash是一個命令處理器,通常運行於文本窗口中,並能執行用户直接輸入的命令。Bash還能從文件中讀取命令,這樣的文件稱為腳本。和其他Unix shell 一樣,它支持文件名替換(通配符匹配)、管道、here文檔、命令替換、變量,以及條件判斷和循環遍歷的結構控制語句。包括關鍵字、語法在內的基本特性全部是從sh借鑑過來的。其他特性,例如歷史命令,是從cshksh借鑑而來。總的來説,Bash雖然是一個滿足POSIX規範的shell,但有很多擴展。
一個名為Shellshock安全漏洞在2014年9月初被發現,並迅速導致互聯網上的一系列攻擊。這個漏洞可追溯到1989年發佈的1.03版本。
中文名
重擊
外文名
bash
命令語法
Bourne shell命令語法的超集
全    稱
Bourne-Again Shell
類    別
計算機程序

Bash概念

Bash (GNU Bourne-Again Shell) 是許多Linux發行版的默認Shell [1]  。事實上,還有許多傳統UNIX上用的Shell,例如tcsh、csh、ash、bshksh等等,Shell Script大致都類同,當您學會一種Shell以後,其它的Shell會很快就上手,大多數的時候,一個Shell Script通常可以在很多種Shell上使用。
bash是大多數Linux系統以及Mac OS X默認的shell,它能運行於大多數類Unix風格的操作系統之上,甚至被移植到了Microsoft Windows上的Cygwin系統中,以實現Windows的POSIX虛擬接口。此外,它也被DJGPP項目移植到了MS-DOS上。
bash的命令語法是Bourne shell命令語法的超集。數量龐大的Bourne shell腳本大多不經修改即可以在bash中執行,只有使用了Bourne的特殊變量或內置命令的腳本才需要修改。 bash的命令語法很多來自Korn shell (ksh) 和 C shell (csh), 例如命令行編輯,命令歷史,目錄棧,$RANDOM 和 $PPID 變量,以及POSIX的命令置換語法: $(...)。作為一個交互式的shell,按下TAB鍵即可自動補全已部分輸入的程序名、文件名、變量名等等。 [2] 

Bash歷史

由於理查德·斯托曼對於之前一位開發者的進度不滿,布萊恩·福克斯從1988年1月10日開始開發Bash。斯托曼和自由軟件基金會希望到一個能夠運行已有的shell腳本的自由軟件。他們把這看作是建成一個基於BSDGNU的完全自由的操作系統的戰略的重要部分。這是他們自己注資的幾個項目之一。福克斯作為自由軟件基金會的僱員承擔了這項工作。1989年6月8日,福克斯發佈了Bash的beta版本,版本號為.99。在福克斯1992年中期到1994年中期的某個時候離開自由軟件基金會之前,他一直擔任Bash的主要維護者。之後,他的工作被傳遞給另一個早期貢獻者,切特·雷米(Chet Ramey)。
從那時起,在Linux用户當中sh在很大度上成為了最流行的shell,併成為許多Linux發行版默認的交互式shell(不過Almquist shell可能是默認的腳本shell)。在蘋果公司OS X 操作系統上也是如此。Bash 也被移植到 Microsoft Windows(通過CygwinMinGW)。通過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子進程的方式有關 [3] 

Bash語法與特性

bash的命令語法是Bourne shell命令語法的超集。數量龐大的Bourne shell腳本大多不經修改即可以在bash中執行,只有那些引用了Bourne特殊變量或使用了Bourne的內置命令的腳本才需要修改。bash的命令語法很多來自Korn shellksh)和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的不足做了很多擴展。其中的一些列舉在這裏。

Bash花括號擴展

花括號擴展是一個從C shell借鑑而來的特性,它產生一系列指定的字符串(按照原先從左到右的順序)。這些字符串不需要是已經存在的文件。
$ echo a{p,c,d,b}eape 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}.txtfile1.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

Bash使用整數

與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輸入輸出重定向

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進程內的正則表達式

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轉義字符

$'string'形式的字符串會被特殊處理。字符串會被展開成string,並像C語言那樣將反斜槓及緊跟的字符進行替換。反斜槓轉義序列的轉換方式如下:
轉義字符
轉義字符
擴展成...
\a
響鈴符
\b
退格符
\e
ANSI轉義符,等價於\033
\f
饋頁符
\n
換行符
\r
回車符
\t
水平製表符
\v
垂直製表符
\\
反斜槓
\'
單引號
\nnn
十進制值為nnn的8-bit字符(1-3位)
\xHH
十六進制值為HH的8-bit字符(1或2位)
\cx
control-X字符
擴展後的結果將被單引號包裹,就好像美元符號一直就不存在一樣。
雙引號包裹的字符串前若有一個美元符號($"...")將會使得字符串被翻譯成符合當前locale的語言。如果當前locale是C或者POSIX,美元符號會被忽略。如果字符串被翻譯並替換,替換後的字符串仍被雙引號包裹。

Bash關聯數組

Bash 4.0 開始支持關聯數組,通過類似AWK的方式,對於多維數組提供了偽支持。
$ declare -A a         # 聲明一個名為a的偽二位數組
$ i=1; j=2
$ a[$i,$j]=5           # 將鍵 "$i,$j" 與值 5 對應
$ echo ${a[$i,$j]}

Bash移植性

調用Bash時指定--posix或者在腳本中聲明set -o posix,可以使得Bash幾乎遵循 POSIX 1003.2 標準。若要保證一個Bash腳本的移植性,至少需要考慮到 Bourne shell,即Bash取代的shell。Bash有一些傳統的 Bourne shell 所沒有的特性,包括以下這些:
  • 某些擴展的調用選項
  • 命令替換(即$())(儘管這是 POSIX 1003.2 標準的一部分)
  • 花括號擴展
  • 某些數組操作、關聯數組
  • 擴展的雙層方括號判斷語句
  • 某些字符串生成操作
  • 進程替換
  • 正則表達式匹配符
  • Bash特有的內置工具
  • 協進程

Bash鍵盤快捷鍵

Bash默認使用Emacs的快捷鍵,可以通過set -o vi讓它使用Vi的快捷鍵

Bash進程管理

Bash有兩種執行命令的模式:批處理模式、併發模式。 要以批處理模式執行命令(即按照順序),必須用;分隔
command1 ; command2
在這個例子中,當command1執行完畢,即執行command2要併發執行兩個命令,它們必須用&分隔
command1 & command2
在這種情況下,command1在後台執行(通過&),從而立即將控制返回到shell,以執行command2
總結:
  • 一般命令在前台執行(fg),執行完畢後,控制返回給用户。
  • 在命令後面加上&,它會在後台執行(bg),並將特殊的環境變量$!設置為該任務的進程ID。這時shell可以併發執行其他命令。
  • 按Ctrl+z可以掛起前台運行的程序
  • 掛起的程序可以用fg恢復到前台,或者用bg恢復到後台
  • 後台程序試圖寫入數據到終端設備時(與寫入標準輸出不同)可能被阻塞。
  • shell可以等待一個後台任務執行完成,只需使用wait命令,加上進程ID或者任務序號;也可以等待所有的後台任務,只需使用不加參數的wait

Bash管道

管道用於將一系列命令聯繫起來,也就是將一個命令的輸出通過一個無形的"管道"作為另外一個命令的輸人。管道命令是"|",例如: [4] 
[root@echo root]#cat dir.outlgrep"test"lwc-
[root@echo root]#cat dir.out | grep"test" | wc-
管道將cat命令的輸出(列出dirout文件的內容)送給grep命令,grep在輸入裏查找單詞testgrep的輸出則是所有包含單詞test的行這個輸出又被送給wc命令wc命令統計出輸人的行數。 [4] 

Bash啓動腳本

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腳本比較

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      

BashBash 啓動腳本與操作系統相關的問題

一些 Unix 和 Linux 版本常在/etc放置 Bash 的系統級啓動腳本。Bash在其標準的初始化過程中執行它們,不過其他啓動腳本可以按照不同於Bash啓動序列文檔所述的順序來讀取這些文件。root 用户的文件默認內容,以及新用户被創建時系統提供的默認文件可能有問題。啓動X窗口系統的啓動腳本可能使用用户的 Bash 啓動腳本嘗試在窗口管理器啓動之前設置用户的環境變量。這些問題常常可以通過使用~/.xsession或者~/.xprofile來讀取~/.profile而解決。
參考資料