4.如果字符串不被单引号或双引号包围,则进行波浪符展开,即将~/展开为用户的主目录,将~+/展开为当前工作目录(PWD),将~-/展开为上一个工作目录(OLDPWD);
5.如果字符串不被单引号包围,则进行参数和变量展开;这一类的展开全都以$开头,这是整个Bash字符串展开中最复杂的,其中包括用户定义的变量,包括所有的环境变量,以上两种展开方式都是$后跟变量名,还包括位置变量$1、$2、...、$9、...,其它特殊变量:$@、$*、$#、$-、$!、$0、$?、$_,甚至还有数组:${var[i]},还可以在展开的过程中对字符串进行各种复杂的操作,如:${parameter:-word}、${parameter:=word}、${parameter:+word}、;${parameter:?word}、${parameter:offset}、${parameter:offset:length}、${!prefix*}、${!prefix@}、${name[@]}、${!name[*]}、${#parameter}、${parameter#word}、${parameter##word}、${parameter%word}、${parameter%%word}、${parameter/pattern/string}、${parameter^pattern}、${parameter^^pattern}、${parameter,pattern}、${parameter,,pattern};
10.以上流程全部完成后,最后去掉字符串外面的引号(如果有的话)。以上流程只按以上顺序进行一遍。比如不会在变量展开后再进行大括号展开,更不会在第10步去除引用后执行前面的任何一步。如果需要将流程再走一遍,请使用eval。
1.把它当命令执行;这是Bash中的最根本的用法,毕竟Shell的存在就是为了粘合各种命令。如果一个字符串出现在本该命令出现的地方(比如一行的开头,或者关键字then、do等的后面),它将会被当成命令执行,如果它不是个合法的命令,就会报错;
2.把它当成表达式;Bash中本没有表达式,但是有了((...))和[[...]],就有了表达式;((...))可以把它里面的字符串当成算术表达式,而[[...]]会把它里面的字符串当逻辑表达式,仅此两个特例;
3.给变量赋值;这也是一个特例,有点破坏Bash编程语言语法哲学的完整性。为什么这么说呢?因为=即不是一个元字符,也不允许两边有空格,而且只有第1个等号会被当成赋值运算符。
四、再加上一点点的定义,就可以推导出整个Bash脚本语言的语法了 前面我已经展示了我对字符串从哪里来、到哪里去这个问题的理解。关于字符串的去向,除了两个表达式和一个为变量赋值这三个特例,剩下的就只有当命令来执行了。在前面,我提到了元字符和引用的概念,这里,还得再增加一点点定义:
定义1:控制操作符(ControlOperator) 前面提到元字符是为了把一个字符串分割为多个子串,而控制操作符就是为了把一系列的字符串分割成多个命令。举例说明,在Bash中,一个字符串cat/etc/passwd就是一个命令,第一个单词cat是命令,第2个单词/etc/passwd是命令的参数,而字符串cat/etc/passwd|grepyouxia就是两个命令,这两个命令分别是cat和grep,它们之间通过|分割,所以这里的|是控制操作符。熟悉Shell的朋友肯定知道|代表的是管道,所以它的作用是1.把一个字符串分割为两个命令,2.将第一个命令的输出作为第二个命令的输入。在Bash中,总共只有10个控制操作符,它们分别是||&&&|;;;()|&<newline>。只要看到这些控制操作符,就可以认为它前面的字符串是一个完整的命令。
定义2:关键字(ReservedWords) 我没有将其翻译成保留字,很显然,作为编程语言来说,它们应该叫做关键字。一门编程语言肯定必须得提供选择、循环等流程控制语句,还得提供定义函数的功能。这些功能只能通过关键字实现。在Bash中,只有22个关键字,它们是!casecoprocdodoneelifelseesacfiforfunctionifinselectthenuntilwhile{}time[[]]。这其中有不少的特别之处,比如!{}[[]]等符号都是关键字,也就是说它们当关键字使用时相当于一个单词,也就是说它们和别的单词必须以元字符分开(否则无法成为独立的单词)。这也是为什么在Bash中使用!{}[[]]时经常要在它们周围留空格的原因。(再一次证明=是一个很变态的特例,因为它既不是元字符,也不是控制操作符,更加不是关键字,它到底是什么?)
下面开始推导Bash脚本语言的语法:
推导1:简单命令(Simplecommand) 就是一条简单的命令,它可以是一个以上述控制操作符结尾的字符串。比如单独放在一行的uname-r命令(单独放在一行的命令其实是以<newline>结尾,<newline>是控制操作符),或者虽然不单独放在一行,但是以;或&结尾,比如uname-r;who;pwd;gvim&其中每一个命令都是一个简单命令(当然,这四个命令放在一起的这行代码不叫简单命令),;就是简单地分割命令,而&还有让命令在后台执行的功能。这里比较特殊的是双分号;;,它只用在case语句中。
推导2:管道(PipeLine) 管道是Shell中的精髓,就是让前一个命令的输出成为后一个命令的输入。管道的完整语法是这样[time[-p]][!]command1|command2或这样[time[-p]][!]command1|&command2的。其中time关键字和!关键字都是可选的(使用[...]指出哪些部分是可选的),time关键字可以计算命令运行的时间,而!关键字是将命令的返回状态取反。看清楚!关键字周围的空格哦。如果使用|,就是把第一个命令的标准输出作为第二个命令的标准输入,如果使用|&,则将第一个命令的标准输出和标准错误输出都当成第二个命令的输入。
推导3:命令序列(List) 如果多个简单命令或多个管道放在一起,它们之间以;&<newline>||&&等控制操作符分开,就称之为一个命令序列。关于;&<newline>前面已经讲过了,无需重复。关于||和&&,熟悉C、C++、Java等编程语言的朋友们肯定也不会陌生,它们遵循同样的短路求值的思想。比如command1||command2只有当command1执行不成功的时候才执行command2,而command1&&command2只有当command1执行成功的时候才执行command2。
推导4:复合命令(CompoundCommands) 如果将前面的简单命令、管道或者命令序列以更复杂的方式组合在一起,就可以构成复合命令。在Bash中,有4种形式的复合命令,它们分别是(list)、{list;}、((expression))、[[expression]]。请注意第2种形式和第4种形式大括号和中括号周围的空格,也请注意第2种形式中list后面的;,不过如果}另起一行,则不需要;,因为<newline>和;是起同样作用的。在以上4种复合命令中,(list)是在一个新的Shell中执行命令序列,这些命令的执行不会影响当前Shell的环境变量,而{list;}只是简单地将命令序列分组。后面两种表达式求值前面已经讲过,这里就不讲了。后面可能会详细列出逻辑表达式求值的选项。
上面的4步推导是一步更进一步的,是由简单逐渐到复杂的,最简单的命令可以组合成稍复杂的管道,再组合成更复杂的命令序列,最后组成最复杂的复合命令。
下面是Bash脚本语言的流程控制语句,如下:
1.forname[[in[word...]];]dolist;done;
2.for((expr1;expr2;expr3));dolist;done;
3.selectname[inword];dolist;done;
4.casewordin[[(]pattern[|pattern]...)list;;]...esac;
5.iflist;thenlist;[eliflist;thenlist;]...[elselist;]fi;
6.whilelist-1;dolist-2;done;
7.untillist-1;dolist-2;done。
上面的公式大家看得懂吧,我相信大家肯定看得懂。其中的[...]代表的是可以有也可以真没有的部分。在以上公式中,请注意第2个公式for循环中的双括号,它执行的是其中的表达式的算术运算,这是和其它高级语言的for循环最像的,但是很遗憾,Bash中的算术表达式目前只能计算整数。再请注意第3个公式,select语法,和for...in...循环的语法比较类似,但是它可以在屏幕上显示一个菜单。如果我没有记错的话,Basic语言中应该有这个功能。其它的控制结构在别的高级语言中都很常见,就不需要我在这里啰嗦了。
最后,再来展示一下如何定义函数:
name()compound-command[redirection]
或者
functionname[()]compound-command[redirection]
可以看出,如果有function关键字,则()是可选的,如果没有function关键字,则()是必须的。这里需要特别指出的是:函数体只要求是compound-command,我前面总结过compound-command有四种形式,所以有时候定义一个函数并不会出现{}哦。如下图,这样的函数也是合法的:
That'sall。这就是Bash脚本语言的全部语法。就这么简单。
好像忘了点什么?对了,还有输入输出重定向没有讲。输入输出重定向是Shell中又一个伟大的发明,它的存在有着它独特的哲学意义。这个请看下一节。
五、输入输出重定向 Unix世界有一个伟大的哲学:一切皆是文件。(这个扯得有点远。)Unix世界还有一个伟大的哲学:创建进程比较方便。(这个扯得也有点远。)而且,每一个进程一创建,就会自动打开三个文件,它们分别是标准输入、标准输出、标准错误输出,普通情况下,它们连接到用户的控制台。在Shell中,使用数字来标识一个打开的文件,称为文件描述符,而且数字0、1、2分别代表标准输入、标准输出和标准错误输出。在Shell中,可以通过>、<将命令的输入、输出进行重定向。结合exec命令,可以非常方便地打开和关闭文件。需要注意的是,当文件描述符出现在>、<右边的时候,前面要使用&符号,这可能是为了和数学表达式中的大于和小于进行区别吧。使用&-可以关闭文件描述符。
><&数字exec-,这就是输入输出重定向的全部。下面的公式中,我使用n代表数字,如果是两个不同的数字,则使用n1、n2,使用[...]代表可选参数。输入输出重定向的语法如下:
复制代码