术语定义

空白符
一个space或者tab

单词
被shell按一个单元进行处理的字符串

标识符
由字母、数字、下划线组成的单词

元字符
用于分隔单词的字符,包含:| & ; ( ) < > space tab

控制符
完成控制功能的符号,包含:|| & && ; ;; ( ) | |& 换行符

保留字

保留字是对shell有特殊意义的单词,包含:! case coproc do done elif else esac fi for function if in select then until while { } time [[ ]]

语法

简单命令

简单命令由一系列单词(由空白符分隔,包含命令和可选参数)和重定向操作组成,结束于控制符。简单命令的返回值是它的退出状态,如果命令由信号n结束,它的返回值是128+n。

如:
cd
echo hello
ls>/dev/null

管道

一个管道由一个或多个简单命令组成,两个命令之间由控制符||&分隔。管道的格式如下:
命令1 [ [||&] 命令2 …]

命令1的标准输出通过管道连接到命令2的标准输入,这个连接在所有重定向操作之前完成。如果使用|&,命令1的标准输出和标准错误都连接到命令2的标准输入,等同于2>&1 |操作。

管道的返回状态是最后一条命令的退出状态。

列表

一个列表由一个或多个管道组成,两个管道之间由控制符:;, &, &&, ||分隔,结束于;, &, 换行符

&&||优先级相等,;&优先级相等且低于前面两个。

一个或多个换行符可以代替分号用于分隔命令。

如果命令使用控制符&结束,shell将在后台执行该命令,shell不等待该命令执行完成,且返回状态为0。使用分号分隔的命令将顺序执行,shell依次等待每个命令完成,返回状态是最后一个被执行命令的退出状态。

&&||和C语言类似,视左边命令的执行结果来决定是否继续执行右边的命令。如:
命令1 && 命令2:命令2只有在命令1退出状态为0时才会执行。
命令1 || 命令2:命令2只有在命令1退出状态为非0时才会执行。
&&||列表返回状态是最后一个被执行命令的退出状态。

复合语句

复合语句是下列其中一种:

(列表)

列表命令将在子shell环境中运行,列表中定义的变量在列表执行完成后不会影响到上级shell,返回状态是列表的退出状态。

{ 列表; }

列表命令在当前shell环境中运行,列表必须使用分号或者换行符结束,返回状态是列表的退出状态。不像元字符(){}是保留字,不能用于单词分隔,所以在花括号和列表之间必须使用其它元字符(注意:这里有空格)进行分隔。

((算术表达式))

按照算术运算规则对括号内的表达式进行计算。如果表达式的结果是非0值,则返回状态是0;如果表达式的结果是0,则返回状态是1。相当于:let "算术表达式"命令。

[[ 条件表达式 ]]

按照条件表达式规则对括号内的表达式进行运算,返回值为0或1。条件表达式不进行单词拆分和路径名扩展,进行波浪号扩展、参数和变量扩展、算术扩展、命令替换、进程替换和引用移除。

与列表复合命令类似,[]是保留字,它们和条件表达式之间必须使用元字符进行分隔(一般使用空格)。

当条件表达式使用==!=运算符时,运算符右边的字符串当做模式字符串进行匹配。当使用=~运算符时,运算符右边的字符串当做正则表达式进行匹配。

条件表达式可以使用下列运算符进行组合,优先级从高到低排列如下:
(表达式):返回表达式的值,可以改变运算顺序。
! 表达式:取反表达式的值。
表达式 && 表达式:表达式结果与操作
表达式 || 表达式:表达式结果或操作

注意
该复合语句与[ 条件表达式 ]用法比较相似,但也有很大不同:
[ ]是一个普通命令(test),[[ ]]则是复合语句;
[ ]在两个表达式之间使用-a-o来表示与、或关系,[[ ]]则使用&&||表示;
[ ]不能使用===~,所以不能使用模式匹配和正则表达式匹配;

for…in语句

语法如下:
for name [ [ in [ word … ] ] ; ] do list ; done

先扩展关键字in后面的word生成单词列表,并将列表中的单词逐一赋值给name变量,每完成一次赋值操作就执行一次list命令。如果省略in word,相当于执行in [email protected]

for语句返回状态为最后一条被执行命令的退出状态,如果in word扩展后的单词列表为空,不会执行任何命令,且返回状态为0。

for语句

语法如下:
for (( expr1 ; expr2 ; expr3 )) ; do list ; done

括号内的三个表达式均按照算术表达式规则进行运算,其它规则与C语言for语句相同。先计算expr1的值,再计算expr2的值,如果expr2返回非0值,则执行list命令并计算expr3,然后再次计算expr2,直到expr2返回0结束语句。如果省略任何一个表达式,该字段的值为1。

