08_简单的命令解析器(小钱版)[操作系统][2012-12-15]


一。项目概述


1. 项目名称

SimpleCommandParser(简单的命令解析器)

2. 开发目的

熟悉Linux编程环境,加强对Linux命令的理解及函数的运用。

3. 开发环境

LSB Version:  :core-3.0-ia32
:core-3.0-noarch
:graphics-3.0-ia32
:graphics-3.0-noarch
Distributor ID:  RedHatEnterpriseAS
Description:    Red Hat Enterprise Linux AS release 4 (Nahant Update 4)
Codename:     NahantUpdate4
Compiler:      GNU GCC Compiler (mingw32-g++.exe)
Programming Language: C++

4. 文件说明

① 服务器路径:    /home/10111/project
② 程序测试目录:  /home/10111/project/LastTest/
③ 程序运行文件:  /home/10111/project/LastTest/RunProgram
④ 源程序文件夹:  /home/10111/project/SimpleCommandParser
⑤ 程序制作相关:  /home/10111/project/About

5. 项目功能 (已完成)

① 显示当前所在目录的路径名的指令:pwd
② 列出指定目录名中的所有目录及文件的指令:dir  <目录名>
③ 改变当前工作目录的指令:cd  <目录名或路径>
④ 显示当前日期的指令:date
⑤ 在指定的目录及其子目录中查找指定的文件的指令:
find <目录>  -name <待查找的文件名>
⑥ 重命名一个文件或目录的指令:
rename  <旧文件名1> <新文件名1> <旧文件名2> <新文件名2>…
⑦ 新建目录的指令:
newdir  <目录名1> <目录名2> …<目录名n>
⑧ 删除目录的指令
deldir   <目录名1> <目录名2> …<目录名n>
⑨ 退出命令解释程序的指令:exit

6.项目特点

① 强扩展性:可以方便地根据需求进行指令的增删修改。
② 强容错性:可以接受用户各种方式的错误指令输入。
③ 可维护性:项目模块间耦合性低,可以轻松维护项目。
④ 简单易用:各指令符合用户日常使用习惯。

7. 参考文献或书籍资料

① Unix技术网:http://man.chinaunix.net/
② 百度百科: http://baike.baidu.com/
③ GNU Operating System:http://www.gnu.org/



二。程序使用演示


1. 程序启动

08_简单的命令解析器(小钱版)[操作系统][2012-12-15]_第1张图片

如上图所示,程序启动后,用户能看见友好欢迎界面,欢迎界面上简要地列出了本程序所支持的指令及指令的基本用法。

2. 显示当前日期(date)指令演示

如上图所示,直接在程序中输入指令“date”,程序即可显示当前日期,如果在“date”指令后面(输入空格后)再输入多余的字符串,程序将会忽略后面的字符串,照常执行“date”指令。

3. 显示当前所在目录的路径名的(pwd)指令演示

如上图所示,直接在程序中输入指令“pwd”,程序即可显示当前工作目录路径,如果在“pwd”指令后面(输入空格后)再输入多余的字符串,程序将会忽略后面的字符串,照常执行“date”指令。

4.列出指定目录名中的所有目录及文件的指令( dir )指令演示

08_简单的命令解析器(小钱版)[操作系统][2012-12-15]_第2张图片

如上图所示,如需浏览某一目录内的内容,用户可先输入“dir”然后输入想要浏览的目录,输入的目录参数使用空格间开,按下回车后,程序即可列出当前工作目录中的所有文件(并显示出当前遍历的目录)。

08_简单的命令解析器(小钱版)[操作系统][2012-12-15]_第3张图片

如上图所示,如果在“dir”指令后不添加任何参数,程序将路径默认设为正在工作的目录。

5. 新建目录的指令 (newdir) 演示

08_简单的命令解析器(小钱版)[操作系统][2012-12-15]_第4张图片

如上图所示,如需新建某一目录时,用户可先输入“newdir”然后输入需创建的目录名,输入的目录参数使用空格间开,按下回车后,若跳到下一指令输入语句,即代表命令已成功执行,每次操作可通过dir命令进行验证。如上图,在输入newdir指令后用dir可发现文件夹“FirstTestDirectory”已成功创建,另外,该指令还支持同时接受多个参数,即可同时创建多个文件夹,创建多个文件夹时只需用空格间开每个文件夹名即可(见图中的“SecondTestDirectory”和“ThirdTestDirectory”文件夹的创建)。注意,当需要新建的文件夹名己存在,程序会提示用户文件夹创建失败。

6. 删除目录的指令 (deldir) 演示

08_简单的命令解析器(小钱版)[操作系统][2012-12-15]_第5张图片

如上图所示,如需删除某一目录时,用户可先输入“deldir”然后输入需删除的目录名,输入的目录参数使用空格间开,按下回车后,若跳到下一指令输入语句,即代表命令已成功执行,每次操作可通过dir命令进行验证。如上图,在输入deldir指令后用dir可发现文件夹“FirstTestDirectory”已成功删除,此外,该指令同样支持同时接受多个参数,即可同时删除多个文件夹,删除多个文件夹时只需用空格间开每个文件夹名即可(见图中的“SecondTestDirectory”和“ThirdTestDirectory”文件夹的删除)。注意,当需要删除的文件夹不存在,程序会提示用户目录删除失败。

7. 重命名一个文件或目录的指令 (rename) 演示

08_简单的命令解析器(小钱版)[操作系统][2012-12-15]_第6张图片

如上图所示,若想将目录名为1的目录更改为a,只需输入rename指令,加上1(旧文件名)和a(新文件名),(指令及文件名之间使用空格间开,)即可完成重命令功能,若想同时更改多个文件的名字,只需要在输入参数的时候以旧文件名,新文件名,旧文件名,新文件名…的格式输入即可完成对应功能,若没发现需要重命名的文件,则程序会给出错误提示。

8. 改变当前工作目录的指令 ( cd ) 演示

08_简单的命令解析器(小钱版)[操作系统][2012-12-15]_第7张图片

如上图所示,若想更改当前工作目录,直接输入“cd”命令后跟路径名即可,如果想返回上一级目录,可输入“cd .. ”,如输入的路径有误,会停留在当前工作目录,并且为用户给出提示信息。

9. 在指定的目录及其子目录中查找指定的文件的指令 ( find ) 演示

08_简单的命令解析器(小钱版)[操作系统][2012-12-15]_第8张图片

如上图所示,find指令可以找出在指定的目录及其子目录中查找指定的文件,在找到查找目标文件后,会列出文件所在目录的完整路径,若不输入第一个参数,程序会默认在本目录开始寻找目标文件,如上例所示,在目录“/home/10111/project/LastTest”中输入“find /home/10111/project/LastTest –name file1.txt”和“find –name file1.txt”能达到同样的查找结果。若未能找出文件,程序会自动提示用户“目录不存在或没有在指定目录中找到文件”。

10. 退出命令解释程序的指令 ( exit ) 演示

08_简单的命令解析器(小钱版)[操作系统][2012-12-15]_第9张图片

如上图所示,当输入退出命令“exit”后,程序显示出退出的友好提示信息后会自动返回到Linux终端。



三。概念原理


1. 显示当前所在目录的路径名的指令依赖函数

简要介绍

getcwd(取得当前的工作目录)

相关函数

get_current_dir_name,getwd,chdir

表头文件

#include<unistd.h>

定义函数

char * getcwd(char * buf,size_t size);

函数说明

getcwd()会将当前的工作目录绝对路径复制到参数buf所指的内存空间,参数size为buf的空间大小。在调用此函数时,buf所指的内存空间要足够大,若工作目录绝对路径的字符串长度超过参数size大小,则回值NULL,errno的值则为ERANGE。倘若参数buf为NULL,getcwd()会依参数size的大小自动配置内存(使用malloc()),如果参数size也为0,则getcwd()会依工作目录绝对路径的字符串程度来决定所配置的内存大小,进程可以在使用完此字符串后利用free()来释放此空间。

返回值

执行成功则将结果复制到参数buf所指的内存空间,或是返回自动配置的字符串指针。失败返回NULL,错误代码存于errno。

2. 列出指定目录名中的所有目录及文件的指令依赖函数

简要介绍

readdir(读取目录)

相关函数

open,opendir,closedir,rewinddir,seekdir,telldir,scandir

表头文件

#include<sys/types.h>

#include<dirent.h>

定义函数

struct dirent * readdir(DIR * dir);

函数说明

readdir()返回参数dir目录流的下个目录进入点。

结构dirent定义如下

struct dirent

