文章目录
术语定义
空白符
一个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; doneuntil list-1; do list-2; done
while先执行list-1命令,如果返回值为0,则执行list-2命令。如果list-1命令返回值为非0,则退出循环。
unitl与while语句类似,不同之处在于list-1命令返回值为0时退出循环。
注意:
- 以上复合语句中要求的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
- 语句中的空格和分号可以使用一个或多个换行符替代(命令与参数之间的空格、表达式中间的空格除外)。
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之一。