for语句返回状态为list列表中最后一条被执行命令的退出状态,如果存在任何一个无效表达式,返回状态为false。

select语句

语法如下:
select name [ in word ] ; do list ; done

先扩展关键字in后面的word生成单词列表,并将每个单词打印到标准错误,每个单词会打印一个数字前缀。如果省略in word,相当于执行in [email protected]。然后打印PS3提示符,并读取一行用户输入。如果输入包含以上数字前缀,则name变量赋值为数字对应的单词。如果输入为空,则重新打印和读取输入。如果输入EOF,则命令完成。输入为其它值时name变量赋值为空。

每次输入之后都会执行list命令,直到执行了break命令。select语句返回状态为list列表中最后一条被执行命令的退出状态,如果没有命令被执行,返回状态为0。

case语句

语法如下:
case word in [ [(] pattern [ | pattern ] … ) list ;; ] … esac

case命令先扩展word,再使用路径名扩展规则与pattern进行逐一匹配。word使用波浪号扩展、参数和变量扩展、算术扩展、命令替换、进程替换和引用移除。每个pattern使用波浪号扩展、参数和变量扩展、算术扩展、命令替换、进程替换。

当word与某一pattern匹配时,则执行对应的list命令。如果使用;;操作符,则不再进行后续匹配。如果使用;&操作符,则会执行紧跟着的下一个pattern的list命令(不进行匹配,直接执行)。如果使用;;&操作符,则会继续匹配后面的pattern,并在匹配成功时执行对应的list命令。

如果没有任何pattern匹配上,则case语句返回状态为0。否则返回状态为list列表中最后一条被执行命令的退出状态。

if语句

语法如下:
if list; then list; [ elif list; then list; ] … [ else list; ] fi

语法与C语言if语句相同,if语句返回状态为list列表中最后一条被执行命令的退出状态。如果不满足任何条件,返回状态为0。

while语句和until语句

语法如下:
while list-1; do list-2; done
until list-1; do list-2; done

while先执行list-1命令,如果返回值为0,则执行list-2命令。如果list-1命令返回值为非0,则退出循环。
unitl与while语句类似,不同之处在于list-1命令返回值为0时退出循环。

注意:
  1. 以上复合语句中要求的list命令,既可以使用列表命令,也可以使用复合语句,还可以是它们的组合。

then后直接跟list命令:
if [ -d /etc ]; then ls /etc; fi
也可以跟复合语句:
if [ -d /etc ]; then { ls /etc; } ; fi
也可以是它们的组合:
if [ -d /etc ]; then [[ -f /bin/ls ]] && /bin/ls /etc; fi

  1. 语句中的空格和分号可以使用一个或多个换行符替代(命令与参数之间的空格、表达式中间的空格除外)。

if [ -d /etc ]; then ls /etc; fi
也可以写成:
if
[ -d /etc ]
then
ls /etc
fi
还可以写成:
if [ -d /etc ]; then
ls /etc
fi

函数定义

语法如下:
name () 复合语句 [重定向]
function name [()] 复合语句 [重定向]

省略function关键字时,不能省略name后的括号。带function关键字时,可以省略括号。函数体只能使用复合语句,通常使用{ list; }定义(包括但不限于)。

函数定义以后可以像简单命令那样使用,执行函数时的返回状态为list列表中最后一条被执行命令的退出状态。

引用

引用用于移除某些字符或单词的特殊意义,将它们当做普通字符处理。
元字符和保留字都有自己的特殊意义,如果要当做普通字符处理,必须要引用起来。

有三种类型的引用机制:转义字符、单引号引用、双引号引用。

一个未引用的反斜杠\是转义字符,紧跟转义字符的下一个字符将保持它的字面意思。

在单引号引用中的每个字符将保持它的字面意思,单引号引用中不能再出现另外的单引号,使用转换字符做前缀也不行。

在双引号引用中的每个字符将保持它的字面意思,$`\!除外。
双引号中的转义字符\只有在后续字符为$`"\和换行符时才当做转义字符处理,双引号引用中可以包含转义后的双引号。

[email protected]:~# echo \;
;
[email protected]:~# echo “\;
\;
[email protected]:~# echo \$
$
[email protected]:~# echo “\$
$

$'string'格式的字符串将进行特殊处理,字符串中以转义字符开始的字符将按照ANSI C标准进行扩展,扩展的结果为单引号引用:
\a 响铃
\b 退格
\e
\E 一个转义字符
\f 换页
\n 换行
\r 回车
\t 水平tab
\v 垂直tab
\\ 反斜杠
\' 单引号
\" 双引号
\nnn 以8进制数nnn表示的字符
\xHH 以16进制数HH表示的字符
\uHHHH 以16进制数表示的Unicode字符
\UHHHHHHHH 以16进制数表示的Unicode字符
\cx 一个control+x字符

