2010年11月17日星期三

Bash系列:从命令行开始

Shell的精髓在于命令行
---无名氏语

先从几个简单的命令开始,认识Shell中命令
Shell代码
echo hello
echo -n hello
rm -f hello; touch hello
curl http://www.google.com/ | less



选项和参数
先看第一个,这个最简单了,它只是简单打印一个hello字符串。其中echo是命令部分,hello是参数部分。
第二个命令之比第一个多了一个-n,一般我们把-n叫做选项,这个选项告诉echo命令输出完hello之后不要擅作主张再输出个换行符。
到这里有必要说明下选项和参数,两者有什么联系和区别呢。
从以前的经验中我们也能感觉到,选项对命令的功能起到一定的定制作用,在命令基本功能的基础上加一点定制化的东西。比如上面的echo命令,它的基本功能就是把参数打印出来,-n这个选项定制了:不要输出默认情况下会输出的换行符。ls的基本功能是列出当前目录下的内容,-l选项定制:输出比较详细的信息。如果我自己写了一个程序myecho,它的功能是逐行输出给定的参数。C 代码如下:

#include

int main(int argc,char *argv[])
{
int i=0;
for(i=0; i abc &
echo是命令(它可能是磁盘的程序,也可能是shell内建的命令),-n和hello是参数,>是重定向描述符,&表示后台执行。
这条语句虽然包含的成分比较多,但它只有一个命令(可执行部分)。像这种只有一个可执行部分的语句叫做简单命令(Simple command),是Shell里的执行单元。

退出状态
退出状态是shell中一个很重要概念。 每个命令结束时都会返回给操作系统一个 退出状态码 (有时候也被称为 返回状态 ). 一般约定成功的命令返回0, 而不成功的命令返回非零值, 非零值通常都被解释成一个错误码. 行为良好的UNIX命令, 程序, 和工具都会返回0作为退出码来表示成功, 虽然偶尔也会有例外。之所以退出状态很重要,因为Shell是根据退出状态作为判断命令是否成功执行的依据,很多场合里,退出状态起到布尔值的作用,比如 if,while,for都用命令的退出状态。特殊变量?(没错,就是一个问号)里保存了上一个命令的退出状态,在命令行里,可以执行echo $?查看变量?的值。

前面说到简单命令,它的退出状态是这样的:如果命令执行完毕,自己退出,它的退出状态就是它返回给操作系统的退出状态。如果它是执行过程中被信号KILL掉,它的退出状态就是128+信号值。
例子:
[email protected]:~$ echo hello
hello
[email protected]:~$ echo $?
0 #这里的0就是echo的退出状态。
[email protected]:~$

再来个有信号的例子:



[email protected]:~$ sort
^C #这里按下ctrl+C
[email protected]:~$ echo $?
130 #ctrl+C是信号SIGINT,它的值是2,所以退出状态时128+2=130。可以用man 7 singal查看所有标准信号的值。


管道
管道可谓是Shell中最激动人心的特性,正因为有了管道,Shell才能把众多的Linux工具整合起来,完成强大的功能。
curl http://www.google.com/ | less 就是用了管道。显然,这不是一个简单命令,因为它有两个可执行部分:curl和less。简单的来说,管道的作用是把前一个命令的标准输出重定向到后一个命令的标准输入,也就是前一个命令的输入当成了后一个命令的输入。关于管道和重定向的细节,详见后文。

一般情况下管道的退出状态是最后一个执行的命令的退出状态。 (二般情况和bash的一个名叫pipefail的选项有关,这东西太少用,此处不再赘述)

命令列表
顾名思义,命令列表就是把命令写成一个列表(汗...),rm -f hello; touch hello就是命令列表,这里使用了分号作为命令分隔符。除了分号外,还可以使用||和&&。
rm -f hello || touch hello
rm -f hello && hello

三者的区别在于分号只是纯粹的分割,前面命令的执行效果不会对后面的命令产生影响。而||和&&具有布尔表达式的短路效果,还记得Java 里的判断吗
if (a&&b)... 这种情况下,如果a是false,那么表达式a&&b的值就是false,不会再测试b。
if (a||b)... 这种枪口下,如果a是true,那么表达死a||b的值就是true,不会再测试b。
和java里的判断类似,对于&&来说,只有第一个命令执行成功(退出状态是0),才会执行第二个命令,否则不执行第二个命令。||就刚好相反,如果第一个命令执行失败(退出状态非0),才会执行第二个命令,否则不会执行第二个命令。

列表的退出状态是列表中最后一个执行的命令的退出状态。对于a&&b,如果a的退出状态非0,b根本不会执行,a才是最后一个执行的命令,列表的退出状态就是a的退出状态。

复合命令
复合命令的形式比较多,控制结构也算复合结构。
(list): 在子Shell中执行命令列表(里面也可以是简单命令),简单的说,就是当前Shell会创建一个新的Shell进程,在新的Shell进程里执行 list里的命令。意义何在?如果修改了环境变量,可以保证当前环境变量不会对括号里的命令产生影响。
当然,也可以在括号里先设置一些环境变量,给里面的命令使用,而且不会对当前环境造成影响。

{list}:在当前环境执行,和普通的命令列表不同的是,这里的list构成一个“组”,可以简单的认为它们就是一个命令(当然,还是有顺序执行的)。
{ echo helo; echo world; }>abc 文件abc的内容有两行,分别是hello和world
echo helo; echo world; >abc abc的内容是空的

两者的退出状态都是其中的命令列表的退出状态。

关于{}还有一点要注意的是花括号{}和list直接要有空格,而且最后一个命令后面必须有分号; 圆括号()这无此限制。要解释这个,就要说说Bash的元字符(Metacharacter)和保留字(Reserved words)

元字符,保留字,引用
Shell中命令和参数使用空格(space)或制表符(tab)分隔的。这两中字符叫做"blank"
有些字符具有特殊的含义,用来分隔token,叫做元字符。下面都是元字符
| & ; ( ) < > space tab

Shell处理命令行的第一步就是用元字符把语句分隔成多个token,比如语句 curl http://www.google.com/ | less,Shell首先用元字符把它分成如下token: curl, http://www.google.com/, |和 less。后面讲到Shell如何处理语句时会详细解释,详见后文。

还有些字符具有特殊含义,执行控制功能,叫做控制字符,下面都是控制字符
|| & && ; ;; ( ) | |&
很明显,控制字符的作用就是控制。 比如|就有管道的作用, &具有使命令后台执行的功能。它们两个同时也是元字符,立场很不坚定。

每种语言都有保留字,在 java里,while,if等都是保留字,Shell当然也有自己的保留字,如下所示:
! case do done elif else esac fi for function if in select then until while { } time [[ ]]

保留字本身没甚么特别之处,只是程序语言说:“这些单词和符号我自己用,你们不准使用”,于是它们就成了保留字。


引用
这个概念出镜率非常高。
如果我想输出一个左圆括号(,直接执行 echo (是不行的,不信你可以试试,(在Shell里是特殊字符,这时候就需要引用"上场"了:echo '('或者echo "("都可以。
引用难道就是字符两边加上单引号或双引号?这个答案算部分正确,引用的目的是去除元字符和保留字的特殊意义,加引号只是手段罢了。关于引用的详细描述,以及单引号和双引号的区别。详见后文。

没有评论:

发表评论