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 gainSH 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借鉴而来的特性,它产生一系列指定的字符串(按照原先从左到右的顺序)。这些字符串不需要是已经存在的文件。
$echo a{ p,c,d,b} eape ace ade abe$echo { a,b,c}{ d,e,f} ad ae af bd be bfcd 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} .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 与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 Yesfi (( Z >23 )) && echo Yes((...)) 命令支持下列比较操作符:'== ', '!= ', '> ', '< ', '>= ',和'<= '。
bash不能在自身进程内进行浮点数 运算。当前有这个能力的unix shell只有KornShell 和Z shell 。
bash拥有传统Bourne shell缺乏的I/O重定向语法。bash可以同时重定向标准输出和标准错误,这需要使用下面的语法:
这比等价的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 -u6 dtado echo " $dta " done # 关闭文件test.data exec 6 <& -抓取外部命令的输出
# 运行'find'并且将结果存于VAR # 搜索以"h"结尾的文件名 VAR = $( find . -name"*h" ) bash 3.0支持进程内的正则表达式 ,使用下面的语法:
正则表达式语法同regex(7) man page所描述的一致。正则表达式匹配字符串时上述命令的退出状态为0,不匹配为1。正则表达式中用圆括号括起的子表达式可以访问shell变量BASH_REMATCH ,如下:
if [[ abcfoobarbletch = ~'foo(bar)bl(.*)' ]] then echo The regex matches!echo $BASH_REMATCH -- outputs: foobarbletchecho ${ BASH_REMATCH [1] } -- outputs: barecho ${ BASH_REMATCH [2] } -- outputs: etchfi 使用这个语法的性能要比生成一个新的进程来运行grep 命令优越,因为正则表达式匹配在bash进程内完成。如果正则表达式或者字符串包括空格或者shell关键字 ,(诸如'* '或者'? '),就需要用引号包裹。Bash 4 开始的版本已经不需要这么做了。
$'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 4.0 开始支持关联数组,通过类似AWK 的方式,对于多维数组提供了伪支持。
$declare -A a# 声明一个名为a的伪二位数组 $i = 1 ; j = 2 $ a[ $i ,$j ]= 5 # 将键 "$i,$j" 与值 5 对应 $echo ${ a [ $i , $j ] } 调用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
命令查看,包含正在后台运行和停止了的。
$jobs [ 1 ] - Running command1& [ 2 ] + Stopped command2上例的输出结果中,中括号包围的数字(如:" [1]" 和 "[2]" )为job的ID号。加号(+)用于指定fg
和bg
命令的默认对象job。减号( - )用于指定,若当前的默认对象job退出后,下一个默认对象job是谁。"Running" 和 "Stopped" 表示进程状态。 最后一个字段为命令。[ 7]
总结:
一般命令在前台执行(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_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块的结束标志 一些 Unix 和 Linux 版本常在/etc
放置 Bash 的系统级启动脚本。Bash在其标准的初始化过程中执行它们,不过其他启动脚本可以按照不同于Bash启动序列文档所述的顺序来读取这些文件。root 用户的文件默认内容,以及新用户被创建时系统提供的默认文件可能有问题。启动X Window系统 的启动脚本可能使用用户的 Bash 启动脚本尝试在窗口管理器 启动之前设置用户的环境变量。这些问题常常可以通过使用~/.xsession
或者~/.xprofile
来读取~/.profile
而解决。