$"string"格式的字符串将按照当前locale进行转换,如果当前locale为C或POSIX,则忽略前置的$符号,转换的结果为双引号引用。

变量与参数

变量

与C语言类似,变量有变量名称和变量的值,除此之外,变量还有0个或多个属性。变量属性通过内建命令declare定义。

变量通过以下语法赋值:
name=[value]
如果省略value,将赋值一个空字符串,空字符串也是一个有效的值。使用赋值语句可以设置一个变量,变量一旦设置,只能通过内建命令unset删除。

value将经历波浪号扩展、变量与参数扩展、命令替换、算术扩展、引用移除。如果变量的属性为integer,value将当做算术表达式进行运算,就算不在$((...))中也是如此。value不进行单词拆分和路径名扩展。

+=运算符可以用于赋值语句,它将在变量原有值的基础上追加或加上新的值。如果+=用于integer属性的变量,新的值将按照算术运算规则加到原值上。如果+=用于字符串变量,新的值将追加到原字符串末尾。

位置参数

位置参数是一种特殊的变量,变量名由一个或多个数字组成,在shell调用时赋值,并且只能通过内建命令set重新赋值。当执行一个函数时,位置参数将被临时替换。

位置参数类似于C语言的argv数组,位置参数0对应argv[0],位置参数1对应argv[1],以此类推…

特殊参数

以下特殊参数只能使用,不能对它们进行赋值:

*
扩展为从1开始的位置参数。如果不在双引号中,每个参数扩展为独立的单词,内容扩展完成之后,将进行单词拆分和路径名扩展。如果在双引号中,将扩展为一个单词,两个位置参数之间使用IFS变量的第一个字符进行分隔。如果IFS未设置,使用空格分隔参数。

@
*参数类似,不同之处在于,该参数在双引号中也会扩展为独立的单词。

#
扩展为十进制的位置参数个数,类似于C语言的argc。

?
扩展为最后一条被执行命令的退出状态。

$
扩展为当前shell的PID。

!
扩展为最后一个后程任务的PID。

0
扩展为shell或shell脚本的名称。

扩展

扩展在命令行拆分为单词后进行,一共有7种类型的扩展:花括号扩展、波浪号扩展、参数和变量扩展、命令替换、算术扩展、单词拆分、路径名扩展。

扩展顺序为:花括号扩展;波浪号扩展、参数和变量扩展、算术扩展、命令替换(按顺序从左到右);单词拆分;路径名扩展。

只有花括号扩展、单词拆分和路径名扩展会改变单词数量,其它类型扩展由一个单词扩展为另一个单词。唯一例外的是"[email protected]“和”${name[@]}"扩展。

花括号扩展

花括号扩展用于产生字符串,它的机制类似于路径名扩展。花括号扩展的模式字符串由一个可选前缀、位于一对花括号中的由一系列逗号分隔的字符串或序列表达式、一个可选的后缀组成。前缀和后缀分别添加到花括号中每个字符串的前面和后面。

花括号扩展可以嵌套。每个扩展的字符串不会排序,从左到右依次扩展。例如:a{d,c,b}e扩展为ade ace abe

序列表达式格式为{x..y[..incr]},x和y可以是一个整数或单个字符,incr是一个可选的整型增量,表达式扩展为x到y之间的所有数字或字符。整型增量的默认值为1(x小于y)或-1(x大于y)。

波浪号扩展

如果一个单词由一个未引用的波浪号(~)开始,在第一个未引用的/之前的所有字符(如果没有/,则是这个单词的所有字符)被当做"波浪号-前缀","波浪号-前缀"中的字符串看做一个登录名。

如果登录名为空,波浪号扩展为环境变量HOME的值。如果HOME未设置,则用执行脚本的用户家目录替换。

如果登录名不为空,则"波浪号-前缀"替换为指定登录名的家目录。

参数和变量扩展

$字符用于参数和变量扩展、命令替换、算术扩展。参数和变量名可以放在一对花括号中,当变量名后面没有紧跟其它字符时,花括号是可选的。

  • ${变量}
    扩展为变量的值,当变量为位置参数且多于一个数字时,或者变量名后紧跟一个其它字符时,必须使用花括号。

命令替换

命令替换使用一个命令的输出进行替换,支持两种格式:$(命令)`命令`。BASH执行命令并使用命令的标准输出进行替换。

算术扩展

算术扩展计算算术表达式,并替换为算术表达式的计算结果,格式为:$((算术表达式))

单词拆分

bash扫描参数扩展、命令替换、算术扩展的结果,并使用IFS作为分隔符将结果拆分为多个单词,位于双引号中的字符串不进行拆分。

