分类:
2009-05-03 21:42:56
对Linux不是太陌生的读者都应该对Shell有一定的了解,就是这个程序在我们登陆后自动执行,打印出一个$符号,然后等待我们输入命令。Linux下最常用的Shell应用程序是Bash,绝大部分Linux发行版默认安装的都是它。下面我们也来亲手编写一个Shell程序,这个Shell远远不如Bash复杂,但也能满足我们一般的使用,下面,我们就开始。
首先,给这个Shell取一个名字,不妨就叫做Mini Shell。
Linux系统的命令分为内部命令和外部命令两种,内部命令由Shell程序实现,如cd、echo等,Linux的内部命令数量有限,而且绝大部分都很少用到。而每一个Linux外部命令都是一个单独的应用程序,我们非常熟悉的ls、cp等绝大多数命令都是外部命令,这些命令都以可执行文件的形式存在,绝大部分放在目录/bin和/sbin中。这样一来,我们编程的难度就可以大大下降了,我们只需要实现很有限的内部命令,对于其它的输入,统统当作应用程序来执行即可。
为了简单明了起见,Mini Shell只实现了2个内部命令:
1、cd 用于切换目录,和我们熟悉的命令cd类似,除了没有那么多的附加功能。
2、quit 用于退出Mini Shell。
下面是程序清单:
1: /* mshell.c */ 2: #include |
这个程序稍稍有点长,我们来对它作一下详细的解释:
函数main:
14行:定义字符串cmd,用于接收用户输入的命令行。
15行:定义指针数组cmd_arg,它的形式和作用都与我们熟悉的char *argv[]一样。
从以上2个定义可以看出Mini Shell对命令输入的2个限制:首先,用户输入的命令行必须在255个字符之内(除去字符串结束标志'\0');其次,命令行的参数个数不得超过10个(包括命令本身在内)。
18行:进入一个do-while循环,这个循环是本程序的主体部分,基本思想是"等待输入命令--处理已输入命令--等待输入命令"。
22行:打印输入提示信息。在Mini Shell中,你可以随意定自己喜欢的命令输入提示信息,本程序中使用了"-=Mini Shell=-*| ",是不是有点像一个CS高手?如果不喜欢,你可以用任意的字符替换它。
23行:接收用户输入。
25-27行:fgets接受输入时,会将输入字符串时末尾的换行符("\n")一起接受,这是我们不需要的,所以要把它去掉。本程序中简单的用字符串结束标志'\0'覆盖了字符串cmd的最后一个字符来实现这个目的。
30行:初始化指针数组cmd_arg。
32-42行:对输入进行分析,将cmd中参数间的空格用'\0'填充,并把各参数的起始地址分别赋与cmd_arg数组。这样就把cmd分解成了cmd_arg,但分解后的各命令参数仍然使用着cmd的内存空间,所以在命令执行结束前不宜对cmd另外赋值。
45行:如果还未分析到输入字符串的末尾(i
51-52行:内部命令quit:字符串cmd_arg[0]就是命令本身,如果命令是quit,则退出循环,也就等于退出该程序。
55-58行:内部命令cd:调用函数do_cd()完成cd命令的动作。
61行:对于其它的外部命令和应用程序,调用函数execute_new()执行。
函数do_cd:
68行:仅仅考虑紧跟在命令后面的参数argv[1],而不再考虑其它的参数。如果这个参数存在,就把它作为要转换的目录。
69行:调用系统调用chdir切换当前目录,参见附录1。
70-82行:对chdir可能出现的错误进行处理。
函数execute_new:
92行:调用系统调用fork产生新的子进程。
93行:如果返回负值,说明fork调用出现错误。
96行:如果返回0,说明当前进程是子进程。
97行:调用execvp执行新的应用程序,并检测调用是否出错(返回负值)。这里使用execvp的原因是它可以自动在各默认目录里寻找目标应用程序的位置,而不必我们自己编程实现。
98-107行:对execvp可能出现的错误进程处理。
108行:如果execvp的执行出现错误,子进程在这里终止。表面上看起来,这个exit是接着97行的错误判断的下一行语句,而非if语句的一部分,似乎无论调用execvp成功与否都会接着执行exit。但事实上,如果execvp调用成功的话,这个进程将会被新的程序代码填充,因而根本不可能执行到这一行。反之,如果执行到了这一行,说明前面的execvp调用一定出现了错误。这样的效果和exit被包含在if语句中的效果是完全一样的。
109行:如果fork返回其它值,说明当前进程是父进程。
110行:调用系统调用wait。wait在这里有两个作用: