Skip to content

Latest commit

 

History

History

shellproject

利用之前的知识,这个实验中我们要自己实现一个简单的 shell ——跟我们之前使用到的 bash shell 类似,它的功能是 显示当前所在的目录,然后对输入的命令进行解释。在目录下有一个文件main_sol,它是一个已经编译好的标程,会用来生成测试用的文件。你可以在目录下执行make后直接运行这个程序,查看它的效果——你的 shell 必须跟它的显示效果保持一致。

第一部分:log_t

在实现shell之前,你需要实现一个数据结构log_t——结构体的布局可以参见log.h头文件,它相当于一个用链表实现的栈,作用是保存输入的每一条命令以便查找。

log.h中定义了这样几个函数,你需要全部实现它们:

  • void log_init(log_t *l);初始化一个log_t——熟悉面向对象程序设计的同学应该能看出,这相当于是log_t的构造函数。你需要将lhead设置为NULL

  • void log_destroy(log_t* l);销毁一个log_t,相当于log_t的析构函数——你需要回收一个log_t所占用的所有内存,并将lhead设置为NULL,如果head已经为NULL则直接返回。

  • void log_push(log_t* l, const char *item);把一个字符串item插入到一个log_t的末尾。如果lheadNULL的话,你需要在这个函数中为log_t建立第一个结点并分配对应的内存空间。

  • char *log_search(log_t* l, const char *prefix);遍历整个log_t的链表,搜索是否有包含前缀prefix存在的字符串,如果有的话则返回这个字符串(只返回第一个),没有的话直接返回NULL

有 C++ 或者其他面向对象语言基础的同学可以看出,这一部分我们其实是在用 C 语言来进行面向对象编程——对于系统编程来说,很多情况下确实需要这种看起来有些“蹩脚”的编程方式,因此这里需要大家对此熟练掌握。

第二部分:shell

接下来,我们需要来实现 shell 本身——一个 shell 相当于一个无限循环的程序,它在循环中应该执行这些操作:

  • 向标准输出打印一个命令提示符
  • 从标准输入读取一个命令
  • 判断要执行哪些命令

在这一步,你需要修改 shell.c 里的prefix函数和execute函数。接下来我们将依次介绍进一步的细节:

命令提示符

你需要在prefix函数里输出以下格式的命令提示符:

/path/to/cwd$

这里可以直接用printf("%s$ ", cwd)来实现——cwd部分为当前所在目录。你可以自行查阅文档,阅读关于getcwd()函数的相关信息。

从标准输入读取命令

你的程序要每次从标准输入读取一行,获取输入的命令——你可以查阅文档学习getline()函数的用法,这里要 注意回收内存,不然你将无法获得内存泄露检测的分数。这部分功能已经在 main.c 文件中实现了,你可以阅读这部分代码,但无法对其进行修改,最终测试时会将你所做的任何修改覆盖掉。读入后,会将命令作为参数传递给execute函数。

对内置命令进行解析

你需要在execute函数里对内置命令进行解析,实现如下几个命令:

1)cd

cd xxx

进入某个目录(上述代码会进入xxx目录)——你可以查阅chdir()函数的用法。如果目录不存在,则要打印xxx: No such file or directory并换行,其中xxx表示输入的目录名称。

2)exit

exit

退出 shell 程序,在execute函数中直接返回 000。在其他情况下,execute返回一个非零值即可。

3)显示历史命令

!#

所有输入的命令都保存在一个log_t中,这个命令的作用是显示所有输入过的命令,每个占一行。从栈底元素开始输出,log_t变量名为Log,定义在 shell.h 文件中,请勿修改。

注意,以!开头的所有命令都不会被放入命令栈中。

4)根据前缀查找命令

!prefix

查找是否曾经输入过包含前缀prefix的命令,如果找到(如果有多个,只找最靠近栈底的一个),则执行这条命令,如果没有,则返回No Match换行。新执行的命令也会被放入栈顶。

5)ls

ls

跟 bash shell 的ls命令一样,列举当年目录下所有子目录和文件——你可以直接用system()函数执行这个命令。

执行外部命令

你需要使用fork()exec()等函数来执行一个外部命令——如果执行失败,则输出%s: no such command换行。注意,这里输入的命令采用的是 DOS 格式,而不是 Unix 格式——运行当前目录下可执行文件args应该直接输入args而非./args,因此你 不能使用system()函数来执行该命令,否则将通不过判题测试。

文件夹下有一个args.cpp文件,你可以调用g++(注意不是gcc,因为是 C++ 代码)编译它,然后试着用你的 shell 来执行这个程序。这个程序会输出一行Hello world!然后依次输出它接收的每一个参数。

如果外部命令无法执行,则输出XXX: no such command并换行,其中XXX表示输入的完整外部命令。注意,你必须确保无论执行成功还是失败,都 不要有额外的子进程留下

即使外部命令未正确执行,也依然将这条命令放入命令栈中。

本地判分

/include文件夹内包含了 头文件(header file) ,头文件声明了函数和类;

/src文件夹内含有具体的 .c,其中包含了对相关函数的实现——你需要实现的函数在shell.clog.c中。

主函数保存在main.c中。不要擅自修改主函数代码! 否则可能会导致你的代码不能通过判题。

在自测时你可以在终端中输入make来编译执行本地判分器,输入make clean可以清空之前编译出的文件。其它的make指令可以在Makefile中查看。

编译出的可执行文件名为main,在运行make之后,你可以直接执行./main来直接运行你的 shell。

提交与在线判分

正如前面提到的,这次作业由在线判分器进行自动判分。

在这次任务中,你只能修改 shell.c 和 log.c 文件,对其他文件进行的修改将会被覆盖。你不能通过standard output输出任何额外的内容(这意味着你 不可以 使用printf或类似的函数输出多余的信息),因为这会导致在线判分器判分失误从而影响你的成绩。在你提交作业后,自动判分器将针对你提交的内容进行正确性测试与内存测试——本地判题将只进行正确性测试,一共占 45%45\%45% 的成绩。

在你已经通过正确性测试的前提条件下,你的程序接下来将会接受内存测试——如果没有内存泄露的话你将获得额外 20%20\%20% 成绩。至此,你可以获得基础的 65%65\%65% 的成绩。

在文件夹下有一个空的fbomb.txt,输入任意内容让该文件不为空,就可以进行fork bomb测试——通过测试后,你将获得剩余的 35%35\%35% 的成绩。

注意,你必须确保你的fork实现是正确的——如果产生了额外的进程,无法终止的话判题器将不能正常结束,被系统杀死,然后你将 无法得到任何分数

注 1:你可以反复提交评分,你的最终分数将以最高一次提交得分为准。

注 2:本项目作业需要你独立完成,使用他人提供的代码进行提交(无论是否为最高分提交)都将被视为违背学术纯洁性的行为。