路径名扩展

在单词拆分完成后,bash扫描每个单词,看是否存在* ? [字符,如果存在以上字符,则该单词作为一个通配符,替换为与通配符匹配的按字母排序的文件名列表。

特殊模式字符意思如下:
*:匹配任意字符,包括空字符串。两个连续的**匹配所有文件和目录以及子目录。如果后面跟一个/**/只匹配目录和子目录。

?:匹配任意单个字符。

[...]:匹配方括号中的任意一个字符。一对由破折号分隔的字符是一个范围表达式,所有包含在范围之内的字符也会进行匹配。如果方括号中第一个字符为!^,则匹配不在方括号中的字符。

引用移除

上述扩展完成以后,所有不在引用中的\ ' "字符将被删除。

重定向

在一个命令执行之前,它的输入和输出可以被shell重定向。重定向允许命令文件描述符被复制、打开、关闭,指向不同的文件,可以修改命令读写的文件。重定向操作符可以出现在简单命令的任何位置。

在下面的描述中,如果省略了文件号,如果第一个字符是<,将重定向标准输入(文件号0)。如果第一个字符是>,将重定向标准输出(文件号1)。

重定向的顺序非常重要,例如:

ls > dirlist 2>&1

重定向标准输出和标准错误到文件dirlist,命令:

ls 2>&1 > dirlist

只重定向标准输出到文件dirlist,因为在重定向标准输出之前,已经将标准输出复制到标准错误。

输入重定向

重定向输入将打开word扩展后得到的文件名用于文件描述符n的读取,如果未指定n,则重定向标准输入。

输入重定向格式如下:
[n]<word

输出重定向

重定向输出将打开word扩展后得到的文件名用于文件描述符n的写入,如果未指定n,则重定向标准输出。如果文件不存在则创建一个,如果文件已存在则截断为0。

输出重定向格式如下:
[n]>word

追加输出重定向

与输出重定向不同之处在于如果文件已存在,则在文件末尾追加数据。

格式如下:
[n]>>word

标准输出和标准错误重定向

打开word扩展后得到的文件名同时用于标准输出和标准错误。

格式如下:
&>word>&word

第一种格式更好,相当于>word 2>&1。同样可以使用&>>word进行追加。

复制文件描述符

重定向操作[n]<&word用于复制输入文件描述符。如果word扩展结果为1个或多个数字,文件描述符n将由word复制产生。如果word扩展的数字未指定一个有效的用于输入的文件句柄,重定向将会出错。如果word为-,将关闭文件n。如果未指定n,则使用标准输入。

操作[n]>&word用于复制输出文件描述符。其它行为和复制输入文件描述符一样。

如果word扩展后不是一个数字,将打开或创建word指定的文件,再进行复制。

算术表达式

算术运算符按优先级从高到低排列如下:

id++ id--:变量后自加和后自减
++id --id:变量先自加和先自减
- +:一元加减
! ~:逻辑取反和位取反
**:乘方
* / %:乘、除、取模
+ -:加、减
<< >>:按位左移和右移
<= >= < >:比较
== !=:等于、不等于
&:位与
^:位异或
|:位或
&&:逻辑与
||:逻辑或
expr?expr:expr:条件运算符
= *= /= %= += -= <<= >>= &= ^= |=:赋值
expr1 , expr2:逗号运算符

条件表达式

  • -a file
    文件存在返回真
  • -b file
    文件存在并且是一个块设备文件返回真
  • -c file
    文件存在并且是一个字符设备文件返回真
  • -d file
    文件存在并且是一个目录返回真
  • -e file
    文件存在返回真
  • -f file
    文件存在并且是一个普通文件返回真
  • -h file
    文件存在并且是一个符号链接返回真
  • -k file
    文件存在并且sticky设置返回真
  • -p file
    文件存在并且是一个命名管道返回真
  • -r file
    文件存在并且可读返回真
  • -s file
    文件存在并且文件大小大于0返回真
  • -t fd
    文件句柄fd已经打开并且指向一个终端返回真
  • -u file
    文件存在并且已设置user id返回真
  • -g file
    文件存在并且已设置group id返回真
  • -w file
    文件存在并且可写返回真
  • -x file
    文件存在并且可执行返回真
  • -v varname
    变量已经设置返回真
  • -z string
    字符串长度为0返回真
  • -n string
    字符串长度不为0返回真
  • string1 == string2
  • string1 = string2
    字符串相等返回真
  • string1 != string2
    字符串不相等返回真
  • string1 < string2
    字符串1小于字符串2返回真
  • string1 > string2
    字符串1大于字符串2返回真
  • arg1 OP arg2
    OP是-eq, -ne, -lt, -le, -gt, or -ge之一。

BASH参考手册

相关文章: