shell命令解释器心得

时间:2022-05-28 14:24:50

经过了为期一周的shell命令解释器项目,基本达到了预期的要求。回顾这一周的写程序过程,可以按时间来总结一下这次项目。shell命令解释器心得

第一天是写一个简单的shell解释器。这次的项目完全是用c语言编写的,而自身暴露出的问题就是c语言的精华—指针。由于c中是大量的对字符串操作,所以指针起着很关键的作用。首先这个程序需要是一个死循环,然后打印出命令提示符。取得的命令放在定义好的数组里面,可以使用getc(),fgets(),还有scanf()。但是是有区别的。fgets()函数在输入命令最后会带有’\n’,所以在输入完命令后要把’\n’换成’\0’;而scanf()函数中间如果输入了’ ’ 就会当成换行符,所以不建议采用scanf()。解决完输入命令后,就可以去执行命令了。我们需要再创建一个子进程,在子进程中使用execvp去执行命令,父进程调用waitpid()函数等待子进程的结束,然后进入下一次循环。这个简单的shell解释器弄好后,就可以往上添加功能了。下午的任务是使解释器可以执行带参数的命令。在提示符中,这次要求包含当前工作的路径,可以使用getcwd()函数,然后把整体的提示符放在定义好的字符指针中。接下来我们把命令按’ ’字符进行切割,可以使用strtok()函数(这个函数很好用,这次的项目才发现)。分割好的命令放在字符指针数组里面。在子进程中去执行。在父进程中需要判断一下是在前台运行还是在后台运行,决定是否调用waitpid()函数。

第二天上午是添加内部命令cd,exit。在分析完命令后,判断输入的命令arg[0] 是否为cd或者是exit,如果是的话,在创建子进程去执行这个命令之前就作为内部命令去执行。cd命令用到chdir()函数,来改变当前工作目录。下午的任务首先是引入文件的概念。把公共的变量和函数放在头文件里,注意防止头文件被重复包含,而且头文件最好放当前路径下,这样的可移植性。接下来添加重定向功能和管道功能。用一个循环来判断arg[1],当采样到arg[1]==”>>”或者arg[1]==”|”的时候去执行。有一个说明,循环里需要判断一下arg[j]是否为空,如果为空则break;这句话是必须加的,因为如果刚开始如果输入的不是命令,而是回车,arg[1]就为null,导致比较符号时就会出现段错误。例如执行ls >> test命令后,ls 的输出放在文件test中,如果test文件不存在,则创建。重定向首先打开文件,fd=open(dest,O_RDWR|O_CREAT|O_APPEND)。起初没有加上O_APPEND,程序出现了问题,当向已建立好的文件重定向的时候,重定向失败,所以一定要添加上此语句。打开成功后,使用重定向函数dup2()。dup2(fd,1),使输出的文件描述符指向文件的文件描述符。添加管道功能。例如:ls | wc ,把ls的输出作为wc的输入去执行。我们需要用管道函数pipe()。在创建完管道后,我们需要再创建一个子进程,通过两个进程之间完成管道传递内容。当发现有”|”符号时,需要把此符号处赋值位NULL。在子进程中执行管道文件的前端,父进程等待子进程的退出,然后去执行后端命令。

第三天上午引入环境变量功能。首先创建一个环境变量配置文件,里面内容是一行PATH环境变量PATH=/bin:/sbin:/usr/bin:/usr/sbin,然后调用自己写的函数environ去进行读取环境变量,查找文件。这个函数的实现首先用到open来打开配置文件,然后读取内容放到数组中,需要说明的是一般从文件读内容,最后会有个’\n’,所以要把它处理成’\0’。读到数组后,按”:”进行分割,分割后的内容同样存放到指针数组里,连接命令arg[0]存放到str数组中,调用access()函数去判断文件是否存在。返回相应的值。

第四天添加历史记录功能。完成历史记录功能,我们就不能再使用fgets()函数了,因为我们需要一个字符一个字符输入。在输入之前,我们调用set_keypress()函数把回显关掉,然后打一个字符输出一个字符,并存入结构体数组中。当我们键入方向键上和下的时候,就进入历史记录功能。由于回显关闭,所以利用“障眼法”来把历史命令显示出来。关键是printf(“\r ”);printf(“\r[root@local>]”);首先”\r”是回到行首,然后空格把已有字符覆盖,再”\r”活到行首输出提示符。再一些细节的操作后,把我们存在结构体数组中的内容输出来。当我们键入回车的时候,跳出历史记录,并恢复回显功能。历史记录的难点是回显,以及输入后存储命令,一些条件判断语句一定要严谨,否则程序流程错误。

第五天上午主要是完善历史记录功能。下午,老师把jobs的实现大概讲了一下。这个功能的实现我感觉是相当的困难,因为对队列的操作其实就是数据链表的操作。数据链表的知识掌握的不是很好,以后要加强。数据链表的基本操作是创建,查询,添加和删除。在本功能中用到的是创建,添加和删除。创建比较简单,头尾结点指向好就可以。添加采取从末尾顺序添加,也比较容易。删除主要是当采样到ctrl_c键的时候,进行链表的删除。Bg命令的时候,关键语句kill(p->pid,SIGCONT);功能是向子进程发送信号。Fg命令同样有kill函数,但是在kill函数后面要有这样的关键语句:waitpid(p->pid,&status,WUNTRACED);等待子进程的退出,然后if(WIFSTOPPED(status))来判断由waitpid()返回的状态。

第六天是程序的优化和调试。有人说程序是调出来的,不错,在调试程序的过程中遇到很多问题。首先是程序的结构性,有些重复的操作要避免,语句简而精;其次是重要的地方要写注释,每一个函数,一个模块都要写注释。指针是c语言的精华,但是用他的时候一定要加以小心,以免弄错。在大的工程里,要用makefile来编译文件,这样做可以简化编译,大大的提高效率。

总结。这个项目基本上是用c来编写的,所以对c有了进一步的掌握,尤其是对字符串的一些处理,以及指针的运用都有了更好的提高也为接下来的c++打下了坚实的基础。