{

ino_t d_ino;

ff_t d_off;

signed short int d_reclen;

unsigned char d_type;

har d_name[256;

};

d_ino 此目录进入点的inode

d_off 目录文件开头至此目录进入点的位移

d_reclen _name的长度,不包含NULL字符

d_type d_name 所指的文件类型

d_name 文件名

返回值

成功则返回下个目录进入点。有错误发生或读取到目录文件尾则返回NULL。

附加说明

EBADF参数dir为无效的目录流。

3. 改变当前工作目录的指令依赖函数

简要介绍

chdir(改变当前的工作(目录)

相关函数

getcwd,chroot

表头文件

#include<unistd.h>

定义函数

int chdir(const char * path);

函数说明

chdir()用来将当前的工作目录改变成以参数path所指的目录。

返回值

执行成功则返回0,失败返回-1,errno为错误代码。

执行

current working directory :/tmp

4. 显示当前日期的指令依赖函数

简要介绍

localtime(取得当地目前时间和日期)

相关函数

time, asctime, ctime, gmtime

表头文件

#include<time.h>

定义函数

struct tm *localtime(const time_t * timep);

函数说明

localtime()将参数timep所指的time_t结构中的信息转换成真实世界所使用的时间日期表示方法,然后将结果由结构tm返回。结构tm的定义请参考gmtime()。此函数返回的时间日期已经转换成当地时区。

返回值

返回结构tm代表目前的当地时间。

5. 在指定的目录及其子目录中查找指定的文件的指令依赖函数

简要介绍

ftw(遍历目录树)

头文件

#include <ftw.h>

函数定义

int ftw(const char *dir, int (*fn)(const char *file, const struct stat *sb, int flag), int depth);

函数说明

ftw()会从参数dir指定的目录开始, 往下一层层地递归式遍历子目录. 每进入一个目录, 便会调用参数*fn定义的函数来处理. ftw()会传三个参数给fn(), 第一个参数*file指向当时所在的目录路径, 第二个参数是*sb, 为stat结构指针(该结构的定义请参考stat()), 第三个参数为旗标, 有下面几种可能值:

 

   FTW_F    一般文件

 

   FTW_D    目录

 

   FTW_DNR 不可读取的目录. 此目录以下将不被遍历

 

   FTW_SL   符号连接

 

   FTW_NS   无法取得stat结构数据, 有可能是权限问题

 

最后一个参数depth代表ftw()在进行遍历目录时可同时打开的文件数. ftw()在遍历时每一层目录至少需要一个文件描述词, 如果遍历时用完了depth所给予的限制数目, 整个遍历将因不断地关闭文件和打开文件操作而显得缓慢. 如果要结束ftw()的遍历, fn()只需返回一非零值即可, 此值同时也会是ftw()的返回值, 否则ftw()会试着走完所有的目录, 然后返回0. 遍历中断则返回fn()函数的返回值, 全部遍历完则返回0. 如有错误发生则返回-1.

附加说明

由于ftw()会动态配置内存使用, 请使用正常方式(fn函数返回非0值)来中断遍历, 不要在fn函数中使用longjmp().

6. 重命名一个文件或目录的指令依赖函数

简要介绍

rename(更改文件名称或位置)

相关函数

link,unlink,symlink

表头文件

#include<stdio.h>

定义函数

int rename(const char * oldpath,const char * newpath);

函数说明

rename()会将参数oldpath 所指定的文件名称改为参数newpath所指的文件名称。若newpath所指定的文件已存在,则会被删除。

返回值

执行成功则返回0,失败返回-1,错误原因存于errno

7. 新建工作目录的指令依赖函数

简要介绍

mkdir(创建工作目录)

头文件

#include <sys/stat.h>

 

#include <sys/types.h>

定义函数

int mkdir(const char *pathname, mode_t mode);

函数说明

mkdir()函数以mode方式创建一个以参数pathname命名的目录,mode定义新创建目录的权限。

返回值

若目录创建成功,则返回0;否则返回-1,并将错误记录到全局变量errno。

8. 删除目录的指令依赖函数

简要介绍

rmdir(删除工作目录)

头文件

#include <unistd.h>

定义函数

int rmdir(const char *filename)

函数说明

这函数删除一个工作目录。这工作目录在执行删除功能前必须为空。当然,有两种例外情况也会不能成功删除文件,在errno中包含了这两种情况,是:ENOTEMPTY,二是EEXIST,这两种错误情况是一样的,有些系统使用第一种提示,有些系统使用第二种提示。GNU/Linux和GNU/Hurd系统常常使用ENOTEMPTY。



四。模块设计


1. 设计概要

本次项目的题为简单的命令解释器,显然,单个命令处理流程可以简单地分为下图几个步骤:

那么,要想解释一条指令,最开始第一步就是用户输入指令并由程序读取指令,第二步是由程序分析指令,第三步程序对分析好的指令进行处理,第四步将指令的处理结果输出。

而用户对指令的输入和程序对指令的存储与分析紧密相联,要求用户输入指令及读取指令的方法比较简单,并且在读取指令及分析指令阶段时所产生的大部分临时数据并不需要在处理指令和输出结果这两步中用到,这样可以降低第一二步与第三四步操作的耦合性,因此将读取指令和分析指令封装为一个类进行处理;至于处理指令和输出结果两步,在本次“简单命令解释器”项目中,结果的反馈信息并不多,如在指令执行成功后可直接不输出结果即可向用户表明结果处理成功,在指令执行失败后只需给一句话或一段话以提示用户错误原因,因此,如果将输出结果也独立出一个类就使程序有点冗余了,如下图所示:

08_简单的命令解析器(小钱版)[操作系统][2012-12-15]_第10张图片

可以先将项目项分为这两个类来管理,第一个类用于对指令的读取及解释,第二个类用于对指令的处理和结果的汇报,两个类间的关联只需要是一个规范化好的指令格式数据即可。

08_简单的命令解析器(小钱版)[操作系统][2012-12-15]_第11张图片

如上图所示,事实上,上述一直所提到的四个步骤都只是对一条指令的处理,而这次的“简单命令解释器”项目不可能只处理一条指令就结束程序,因此,我们可以再将四个步骤(两个大类)进行封装,使用一个控制类来总管理它们的各种操作。

最后,补充说明为什么将这次的项目程序编写分为三个大类来处理而不直接使用结构化的过程处理这次的项目。这是因为考虑到了这个项目汲及的指令条数比较多,如果单条指令使用单个流程,那么最终出来的程序就很有可能变得只是由几个函数组成的程序,很不好管理,对于后期的维护扩展与维护更是带来不少困难。下面可以由UML类图看出这样分模块处理的优点。

2. UML类图

 08_简单的命令解析器(小钱版)[操作系统][2012-12-15]_第12张图片

如上图所示,本次项目共用三个自定义类来处理。其中,Client代表有C++的main入口函数的源文件,负责管理Controler类对象的生命周期及在生命周期间调用Controler类对象的相关函数,从而控制整个项目的执行流程。Controler类作为指令的分析及处理的综合管理类,可以从上面“设计概要”说明中体现这个类的重要性,使用这个类可以方便综合管理用户输入指令并由程序读取指令,程序分析指令,程序对分析好的指令进行处理,程序将指令的处理结果输出这四个程序执行步骤,为了达到管理目的,它需要依赖私有数据成员CommandAnalysis和FunctionImplement类。CommandAnalysis类负责要要求用户输入指令和读取指令并将指令规范化。FunctionImplement类负责处理已经规范化好的指令及将指令处理结果输出。



五。详细设计


1. 在CommandAnalysis类中指令的预定义

规则:在CommandAnalysis类中,使用了多个set<string>数据结构对指令进行预存储,而它们的变量名分别为 comm0p,comm1p,comm1np,comm1p_0p,comm2pn,comm1p1o1p_1o1p,optionComm,在这些命名当中,comm代表了命令名称,例如:“dir”,“pwd”和“cd”这些命令等等,而紧跟在comm后的数字和字母p(parameter)代表的是对应comm命令后所需附加的参数个数,如果有n表示,则代表可以跟数字的n倍个参数,下划线则代表“或”,即comm命令可以加下划线前面格式的参数,也可以加下划线后面格式的参数。optionComm代表一些选项关键字,它一般是在comm后面的。

意义:将这些命令分别存放于set<string>类的变量中,这是为了区分不同格式类型的命令,也就是说,我们可以通过这些不同的变量名而给我们需要执行的命令进行分类从而能正确地解析出命令,这也体现了程序的可扩展性,当我们需要添加需要解析的指令时,如果存在同一类型的指令,只需在程序中这个类的初始化时再添加新指令,程序就能解析这个指令而不用添加过多的代码了。

2. 在CommandAnalysis类中命令的规范化

规则:在上面的说明中,我们常常提到了将命令格式化或规范化,那么,不得不说一下,这里的规范化,指的是我自定义的一种存放指令及指令参数的一种格式,由上面的类图中可以看出,CommandAnalysis类中有一个成员变量vector<string> resultList,resultList容器中的第一个元素(即resultList[0])用于存放命令名称,而容器中的第一个元素以下(如resultList[1]...resultList[n])的元素则用于存放命令的参数。

方法:在规范化时,我们使用的是bool CommandAnalysis::formatCommand()函数,该函数先从用户输入的字符串string userCommand中使用getNextWord()函数获取第一个单词临时存放于string currentStr中,然后使用if-else语句判断currentStr是属于上面第一小点所提到的多个set<string>中的哪一个变量中的值,如果不属于这些set集当中,则提示错误信息并返回false,否则跳转到对应的格式处理函数,如:当currentStr存在于set<string> comm1p中,则执行bool CommandAnalysis::judgeComm1p(),若当currentStr存在于变量set<string> comm0p中,则执行bool CommandAnalysis::judgeComm0p(),通过调用相对应的函数进一步对命令进行规范化。在类似命令judgeComm ()系列的函数,它们是通过逐步获取用户输入串string userCommand中的单词(以空格或\t分隔的字符串),以各自的命令规则,将所需要的参数放进resultList容器尾中,而在这规范化的过程中,程序并不会识别这个参数是否正确,参数(如:文件路径)的正确与否会在函数功能的实现时才进行判断。因此,最终的用户串输入规范化结果就会保存在resultList容器中了。

3. 在CommandAnalysis类中字符串的单词获取

参数依赖及解析:我们将单词的获取函数在CommandAnalysis类中声明为bool CommandAnalysis::getNextWord(),该函数在CommandAnalysis类中,并且会依赖私有数据成员string userCommand,string currentStr和long currentIndex完成单词读取的功能,当然,这里的单词,与上文所指的一样,是在userCommand字符串中再由' ','"'或‘\0'分隔的字符串。currentStr变量的作用就是临时存放本次单词获取的结果。在这里需要知道的是,我们这里不断提及到的单词获取是能过不断读取与比较userCommand字符串中的单个字符完成的,每读取一个字符,currentIndex变量就会加1,以记录当前读取的字符位和下一位该读取的字符位。

方法:实际上,单词的读取还需要我们去考虑一些边界条件,首先,我们得知道什么时候我们所读取的字符可能会溢出,这里所指的溢出,我们可以考虑到字符串的边界,因此,作为进入函数后,将currentIndex(可读取字符下标)加1(移到下一位读取字符)后,首先得判断是否越出了userCommand的字符串的长度(即所说的边界),若不会越出边界,即可以进行正常读取。

而在读取时,我们首先得考虑将空格符去除,因为本次项目中的空格符是作为单词的分界符,去除空格符,可以使用一个while循环语句进行操作,当读到空格符时,将currentIndex加1,判断如果currentIndex没有越界,则继续进行循环,直到不再遇到空格符为止。

接下来,我们还需要考虑一种情况,有些参数是允许带空格符的,不过,在这些允许带空格符的单词中,会有通过一种特殊的输入符(“”)双引号进行辨认,也就是说,我们有只要双引号内的字符串,我们都称为单词,读取双引号单词,我们可以先判断正在读取的字符是否双引号,若是,则继续进行判断,currentIndex加1并进行越界判断,如果加了1后的currentIndex处的字符再次为双引号则证明这是一个空单词,并返回错误,结束函数,如果currentIndex处的字符是空格或者字符串结束符,那么这也是一个错误的单词,如果即不是双引号,又不是空格和结束符,就将字符存储并且继续加1移动currentIndex,判断是否合法字符,存储,循环操作直到遇到第二个引号即结束本次单词读取并使该函数结果返回“true”。

最后,如果单词不是以双引号作为开始标记,以空格或结束符为结束标记,我们则可以开始正常逐个字符读取及存储,使用空格或结束符作为字符的读取循环结束标记。

4. 在FunctionImplement类中指令功能选择实现的入口函数

概念:指令功能选择实现的入口函数指的是能判断用户己规范化好的指令类型和能实现在函数内自动进行指令方法调用的公有函数。该函数在类FunctionImplement中函数原型是:int start(vector<string> comm)。comm作为字符串容器,容器内元素第0位存放指令名称,其它位存放指令参数。函数返回它所调用指令的代号: -1:NoCommand, 0:exit, 1:pwd, 2:dir, 3:cd, 4:newdir, 5:deldir, 6:rename, 7:find, 8:date。函数接受空的vector<string>和未规范化的指令容器,当遇到上述两种情况时,函数内部会自动进行判断并返回结果。

方法:进入函数后,首先判断comm容器是否为空,若为空,则输出错误信息,函数返回false,若非空,则继续判断,使用if-else分支结构语句判断comm[0]与指令名“pwd”、“dir”、“newdir”等是否相同,若都不相同,则输出错误提示信息,函数返回false,若相同,则在if语句块中调用对应的功能函数,如:if("pwd" == comm[0]){myPwd();},当遇上可接受n个参数的指令时,则使用for循环语句重复功能函数的调用。

5. 在FunctionImplement类中指令功能的实现函数

在这次指令功能的实现,大多数指令功能都直接调用Linux-C的API,而项目中,在功能实现时,除了调用API外,还记录函数处理结果,并且当功能处理失败时,给出提示信息。如:新建目录的部分函数代码:

int res = mkdir(addr.c_str(), S_IRWXU);
if(0 != res){
      printf("提示信息:创建%s目录失败,", addr.c_str());
      switch(errno){
          case EACCES: printf("不具有写入操作的权限。\n");break;
          …(此处省略代码)
      }
}

这里先是调用了新建目录的库函数,然后临时保存返回值,紧接着对返回值进行判断,如果返回值非零的话,说明函数执行不成功,有错误信息,在if语句中再对错误信息进行处理,如果errno的值为EACCES,则根据API参考文档,它意思是“不具有写入操作的权限”,此时我们将这提示信息输出,将该信息反馈给用户。

6. 在FunctionImplement类中查找功能的实现

原因:在第5小点中说到了大多数功能的实现都是直接调用库函数,而这里特意提到查找功能的实现的原因是它并非只调用一个库函数就可以实现功能。

依赖:查找功能的实现,用到了两个公有成员变量,string targetName和int targetNum,一个静态成员变量static FunctionImplement * myObject还有一个私有成员函数 void myFind(string dirStr, string fileName)和一个公有成员函数int myFindAssistant2(const char * findName);以及一个全局函数int myFindAssistant(const char *file, const struct stat* sb, int flag)。

实现:实现查找功能的入口函数是私有成员函数 void myFind(string dirStr, string fileName),进入该函数后,首先检查dirStr目录是否为空,如果为空,则默认检测当前工作目录,把dirStr设为当前工作目录,然后,将myObject静态数据成员的值设为this指针的地址,目的是能使该对象在全局函数中使用,然后将,公有数据成员targetName设为fileName的值,这样可以让公有成员函数int myFindAssistant2(const char * findName)确定查找目标,将targetNum设为0,找到目标文件的数目为0,准备工作做完后,可以开始调用Linux-C的API函数ftw, ftw(dirStr.c_str(), myFindAssistant, 500),ftw()会从参数dirStr指定的目录开始, 往下一层层地递归式遍历子目录. 每进入一个目录, 便会调用myFindAssistant(…)全局函数来处理。接下来我们看int myFindAssistant(const char *file, const struct stat* sb, int flag)全局函数,在这函数中我们可以直接使用参数flag判断当前char* file是否文件,如果(flag == FTW_F)则开始调用函数FunctionImplement::myObject -> myFindAssistant2(file),该函数对单个文件的判断,判断传入参数路径是否需寻找的目标文件,先分析传入参数先分析路径,如是根路径,则直接与targetName进行对比,如非根路径,则将文件名提取出来后与targetName对比,若找到与目标文件名targetName相等,则输出路径,并将targetNum加1。就这样,将两个公有成员变量,一个静态成员变量,一个私有成员函数,一个公有成员函数,一个全局函数联合起来,实现了文件查找的功能。



六。总结


开篇:

本次项目主题,是操作系统的课程设计题目,刚刚接触到题目那刻,还是觉得题目是相当地有难度的,题为“在Linux环境下模拟实现简单命令解释器”。之后,再细看下面的课程设计的要求和提示,不能调用System()函数,但是有一些功能函数提供给我们。这样一来,我明白了原来这些功能的实现都只需要调用Linux C里面的API就可以。因此,我知道了本次的项目难点不在功能的实现,而是在用户输入指令的规范化和整个项目的模块设计。

过程:

在知道了项目侧重点后,先大致地将项目划分为三块,一块是指令分析,一块是功能实现,三是项目整合。功能实现:对于功能实现一块,认为还是比较简单的,于是就先去了解我所需要实现功能所需要用上的API,搜集准备资料,之后就开始了FunctionImplement类的设计及编写,并在一天内将这个关于功能实现类的代码编写和简单调试完成了,编写调试过程中也没有出现“疑难杂症”。

指令分析:

由于刚学完编译原理理论课不久,想去利用所学的知识完成这次指令分析类的编写,刚开始时,一直在苦想如何使用编译原理的语法分析,词法分析的方法完成这次项目的指令分析,还拿起了编译原理的书本进行复习,复习的时候时刻与自己这次所做的项目进行结合思考,还上网找了一些关于写词法分析器的方法作为思路参考,后来,将这次所需要用的指令语法写出了正则表达式,画出了DFA(有穷自动机),画出了符号表,在观察这些正则表达式和符号表的时候才发现,如果这次用这种方法去完成这项目的话,未免将简单的问题复杂化了,原本可以高效分析出的指令也会变得累赘,不过还是不很相信自己所想的,因为毕竟是数天的精力才将正则表达式、DFA和符号表,数据结构定下来,忽然间放弃这种思路总觉得有点不是很值得,于是就去请教老师,结果老师很不支持这种做法,最后还是决定了放弃这种复杂的做法,直接用直接的方法,逐步获取单词来分析指令。确定了方法后,先定下这次编写这个指令分析类所需要使用的数据结构,完成类的设计,然后再开始代码的编写,这过程也是用了一天左右的时间完成。

项目整合:

最后,在己做好功能实现和指令分析后再进行项止的整合并不难,只需要给功能实现的类加上入口函数,给指令分析的类加上分析结果的传送,再新建一个Controler控制类,创建一个main函数对全局进行控制就完成了。在完成后,进行功能的测试,进行功能的强化改良,这过程再花上了大概一整天的时间将其完成。

说明书编写:

通常在我在项目设计的最后阶段,就是文档的编写和总结,这次在文档编写过程中,写说明书是最耗时间的一个阶段,它并不需要像设计项目时候那样寻求新方法去解决问题,但是会需要我们如何将自己的项目思路,项目概念表达给看说明书的人,这需要一定的概括能力及项目组织思路,因此写说明书也是一个极具挑战的工作。

小结:

通过这次项目的设计与编写,终于将暂时放下了两个月的C++编程重新熟悉了一次,这学期由于各种学科的学习,放下了C++一段时间,刚刚接触项目时竟然还忘了部分的C++语言基础,通过这次项目的编写,找回了一些C++的编程手感,项目设计的思路,还再次加强熟悉Linux编程环境,加强了对Linux命令的理解及函数的运用。

附:

本次小项目代码量:1049行

项目最终完成日期:2012年12月15日

项目作者:Neicole

联系方式:http://blog.csdn.net/neicole




七。源码

08_简单的命令解析器(小钱版)[操作系统][2012-12-15]_第13张图片

readme.txt

1. 项目名称
   SimpleCommandParser(简单的命令解析器)
2. 开发目的
   熟悉Linux编程环境,加强对Linux命令的理解及函数的运用。
3. 开发环境
   LSB Version:  :core-3.0-ia32
   :core-3.0-noarch
   :graphics-3.0-ia32
   :graphics-3.0-noarch
   Distributor ID:  RedHatEnterpriseAS
   Description:    Red Hat Enterprise Linux AS release 4 (Nahant Update 4)
   Codename:     NahantUpdate4
   Compiler:      GNU GCC Compiler (mingw32-g++.exe)
   Programming Language: C++
4. 文件说明
   ① 服务器路径:   /home/10111/project
   ② 程序测试目录: /home/10111/project/LastTest/
   ③ 程序运行文件: /home/10111/project/LastTest/RunProgram
   ④ 源程序文件夹: /home/10111/project/SimpleCommandParser
   ⑤ 程序制作相关:  /home/10111/project/About
5. 项目功能 (已完成)
   ① 显示当前所在目录的路径名的指令:pwd
   ② 列出指定目录名中的所有目录及文件的指令:dir  <目录名>
   ③ 改变当前工作目录的指令:cd  <目录名或路径>
   ④ 显示当前日期的指令:date
   ⑤ 在指定的目录及其子目录中查找指定的文件的指令:
      find <目录>  -name <待查找的文件名>
   ⑥ 重命名一个文件或目录的指令:
      rename  <旧文件名1> <新文件名1> <旧文件名2> <新文件名2>…
   ⑦ 新建目录的指令:
      newdir  <目录名1> <目录名2> …<目录名n>
   ⑧ 删除目录的指令
      deldir   <目录名1> <目录名2> …<目录名n>
   ⑨ 退出命令解释程序的指令:exit
6. 项目特点
   ① 强扩展性:可以方便地根据需求进行指令的增删修改。
   ② 强容错性:可以接受用户各种方式的错误指令输入。
   ③ 可维护性:项目模块间耦合性低,可以轻松维护项目。
   ④ 简单易用:各指令符合用户日常使用习惯。
7. 参考文献或书籍资料
   ① Unix技术网:http://man.chinaunix.net/
   ② 百度百科:http://baike.baidu.com/
   ③ GNU Operating System:http://www.gnu.org/
8.关于
   作者:Neicole
   日期:2012.12.15
   联系:http://blog.csdn.net/neicole


 

Controler.h

 

/**
 * 类名:Controler
 * 项目名:SimpleCommandParser
 * 项目简介:在Linux环境下模拟实现简单命令解释器。
 *            包括:date, pwd, dir, cd, newdir, deldir, exit, rename, find。
 * 运行环境:在windows平台只能执行部分功能,而在Liunx环境能执行该程序的所有功能。
 * 类功能:控制整合命令输入到命令分析,结果输出过程行为的控制类。
 * 类依赖:依赖FunctionImplement作为功能实现的类和CommandAnalysis作为指令输入控制的类。
 * 类方法:在类中提供了基本构造方法(无参)和获取用户输入的函数和命令启动的函数。
 * 作者名:Neicole
 * 联系方式:http://blog.csdn.net/neicole
 **/

#ifndef CONTROLER_H_
#define CONTROLER_H_

#include "CommandAnalysis.h"
#include "FunctionImplement.h"

class Controler
{
    private:
    FunctionImplement * functionImplement;
    CommandAnalysis * commandAnalysis;

    public:
    Controler();            // 构造函数
    ~Controler();           // 析构函数
    bool getTrueCommand();  // 获取用户输入指令
    int startCommand();     // 命令启动
};

#endif  // CONTROLER_H_


 

FunctionImplement.h

/**
 * 类名:FunctionImplement
 * 项目名:SimpleCommandParser
 * 项目简介:在Linux环境下模拟实现简单命令解释器。
 *            包括:date, pwd, dir, cd, newdir, deldir, exit, rename, find。
 * 运行环境:在windows平台只能执行部分功能,而在Liunx环境能执行该程序的所有功能。
 * 类功能:作为命令解释的类,提供了每个命令解释的方法执行及结果处理。
 * 类依赖:使用了STL库中的vector、string基本数据结构类。
 * 类成员:本类没有整个类需要使用上的私有成员变量,
 *          使用了一个功能实现的函数入口方法,int start(vector<string>);
 *          通过该方法可以判断用户己格式化好的指令类型及自动进行方法调用。
 * 作者名:Neicole
 * 联系方式:http://blog.csdn.net/neicole
 **/

#ifndef FUNCTIONIMPLEMENT_H_
#define FUNCTIONIMPLEMENT_H_

#include <vector>
#include <string>

using std::string;
using std::vector;

class FunctionImplement
{
    public:
    // 基本构造及析构函数
    FunctionImplement();
    ~FunctionImplement();

    // 本类中所有功能实现的函数入口
    int start(vector<string>);

    // 作为辅助查功功能的一些成员变量及函数
    static FunctionImplement * myObject;
    string targetName;
    int targetNum;
    int myFindAssistant2(const char * findName);    // 查找功能的辅助函数

    private:
    // 指令功能函数
    void myPwd();          //显示当前所在目录的路径名
    void myDir(string);    //列出指定目录名中的所有目录及文件
    void myCd(string);     //改变当前工作目录
    void myNewdir(string); //新建目录
    void myDeldir(string); //删除目录
    void myExit();         //退出命令解释程序
    void myRename(string oldName,  string newName); //重命名一个文件或目录
    void myFind(string dirStr, string fileName);    //在指定的目录及其子目录中查找指定的文件
    void myDate();                                  //显示当前日期
};

#endif  // FUNCTIONIMPLEMENT_H_


CommandAnalysis.h

/**
 * 类名:CommandAnalysis
 * 项目名:SimpleCommandParser
 * 项目简介:在Linux环境下模拟实现简单命令解释器。
 *            包括:date, pwd, dir, cd, newdir, deldir, exit, rename, find。
 * 运行环境:在windows平台只能执行部分功能,而在Liunx环境能执行该程序的所有功能。
 * 类功能:可以让用户输入指令及对用户所输入的指令进行判断及格式化。
 * 类依赖:使用了STL库中的vector、set、string基本数据结构类。
 * 类成员:使用多个set私有成员分别存放不同的规范格式的指令集;
 *            使用string对象存放用户输入的指令,vector<string>存放指令分析结果;
 *            而string CurrentStr则作为分析指令时的临时数据存放变量,
 *            long CurrentIndex作为当前指令分析(规范化)在用户输入指令字符串中的临时标志位。
 * 作者名:Neicole
 * 联系方式:http://blog.csdn.net/neicole
 **/

#ifndef COMMANDANALYSIS_H_
#define COMMANDANALYSIS_H_

#include <string>
#include <vector>
#include <set>

using std::set;
using std::string;
using std::vector;

class CommandAnalysis
{
    private:
    // 预定义指令
    set<string> comm0p;
    set<string> comm1p;
    set<string> comm1np;
    set<string> comm1p_0p;
    set<string> comm2pn;
    set<string> comm1p1o1p_1o1p;
    set<string> optionComm;

    // 输入及字符串相关
    string userCommand;            // 用户所输入的指令
    vector<string> resultList;     // 分析结果完整指令
    string currentStr;             // 正在分析的指令
    long currentIndex;              // 分析时最后读取到的指令

    // 功能函数
    void constructComm();           // 初始化预定义指令
    void startPrepare();            // 在开始分析用户指令前所做相应的准备
    void saveAndClearCurString();   // 清空并且保存当前临时分析出来的单个单词
    bool getNextWord();             // 从用户输入串中获取下一个单词段

    // 规范化用户输入指令时的判断函数
    bool judgeComm0p();
    bool judgeComm1p();
    bool judgeComm1np();
    bool judgeComm1p_0p();
    bool judgeComm2pn();
    bool judgeComm1p1o1p_1o1p();

    public:
    CommandAnalysis();      // 构造函数
    ~CommandAnalysis();     // 析构函数

    void getUserCommand();  //获取用户输入指令
    bool formatCommand();   // 命令规范化

    vector<string> getResultList();     // 获取分析结果
};

#endif // COMMANDANALYSIS_H_

 

main.cpp

/**
 * 文件名:main.cpp
 * 项目名:SimpleCommandParser
 * 项目简介:在Linux环境下模拟实现简单命令解释器。
 *            包括:date, pwd, dir, cd, newdir, deldir, exit, rename, find。
 * 运行环境:在windows平台只能执行部分功能,而在Liunx环境能执行该程序的所有功能。
 * 文件功能:程序入口,main函数的实现,控制程序总流程。
 * 文件依赖:依赖Controler.h文件,主要初始化Controler类对象,依赖Controler对象执行流程。
 * 程序执行流程:先为用户显示出欢迎菜单,然后让用户输入命令,通过Controler类对象内部分析用户指令。
 *                循环输入和分析过程,直到用户输入"exit"退出程序指令。
 * 作者名:Neicole
 * 制作日期:2012.12.15
 * 联系方式:http://blog.csdn.net/neicole
 **/

#include "Controler.h"
#include <stdio.h>

#define _FUNCTION_IN_UNIX_

int main()
{
    void welcome();
    void exitAbout();
    welcome();
    Controler * control = new Controler();      // 从堆中为control对象申请内存空间
    int breakWhile = 1;
    do{
        if(control -> getTrueCommand()){
            breakWhile = control -> startCommand();
        }
    }while(0 != breakWhile);
    delete control;         // 释放对象所占用的内存空间
    exitAbout();
    return 0;
}

/**
 * 函数名称:void welcome();
 * 函数功能:显示欢迎菜单,函数内含宏命令,
 *            当是Linux环境时,调用Linux环境的菜单。否则调用Windows环境欢迎菜单。
 * 函数参数:函数没有入口参数和返回值。
 **/
void welcome()
{
    #ifdef _FUNCTION_IN_UNIX_
    printf("\033[1m\033[33m\n");
    printf("@--------------> \033[31m欢迎来到Linux环境之简单命令解释器 \033[33m<-----------@\n");
    printf("@                                                              @\n");
    printf("@ \033[32mdate                                            显示当前日期 \033[33m@\n");
    printf("@ \033[32mpwd                                 显示当前所在目录的路径名 \033[33m@\n");
    printf("@ \033[32mdir     <目录名>            列出指定目录名中的所有目录及文件 \033[33m@\n");
    printf("@ \033[32mcd      <目录名或路径>                      改变当前工作目录 \033[33m@\n");
    printf("@ \033[32mnewdir  <目录名1>...<目录名n>                       新建目录 \033[33m@\n");
    printf("@ \033[32mdeldir  <目录名1>...<目录名n>                       删除目录 \033[33m@\n");
    printf("@ \033[32mrename  <旧文件名> <新文件名>           重命名一个文件或目录 \033[33m@\n");
    printf("@ \033[32mfind    <目录> -name <文件名>     在目录及其子目录中查找文件 \033[33m@\n");
    printf("@ \033[32mexit                                        退出命令解释程序 \033[33m@\n");
    printf("\n\033[0m");
    #else
    printf("\n");
    printf("@--------------> 欢迎来到Linux环境之简单命令解释器 <-----------@\n");
    printf("@                                                              @\n");
    printf("@ date                                            显示当前日期 @\n");
    printf("@ pwd                                 显示当前所在目录的路径名 @\n");
    printf("@ dir     <目录名>            列出指定目录名中的所有目录及文件 @\n");
    printf("@ cd      <目录名或路径>                      改变当前工作目录 @\n");
    printf("@ newdir  <目录名1>...<目录名n>                       新建目录 @\n");
    printf("@ deldir  <目录名1>...<目录名n>                       删除目录 @\n");
    printf("@ rename  <旧文件名> <新文件名>           重命名一个文件或目录 @\n");
    printf("@ find    <目录> -name <文件名>     在目录及其子目录中查找文件 @\n");
    printf("@ exit                                        退出命令解释程序 @\n");
    printf("\n");
    #endif
}


/**
 * 函数名称:voidexitAbout();
 * 函数功能:显示退出菜单,函数内含宏命令,
 *            当是Linux环境时,调用Linux环境的菜单。否则调用Windows环境退出菜单。
 * 函数参数:函数没有入口参数和返回值。
 **/
 void exitAbout()
 {
    #ifdef _FUNCTION_IN_UNIX_
    printf("\033[1m\n");
    printf("\033[1m\033[36m( ^_^ )/~~拜拜   \033[0m谢谢使用本程序!\n");
    printf("                 \033[0m软件制作者: Neicole\n");
    printf("                 \033[0m软件制作日期:2012年12月13日\n");
    printf("                 \033[0m联系方式:    http://blog.csdn.net/neicole\n");
    printf("\n\033[0m");
    #else
    printf("\n");
    printf("( ^_^ )/~~拜拜   谢谢使用本程序!\n");
    printf("                 软件制作者: Neicole\n");
    printf("                 软件制作日期:2012年12月13日\n");
    printf("                 联系方式:http://blog.csdn.net/neicole\n");
    printf("\n");
    #endif
    return;
 }


 

Controler.cpp

 

// Controler.cpp

#include "Controler.h"

/**
 * 构造函数: Controler::Controler();
 * 函数功能:无参,函数能将本类中的所有成员变量进行初始化。
 **/
Controler::Controler()
{
    functionImplement = new FunctionImplement();
    commandAnalysis = new CommandAnalysis();
}

/**
 * 析构函数: Controler::~Controler();
 * 函数功能:将本类中己从堆中申请的内存空间撤销。
 **/
Controler::~Controler()
{
    if(NULL != functionImplement){
        delete functionImplement;
    }
    if(NULL != commandAnalysis){
        delete commandAnalysis;
    }
}

/**
 * 函数名称:bool Controler::getTrueCommand();
 * 函数功能:获取用户输入指令及规范化用户输入指令格式。
 * 前置条件:已初始化commandAnalysis私有成员变量。
 * 返回值: 返回bool值,若成功获取且规范化用户指令则返回true;
 *           否则返回false.
 * 后置条件:用户输入指令及分析结果存放于commandAnalysis私有成员变量中。
 **/
bool Controler::getTrueCommand()
{
    bool res;
    commandAnalysis -> getUserCommand();
    res = commandAnalysis -> formatCommand();
    return res;
}

/**
 * 函数名称:bool Controler::getTrueCommand();
 * 函数功能:启动用户所输入的指令。
 * 前置条件:已初始化commandAnalysis和functionImplement私有成员变量。
 * 返回值:  -1:NoCommand, 0:exit, 1:pwd, 2:dir, 3:cd, 4:newdir,
 *             5:deldir, 6:rename, 7:find, 8:date
 **/
int Controler::startCommand()
{
    int res = 0;
    res = functionImplement -> start(commandAnalysis -> getResultList());
    return res; // 0代表跳出输入的循环,也代表exit
}


 

FunctionImplement.cpp

 

// FunctionImplement.cpp

#include "FunctionImplement.h"

#include <vector>
#include <string>
#include <cstdio>
#include <unistd.h>
#include <sys/types.h>
#include <dirent.h>
#include <errno.h>
#include <dirent.h>
#include <time.h>

#define _FUNCTION_IN_UNIX_  // 如果在Linux C 环境下,则#define _FUNCTION_IN_UNIX_

#ifdef _FUNCTION_IN_UNIX_
    #include <ftw.h>        // 查找功能所需头文件(在windows平台下不支持)
    #include <sys/stat.h>   // 查找功能所需头文件(在windows平台下不支持)
#endif

using std::string;
using std::vector;

/**
 * 函数名称:int start(vector<string>);
 * 函数参数:vector<string>作为字符串容器,第0位存放指令名称,其它位存放指令参数。
 * 函数功能:判断用户己格式化好的指令类型及自动进行方法调用。
 * 返回值:  -1:NoCommand, 0:exit, 1:pwd, 2:dir, 3:cd, 4:newdir,
 *             5:deldir, 6:rename, 7:find, 8:date
 **/
int FunctionImplement::start(vector<string> comm)
{
    if(0 == comm.size()){         // 容器为空时,无指令
        printf("NO COMMAND\n");
        return -1;
    }

    int res;
    if("pwd" == comm[0]){
        myPwd();
        res = 1;
    }
    else if("dir" == comm[0]){
        myDir(comm[1]);
        res = 2;
    }
    else if("cd" == comm[0]){
        myCd(comm[1]);
        res = 3;
    }
    else if("newdir" == comm[0]){
        for(int i = 1; i < (int)comm.size(); ++i){   // n个参数同一指令的执行。
            myNewdir(comm[i]);
        }
        res = 4;
    }
    else if("deldir" == comm[0]){
        for(int i = 1; i < (int)comm.size(); ++i){// n个参数同一指令的执行。
            myDeldir(comm[i]);
        }
        res = 5;
    }
    else if("exit" == comm[0]){
        myExit();
        res = 0;
    }
    else if("rename" == comm[0]){ // n个参数同一指令的执行。
        for(int i = 1; i < (int)comm.size()-1; i = i + 2){    // 减1是因为接受双参
            myRename(comm[i],  comm[i+1]);
        }
        res = 6;
    }
    else if("find" == comm[0]){
        myFind(comm[1],  comm[2]);
        res = 7;
    }
    else if("date" == comm[0]){
        myDate();
        res = 8;
    }
    else{
        printf("BAD COMMAND\n");
        res = -1;
    }
    return res;
}

// 构造函数(无需初始化任何参数)
FunctionImplement::FunctionImplement()
{
}

// 析构函数(若类静态指针指向本对象,则将其置空)
FunctionImplement::~FunctionImplement()
{
    if(this == myObject){   // 与this指针相同时,证明是该对象用过该静态类指针
        myObject = NULL;
    }
}

/**
 * 函数名称:void FunctionImplement::myPwd();
 * 函数功能:显示当前所在目录的路径名,无法显示则给出错误提示。
 * 注意事项:函数只支持目录地址在500字符内的路径获取。
 **/
void FunctionImplement::myPwd()
{
    char buf1[500];
    if(NULL != getcwd(buf1, 500)){
        #ifdef _FUNCTION_IN_UNIX_
         printf("\033[31m当前工作目录:\033[0m%s\n", buf1);
        #else
         printf("当前工作目录:%s\n", buf1);
        #endif
    }
    else{
         printf("错误:当前路径过长或用户权限不足,错误代号:%d\n", errno);
    }
    return;
}


/**
 * 函数名称:void FunctionImplement::myDir(string addr);
 * 函数功能:列出指定目录名中的所有目录及文件。(通过readdir()函数实现功能)
 * 依赖函数:void FunctionImplement::myPwd();
 **/
void FunctionImplement::myDir(string addr)
{
    if("" == addr){
        char buf1[500];
        if(NULL == getcwd(buf1, 500)){
            printf("错误:无法使用当前路径进行操作,当前路径过长或用户权限不足,错误代号:%d\n", errno);
            return;
        }
        addr = buf1;
    }
    // 打开目录
    DIR * dir = opendir(addr.c_str());
    if(NULL == dir){    // 打开目录失败
        printf("打开且陈列目录错误:");
        switch(errno){
            case EMFILE: printf("已达到进程可同时打开的文件数上限。\n");break;
            case ENFILE: printf("已达到系统可同时打开的文件数上限。\n");break;
            case ENOTDIR: printf("参数name非真正的目录。\n");break;
            case ENOENT: printf("参数name 指定的目录不存在,或是参数name 为一空字符串。\n");break;
            case ENOMEM: printf("核心内存不足。\n");break;
            default: printf("错误代码:%d \n", errno);break;
        }
        return;
    }

    // 遍历目录
    #ifdef _FUNCTION_IN_UNIX_
        printf("\033[31m[遍历目录]\033[34m%s\033[0m\n", addr.c_str());
    #else
        printf("[遍历目录]%s\n", addr.c_str());
    #endif
    int i = 1;
    struct dirent * ptr;
    for(; NULL != (ptr = readdir(dir)); ++i)    // 读取下一个节点
    {
       if(0 == strcmp("..", ptr -> d_name) || 0 == strcmp(".", ptr -> d_name)){       // 去掉几个点点
            --i;
            continue;
        }
        #ifdef _FUNCTION_IN_UNIX_
            printf("\033[31m[名称]\033[0m%-22s", ptr -> d_name);  // 在Linux中字体带颜色
            if(0 == i%2){
                printf("\n");
            }
        #else
            printf("名称:%s\n", ptr -> d_name);
        #endif
    }
    if(1 == i){
        printf("提示信息:这是一个空目录。\n");
    }
    printf("\n");

    // 关闭目录
    closedir(dir);
    return;
}

/**
 * 函数名称:void FunctionImplement::myCd(string addr);
 * 函数功能:改变当前工作目录
 **/
void FunctionImplement::myCd(string addr)
{
    int res = chdir(addr.c_str());
    if(0 != res){
        printf("提示信息:目录更改失败,请输入正确的工作目录!\n");
    }
    return;
}

/**
 * 函数名称:void FunctionImplement::myNewdir(string);
 * 函数功能:新建文件目录。
 **/
void FunctionImplement::myNewdir(string addr)
{
    #ifdef _FUNCTION_IN_UNIX_
        int res = mkdir(addr.c_str(), S_IRWXU);
        if(0 != res){
            printf("提示信息:创建%s目录失败,", addr.c_str());
            switch(errno){
                case EACCES: printf("不具有写入操作的权限。\n");break;
                case EEXIST: printf("目录己存在。\n");break;
                case EMLINK: printf("父目录文件数目过多。\n");break;
                case ENOSPC: printf("磁盘没有足够的空间写入。\n");break;
                case EROFS: printf("不可对只读文件系统写入。\n");break;
                default: printf("名字有冲突或发生异常。\n");break;
            }
        }
    #else
        int res = _mkdir(addr.c_str());
        if(0 != res){
            printf("提示信息:目录创建失败,请输入正确的工作目录!\n");
        }
    #endif
    return;
}

/**
 * 函数名称:void FunctionImplement::myDeldir(string addr);
 * 函数功能:删除文件目录(注意:目录必须为空).
 **/
void FunctionImplement::myDeldir(string addr)
{
 //   addr="test";          // 测试目录
    int res = 0;
    #ifdef _FUNCTION_IN_UNIX_
        res = rmdir(addr.c_str());
    #else
        res = _rmdir(addr.c_str());
    #endif
        if(0 != res){
            printf("提示信息:目录删除失败,请输入正确的空目录!\n");
        }

    return;
}

/**
 * 函数名称:void FunctionImplement::myRename(string oldName,  string newName);
 * 函数功能:重命名一个文件或目录。
 **/
void FunctionImplement::myRename(string oldName,  string newName)
{
    if(0 != rename(oldName.c_str(),newName.c_str())){
        printf("提示信息:重命名失败,请输入正确文件名!错误代号:%d 。\n",  errno);
    }
    return;
}

// 用于查找功能的静态指针的初始化
FunctionImplement * FunctionImplement::myObject = NULL;
//在指定的目录及其子目录中查找指定的文件(辅助函数)
int myFindAssistant(const char *file, const struct stat* sb, int flag)
{
    #ifdef _FUNCTION_IN_UNIX_
        if(flag == FTW_F)
        {
           FunctionImplement::myObject -> myFindAssistant2(file);
        }
    #endif
    return 0;
}

/**
 * 函数名称:int FunctionImplement::myFindAssistant2(const char *);
 * 函数功能:查找功能的最终实现函数,
 *           (实际上这函数是对单个文件的判断,判断传入参数路径是否需寻找的目标文件)
 * 函数实现:先分析路径,如是根路径,则直接与targetName进行对比,
 *            如非根路径,则将文件名提取出来后与targetName对比。
 **/
int FunctionImplement::myFindAssistant2(const char * finding)
{
    #ifdef _FUNCTION_IN_UNIX_
        string fileName = targetName;
        string findingStr(finding);
 //      printf("finding: %s\n target:%s\n", finding, targetName.c_str());

        size_t index = findingStr.find_last_of("/");
        if(string::npos != index){          // 找到\, 如果不在根目录,先去除路径名
            findingStr.erase(0, index+1);
 //           printf("计算后的findingStr为:%s\n", findingStr.c_str());
        }
        if ( 0 == strcmp(findingStr.c_str(), targetName.c_str())){// 匹配,则输出结果
            printf("%s\n", finding);
            ++targetNum;
        }
    #endif
    return 0;
}

/**
 * 函数名称:void FunctionImplement::myFind(string dirStr, string fileName);
 * 函数依赖:this -> getcwd(char*); ::myFindAssistant(...);
 * 函数功能:作为查找功能的入口函数,在指定的目录及其子目录中查找指定的文件。
 **/
void FunctionImplement::myFind(string dirStr, string fileName)
{
    #ifdef _FUNCTION_IN_UNIX_
        // 如果没有第一个参数,则默认获取当前目录,把当前目录作为查找操作的根目录。
        if("" == dirStr){
            char buf1[500];
            if(NULL == getcwd(buf1, 500)){
                printf("错误:无法使用当前路径进行操作,当前路径过长或用户权限不足,错误代号:%d\n", errno);
                return;
            }
            dirStr = buf1;
        }
        // 初始化待会函数需要用上的静态指针
        myObject = this;
        // 将需要查找的文件名通过成员变量传出,由后面程序交由this->myFindAssistant2(const char * )处理。
        targetName = fileName;
        // 统计找到的文件数。
        targetNum = 0;
        // 开始调用递归目录寻找文件的函数
        ftw(dirStr.c_str(), myFindAssistant, 500);
        // 对文件数目进行处理
        if(0 == targetNum){
            printf("提示:目录不存在或没有在指定目录中找到文件。\n");
        }
    #endif
    return;
}

/**
 * 函数名称:void FunctionImplement::myDate();
 * 函数功能:显示当前日期。
 **/
void FunctionImplement::myDate()
{
    time_t timep;
    struct tm *p;
    time(&timep);          // 返回从公元1970年1月1日的UTC时间从0时0分0秒算起到现在所经过的秒数。
    p = localtime(&timep); // 取得当地时间(转换时间为标准格式)
    printf ("%d Year %d Month %d Day\n", (1900 + p->tm_year),(1 + p->tm_mon), p -> tm_mday);
//    printf(“%s%d:%d:%d\n”, wday[p->tm_wday],p->tm_hour, p->tm_min, p->tm_sec);
    return;
}


//退出命令解释程序
void FunctionImplement::myExit()
{
    return;// doNothing 交给程序流程控制去做,在main函数中 return 0;
}


 

CommandAnalysis.cpp

// CommandAnalysis.cpp
#include "CommandAnalysis.h"
#include <iostream>

using std::cout;
using std::cin;
using std::endl;

/**
 * 函数名称:void CommandAnalysis::constructComm();
 * 函数功能:初始化set集,将需要用上的指令放入对应的指令类型集中。
 **/
void CommandAnalysis::constructComm()
{
    comm0p.insert("pwd");
    comm0p.insert("exit");
    comm0p.insert("date");
    comm1p.insert("cd");
    comm1np.insert("newdir");
    comm1np.insert("deldir");
    comm1p_0p.insert("dir");
    comm2pn.insert("rename");
    comm1p1o1p_1o1p.insert("find");
    optionComm.insert("-name");
    return;
}

/**
 * 构造函数: CommandAnalysis::CommandAnalysis();
 * 函数功能:初始化时,通过调用本类函数初始化指令集。
 **/
CommandAnalysis::CommandAnalysis()
{
    this -> constructComm();
}

// 析构函数
CommandAnalysis::~CommandAnalysis()
{
    // 里面没有野指针,无需构造
}


/**
 * 函数名称:void CommandAnalysis::saveAndClearCurString()
 * 函数功能:将私有成员currentStr存入私有成员resultList中,存入后将currentStr内容清空。
 **/
void CommandAnalysis::saveAndClearCurString()
{
    resultList.push_back(currentStr);
    currentStr.clear();
    return;
}

/**
 * 函数名称:void CommandAnalysis::startPrepare();
 * 函数功能:为开始获取命令,分析命令做准备,
 *     清空所有与用户指令及指令结果有关的字符串信息。
 **/
void CommandAnalysis::startPrepare()
{
    userCommand.clear();
    resultList.clear();
    currentStr.clear();
    currentIndex = -1;  // 设为-1与下面的获取单词的算法有关
}

/**
 * 函数名称:bool CommandAnalysis::getNextWord();
 * 前置条件:currentIndex设为需要获取单词的首字母前一位。
 * 函数功能:从用户输入的指令串中获取下一单词存放于currentStr变量中。
 * 后置条件;将新获取的单词存于currentStr变量中。
 * 返回值:true:成功获取出由' ','"'或‘\0'分隔的单词。
 *          false:不能获取单词,currentIndex越界或双引号中内容为空。
 **/
bool CommandAnalysis::getNextWord()
{
    currentStr.clear();
    // 将下标移到下一位
    if((long)userCommand.length() <= ++currentIndex){
        return false;
    }

    // 去除空格(实际上是下标移动)
    while(' ' == userCommand[currentIndex] || '\t' == userCommand[currentIndex]){
        if((long)userCommand.length() <= ++currentIndex){
            return false;
        }
    }

    // 带空格目录时有可能出现双引号,此时不能以空格判断结束,直到下一个双引号或终结位置才结束
    // 不储存双引号
    if('\"' == userCommand[currentIndex]){
        if((long)userCommand.length() <= ++currentIndex){ // 下标移动,直到字符串末尾
                return false; // 只有一个单引号,返回错误
        }
        if(' ' == userCommand[currentIndex] && '\t' == userCommand[currentIndex] && '\0' == userCommand[currentIndex]){
            return false;   // 空串
        }
        while(true){
            currentStr.push_back(userCommand[currentIndex]);
            if((long)userCommand.length() <= ++currentIndex){ // 下标移动,直到字符串末尾
                return true;
            }
            if('\"' == userCommand[currentIndex]){      // 遇到第二个双引号则存储后跳出
                currentStr.push_back(userCommand[currentIndex]);
                return true;
            }
        }
    }

    // 空格或字符串结束符作为结束标记
    while(' ' != userCommand[currentIndex] && '\t' != userCommand[currentIndex] && '\0' != userCommand[currentIndex]){
        currentStr.push_back(userCommand[currentIndex]);
        if((long)userCommand.length() <= ++currentIndex){ // 下标移动,直到字符串末尾
            return true;
        }
    }

    return true;
}

/**
 * 函数名称:bool  CommandAnalysis::judgeComm0p();
 * 函数功能:规范化无参命令,将规范结果存放于vector<string>resultList中。
 * 容错性:在规范化时,若接收了无参命令,则忽略后面的参数。
 **/
bool  CommandAnalysis::judgeComm0p()
{
    this -> saveAndClearCurString();        // 保存命令
    /* 注释掉以增强容错性
    if(true == getNextWord()){      // 不符合无参命令的规则
        cout << resultList[0] << "命令不带参数,请以正确格式输入命令!\n";
        return false;
    }
    */
    return true;
}

/**
 * 函数名称:bool CommandAnalysis::judgeComm1p();
 * 函数功能:规范化带1位参数的命令,将规范结果存放于vector<string>resultList中。
 * 容错性:在规范化时,若为接收了正确的带1参命令及参数,则忽略后面的参数。
 **/
bool CommandAnalysis::judgeComm1p()
{
    this -> saveAndClearCurString();        // 保存命令
    if(true == getNextWord()){              // 获取第1个参数
        this -> saveAndClearCurString();
    }
    else{
        cout << resultList[0] << "命令至少需要一个参数,请以正确格式输入命令!\n";
        return false;
    }
    /* 注释掉以增强容错性
    if(true == getNextWord()){      // 不符合1参命令的规则
        cout << resultList[0] << "命令参数过多,请以正确格式输入命令!\n";
        return false;
    }
    */
    return true;
}


/**
 * 函数名称:bool CommandAnalysis::judgeComm1np();
 * 函数功能:规范化带1位或多位参数的命令,将规范结果存放于vector<string>resultList中。
 * 特性:在规范化时,若为接收了正确的带1参命令及参数,则可继续接收后面的参数。
 **/
bool  CommandAnalysis::judgeComm1np()
{
    this -> saveAndClearCurString();        // 保存命令
    if(true == getNextWord()){              // 获取第1个参数
        this -> saveAndClearCurString();
    }
    else{
        cout << resultList[0] << "命令至少需要一个参数,请以正确格式输入命令!\n";
        return false;
    }
    while(true == getNextWord()){           // 获取第n个参数
        this -> saveAndClearCurString();
    }
    return true;
}

/**
 * 函数名称:bool CommandAnalysis::judgeComm1p_0p();
 * 函数功能:规范化带1位或1位默认参数参数的命令,将规范结果存放于vector<string>resultList中。
 * 特性:在规范化时,若接收的命令为带1参命令,但不能获取到参数,则将默认参数设为""空串存放。
 **/
bool CommandAnalysis::judgeComm1p_0p()
{
    this -> saveAndClearCurString();        // 保存命令
    if(true == getNextWord()){              // 获取第1个参数
        this -> saveAndClearCurString();
    }
    else{
        currentStr = "";                    //  加上默认参数
        this -> saveAndClearCurString();
    }
    /* 注释掉以增强容错性
    if(true == getNextWord()){      // 不符合2参命令的规则
        cout << resultList[0] << "命令参数过多,请以正确格式输入命令!\n";
        return false;
    }
    */
    return true;
}

/**
 * 函数名称:bool CommandAnalysis::judgeComm2pn()
 * 函数功能:规范化带2位或多位参数的命令,将规范结果存放于vector<string>resultList中。
 * 特性:在规范化时,若为接收了正确的带2参命令及参数,则可继续接收后面的参数。
 *        在接收后面的参数时,只进行两位同时接收,如果为单数,则忽略最后的参数。
 **/
bool CommandAnalysis::judgeComm2pn()
{
    this -> saveAndClearCurString();        // 保存命令
    if(true == getNextWord()){  // 获取两个参数
        this -> saveAndClearCurString();
    }
    else{
        cout << resultList[0] << "命令至少需要两个参数,请以正确格式输入命令!\n";
        return false;
    }
    if(true == getNextWord()){  // 获取两个参数
        this -> saveAndClearCurString();
    }
    else{
        cout << resultList[0] << "命令至少需要两个参数,请以正确格式输入命令!\n";
        return false;
    }
    /* 注释掉以增强容错性
    if(true == getNextWord()){      // 不符合两参命令的规则
        cout << resultList[0] << "命令参数过多,请以正确格式输入命令!\n";
        return false;
    }
    */
    while(true == getNextWord()){// 开始扩展重命令功能,给多个文件重命名,只会双个双个参数接受
        this -> saveAndClearCurString();
        if(true == getNextWord()){
          this -> saveAndClearCurString();
        }
        else{       // 不能连续获取第二个参数则从结果容器中弹出
          resultList.pop_back();
        }
    }
    return true;
}

/**
 * 函数名称:bool CommandAnalysis::judgeComm1p_0p();
 * 函数功能:规范化[command x -y z]形式的命令,将规范结果存放于vector<string>resultList中。
 * 特性:在规范化时,若[command x -y z]将x省略则将自动将x设为""空串继续以规范化格式存放。
 **/
bool  CommandAnalysis::judgeComm1p1o1p_1o1p()
{
    this -> saveAndClearCurString();        // 保存命令
    if(true == getNextWord()){
         if(optionComm.count(currentStr)){   //  条件成立则带1默认参数
              currentStr = "";            //  加上默认参数
              this -> saveAndClearCurString();
              if(true == getNextWord()){      // 获取最后一位参数
                  this -> saveAndClearCurString();
              }
              else{
                   cout << resultList[0] << "命令参数错误,请以正确格式输入命令!\n";
                   return false;
              }
              /* 注释掉以增强容错性
               if(true == getNextWord()){      // 不符合两参命令的规则
                 cout << resultList[0] << "命令参数过多,请以正确格式输入命令!\n";
                 return false;
               }
               */
          }
          else{  // 无默认参数的情况
                this -> saveAndClearCurString();   // 存入第一参数
                if(true == getNextWord() && optionComm.count(currentStr)){
                    if(true == getNextWord()){      // 获取最后一位参数
                       this -> saveAndClearCurString();
                    }
                    else{
                        cout << resultList[0] << "命令参数错误,请以正确格式输入命令!\n";
                        return false;
                    }
                }
                else{
                    cout << resultList[0] << "命令参数错误,请以正确格式输入命令!\n";
                    return false;
                }
            }
            /* 注释掉以增强容错性
           if(true == getNextWord()){      // 不符合两参命令的规则
             cout << resultList[0] << "命令参数过多,请以正确格式输入命令!\n";
             return false;
           }
           */
        }
       else{
          cout << resultList[0] << "命令参数错误,请以正确格式输入命令!\n";
          return false;
        }
        return true;
}

/**
 * 函数名称:void CommandAnalysis::getUserCommand();
 * 函数功能:获取用户输入指令。
 **/
void CommandAnalysis::getUserCommand()
{
    this -> startPrepare();
 //   cout << "jiangzhenqian@ ";   // Windows平台样式
    cout << "\033[1mjiangzhenqian@ \033[0m";        // Linux平台样式
    // 获取一行字符存放于userCommand中
    std::cin.sync();
    for( char ch = getchar(); '\n' != ch; ch = getchar()){
        userCommand.push_back(ch);
    }
    return;
}

/**
 * 函数名称:bool CommandAnalysis::formatCommand();
 * 前置条件:vector<string>resultList成员变量为空。
 * 函数功能:将命令规范化。
 * 后置条件:成功规范化的指令以字符串序列的方式存放于vector<string>resultList变量中。
 **/
bool CommandAnalysis::formatCommand()
{
    bool res = true;
    if(true == getNextWord()){

        if(comm0p.count(currentStr)){       // 属于无参命令
            res =  this -> judgeComm0p();
        }
        else if(comm1p.count(currentStr)){  // 属于带1参命令
            res =  this -> judgeComm1p();
        }
        else if(comm1np.count(currentStr)){  // 属于带1个或以上参数的命令
            res =  this -> judgeComm1np();
        }
        else if(comm1p_0p.count(currentStr)){// 属于带1参或无参命令
            res =  this -> judgeComm1p_0p();
        }
        else if(comm2pn.count(currentStr)){// 属于带2参命令
            res = this -> judgeComm2pn();
        }
        else if(comm1p1o1p_1o1p.count(currentStr)){// 属于带1p1o1p或1o1p命令
            res = this -> judgeComm1p1o1p_1o1p();
        }
        else{// 不属于支持的命令
             cout << "不支持该命令,请输入正确的命令!\n";
             res = false;
        }
    }
    else{
        cout << "未发现指令,请输入指令。\n";
        res = false;
    }
    return res;
}

/**
 * 函数名称:vector<string> CommandAnalysis::getResultList();
 * 函数功能:获取分析结果变量。
 **/
vector<string> CommandAnalysis::getResultList()
{
    return resultList;
}


 

你可能感兴趣的:(操作系统,命令解释器)