操作系统整合性shell用户接口和实践:

写在开篇:这是我的操作系统课程设计~~

目录:

  • 操作系统整合性shell用户接口和实践:
    • (一). 摘要:
    • (二). 设计目的:
    • (三). 设计内容与要求:
    • (四). 设计原理:
      • 一.exit:结束程序执行
        • 1.主要代码实现:
        • 2.具体分析:
      • 二.history:显示历史纪录
        • 1.主要代码实现:
        • 2.具体分析:
      • 三.!!:执行最近进行过的指令,如果没有历史执行纪录,则会显示没有指令可以执行的讯息
        • 1.主要代码实现:
        • 2.具体分析:
      • 四.!N:执行第N笔历史纪录,如果历史纪录没有第N笔,则会显示错误讯息没有这一笔历史纪录可供再执行一次
        • 1.主要代码实现:
        • 2.具体分析:
      • 五.Ls,ls -l 及ls -l|more: ls用于显示指定工作目录下之内容(列出目前工作目录所含之文件及子目录)。Ls -l是除了文件名称外,也将文件形态,权限,拥有者,文件大小等详细资讯列出。Ls -l|more用于将ls -l的内容显示出来后,可以按回车enter来显示下一页
        • 1.主要代码实现:
        • 2.具体分析:
      • 六.mkdir: 创建一个目录
        • 1.主要代码实现:
        • 2.具体分析:
      • 七.rmdir: 删除一个空目录
        • 1.主要代码实现:
        • 2.具体分析:
      • 八.(1)ps:观察系统所有的进程数据。 (2)ps axjf:连同部分进程树状态显示。 a:不与terminal有关的所有process。 u:有效使用者(effective user)相关的process。 x:通常与a这个参数一起使用,可列出较完整信息。 l:较长、较详细的将该PID的的信息列出。 j:工作的格式(jobs format)。 f:做一个更为完整的输出。 (3)ps aux:ps aux会依照PID的顺序来排序显示
        • 1.主要代码实现加具体分析:
      • 九.top:用来监控linux的系统状况,是常用的性能分析工具,能够实时显示系统中各个进程的资源占用情况
        • 1.主要代码实现:
        • 2.简单说明:
      • 十.cal: cal命令可以用来显示公历(阳历)的日历
        • 1.主要代码实现:
        • 2.具体分析:
      • 十一. whoami:用于显示自身用户名称
        • 1.主要代码实现:
        • 2.具体实现:
      • 十二. date: date可以用来显示或设定系统的日期与时间
        • 1.主要代码实现:
        • 2.具体分析:
      • 十三. pwd: 用来查看当前工作目录的完整路径
        • 1.主要代码实现:
        • 2.具体分析:
      • 十四. mv: 用来为文件或目录重命名,或将文件或目录移动到其他目录
        • 1.主要代码实现及具体分析:
      • 十五. cp: 命令主要用于复制文件或者目录
        • 1.主要代码实现:
        • 2.具体分析:
      • 十六. file: 命令用于辨识文件类型,通过file指令,我们得以辨识该文件的类型
        • 1.主要代码实现:
        • 2.具体分析:
      • 十七. cat : 命令用于连接文件并打印到标准输出设备上
        • 1.主要代码实现:
        • 2.具体分析:
      • 十八. rm: 命令用于删除一个文件或者目录
        • 1.主要代码实现:
        • 2.具体实现:
    • (五). 流程图:
    • (六). 测试结果与说明:
      • 1.cat功能测试:
      • 2.ls功能测试:
      • 3.ls -l功能测试:
      • 4.ls -l |more功能测试:
      • 5.cat功能测试:
      • 6.touch功能测试:
      • 7.cp功能测试:
      • 8.date功能测试:
      • 9.file功能测试:
      • 10.mkdir功能测试:
      • 11.mv功能测试:
      • 12.ps功能测试:
      • 13.pwd功能测试:
      • 14.rm功能测试:
      • 15.rmdir功能测试:
      • 16.whoami功能测试:
      • 17.top功能测试:
      • 18.history+!!+!N功能测试:
      • 19.exit功能测试:
    • (七). 写在最后:
      • 源码:放在Gayhub上~~
      • see you next blog~~

(一). 摘要:

要能完全理解操作系统,最好的方法就是实践出一个操作系统。因此在这一次的课程设计,主要就是实践操作系统的一些命令。其中shell在UNIX或Linux操作系统中是一种很重要的使用者接口,可以透过命令行的方式输入指令。这个项目希望可以实现shell的操作方式,并且可以将所有的执行记录下来,保存程历史纪录,这跟现行所有操作系统保存系统的log档案是一样的道理。在这个实践过程,能熟悉Linux的指令操作,同时也能以指令操作了解进程的产生、进程的管理,以及系统的调用等功能,充分了解操作系统的指令运行。

(二). 设计目的:

  1. 能熟悉操作系统的实践
  2. 能孰悉Linux的shell指令
  3. 能实践整合性的操作系统shell用户接口指令设计
  4. 能实现操作系统的历史纪录
  5. 能了解和实现进程如何产生以及运作
  6. 能实现进程产生
  7. 能实现操作系统功能调用

(三). 设计内容与要求:

(1). ps: 观察系统所有的进程数据。
(2). ps axjf: 连同部分进程树状态显示。
(3). ps aux: ps aux会依照PID的顺序来排序显示。
(4). ls -l: 将文件信息详细列出来
(5). ls -l|more:在ls -l的基础上加上more的功能,自己实现时一条一条按回车输出
(6). top: 监控linux的系统情况
(7). cal: 查看公历日历
(8). whoami: 查看当前用户名
(9). date: 实现系统的日期与时间
(10). pwd: 显示当前整个路径名
(11). mv: 给文件重命名或移动文件
(12). cp: 复制文档
(13). file: 用于辨识文件类型
(14). cat: 连接文件并打印到标准输出设备上
(15). rm: 删除文档

  1. ps:观察系统所有的进程数据。
  2. ps axjf:连同部分进程树状态显示。
  3. ps aux:ps aux会依照PID的顺序来排序显示。
  4. ls -l:将文件信息详细列出来 。
  5. ls -l|more:在ls -l的基础上加上more的功能,自己实现时一条一条按回车输出 。
  6. top:监控linux的系统情况。
  7. cal:查看公历日历。
  8. whoami:查看当前用户名。
  9. date:实现系统的日期与时间。
  10. pwd:显示当前整个路径名。
  11. mv:给文件重命名或移动文件。
  12. cp:复制文档。
  13. file:用于辨识文件类型。
  14. cat:连接文件并打印到标准输出设备上。
  15. rm:删除文档。

(四). 设计原理:

一.exit:结束程序执行

1.主要代码实现:
//退出shell
void quitSHELL()
{printf("I`m Chinese! 再见\n");exit(1);
}
2.具体分析:

在shell.cpp中实现一个函数接口quitSHELL(),当shell.cpp调用该函数时,打印一行“I’m Chinese!再见”,内部再调用exit()函数退出程序。原理很简单,这里无须赘述。

二.history:显示历史纪录

1.主要代码实现:
/查看history
void historySHELL()
{if(_history.second.size() == 0){printf("不存在历史记录\n");return;}auto iter = _history.second.begin();for(int i = 1 + _history.first; iter != _history.second.end(); ++iter, ++i){printf("%4d %s\n", i, *iter);}
}//添加history
void historyADD(const char* pbuf, const int buf_len)
{//new一个char*出来方便储存char* temp_buf = new char[buf_len];strcpy(temp_buf, pbuf);temp_buf[buf_len-1] = 0;//如果已储存消息条数小于10则直接添加if(_history.second.size() < 10){ //入表_history.second.push_back(temp_buf);}//否则前端+1 弹表头,入表尾else{//起始序号++++_history.first;//delete掉头节点 char* t = _history.second.front();_history.second.pop_front();delete[] t;//入表_history.second.push_back(temp_buf);}
}
2.具体分析:

通过实现一个historySHELL()的函数接口,判断_history_second.size()是否为0,若为0,则表明在此之前并没有输出执行过任何命令,那么我们打印“不存在任何历史记录”,否则,也就是_history_second.size()不等于0,那么我们进一步判断。通过algorithm内置的.begin()接口得到一个指向开始的迭代器,进而利用for循环从头遍历到尾部,也就是从_history_second.begin()到_history_second.end(),依次输出在此之前执行过的指令信息。
当然,我们也严格根据老师所给的要求实现了如下功能:
创建一个historyADD()函数来添加任何一个执行过的执行信息并存储起来。
我们动态开辟一块空间来存储history的信息。

判断一下已经存储过的信息条数是否小于10,如果小于10条信息,那我们可以直接利用尾插法进行信息的插入。

如果消息条数大于10,让前端表头+1,也就是起始序号++,把原来的头节点删除,创建一个新的头节点,然后再进行入表操作。

三.!!:执行最近进行过的指令,如果没有历史执行纪录,则会显示没有指令可以执行的讯息

1.主要代码实现:
//!!
void rollbackSHELL()
{char buf[1024];//获取最后一条记录strcpy(buf, _history.second.back());//执行命令runCMD((const char*)buf);
}
2.具体分析:

实现一个rollbackSHELL()函数,首先创建一个buf[]数组来存储信息,利用stdlib.h头文件内置的拷贝函数strcpy()将_history.second.back()最后一条信息记录拷贝到buf[1024]数组中,随后调用runCMD()执行相关操作。

runCMD的实现代码如下:

//执行指定命令
void runCMD(const char* cmd)
{//命令长度int buf_len = strlen(cmd);//储存命令char buf[1024];strcpy(buf, cmd);buf[buf_len] = '\n';//储存切割好的命令char* MyArgv[10] = {0};//将读取到的字符串分成多个字符串int argc = cutCMD(buf, MyArgv);//如果是shell自带的命令的话直接执行int temp = judgeCMD(MyArgv);if(temp > 0){switch(temp){//退出shellcase 1: quitSHELL(); break;//查看historycase 2: historySHELL(); break;//!相关无法被执行case 3: case 4: printf("!相关命令无法被重复执行\n"); break;}}//否则fork切换进程执行命令else{//若要求后台执行if(!strcmp("&", MyArgv[argc - 1])){pthread_t id;pthread_create(&id, NULL, runADD, (void*)buf);//线程等待pthread_join(id, NULL);//printf("后台执行结束    %s\n", buf);}else{//利用fork新建进程执行命令forkCMD(MyArgv);}}
}

runCMD()接受一个传来的指针,首先通过strlen()得到该数组的长度,其次再创建一个buf数组,将得到的信息拷贝到新建的buf数组中去,将buf数组的尾部赋值为’\n’,使其变成一个字符串。

储存好已经分配好的命令,将读取到的字符串分割成多个字符串,判断如果是shell自带的命令则直接执行,如果不是则进行后续操作。

如果得到的返回值大于0,则进入switch操作,当是switch为1时,直接调用quitSHELL函数退出程序,如果switch等于2,则调用historySHELL函数查看历史执行过的命令,返回值为表明执行了重复命令,此时我们给上一句提示:相关命令无法被重复执行!
当接受到的返回值小于等于0时,执行fork切换进程操作。
当!strcmp("&", MyArgv[argc - 1])为真时,执行&实现后台执行,为假时,利用fork新建进程执行命令。

四.!N:执行第N笔历史纪录,如果历史纪录没有第N笔,则会显示错误讯息没有这一笔历史纪录可供再执行一次

1.主要代码实现:
//!N
void rollbackN(const char* cmd)
{int t = strlen(cmd);//回溯指令idint num = 0;//获取idfor(int i = 1; i < t; ++i){if(cmd[i] >= '0' && cmd[i] <= '9') {num = num * 10 + cmd[i] - '0';}else{printf("回溯指令行数输入有误\n");return;}}//所记录指令的id起始int start = _history.first + 1;int end = start + _history.second.size() - 1;//边缘判定if(_history.second.size() == 0){printf("历史记录为空 无法执行\n");return;}if(num < start || num > end){printf("没有id为%d的历史记录 无法执行\n", num);return;}//找到这条指令auto iter = _history.second.begin();for(int i = 0; iter != _history.second.end() && i != num - start; ++i){++iter;}//执行命令runCMD((const char*)*iter);
}
2.具体分析:

实现rollbackN函数来接受外部传过来的指针,用strlen函数来得到这个指针指向信息的长度。定义一个回溯变量num,进而获取id。

分别定义变量记录指令的id起点和终点,进行边缘的判定,当判断为0时,我们打印“历史纪录为空 无法执行”,结束掉程序。在不为空的情况下,当num<start或者num>end时,我们打印没有对应的历史记录。
否则便是通过遍历找到了这条指令,迭代器iter再传给runCMD(),执行该指定的命令。这里在!!中已经分析过,二者没有任何区别,故在此不再复述。

以上操作我都在shell.cpp里实现,故在此做一个总结:
通过对linux下shell实现原理的探索,我们决定实现:

  1. 以shell系统为核心文件,通过核心内的fork/exec相关函数跳转到键入命令的自实现程序,从而实现shell的命令执行功能。
  1. 自实现命令程序相关:通过whereis这个命令,我们可以得知linux下命令程序通常是存储在/usr/bin文件夹下。
    故我们组的自实现命令程序被存放在shell文件夹内的./bin目录下。
  1. 我们通过每一个独立自实现程序的main函数内的argc与argv参数,传入命令参数,从而根据参数执行不同的内容。
    例如在ls命令程序内,通过argv参数得知执行的为ls-l还是ls或是ls -l | more。
  1. 实验报告中要求实现history与!相关内容,由于我的shell为独立程序,与自实现命令程序相互独立,故我把history与!的相关内容实现在shell内。
    由此shell在接收命令后,首先应该判断为内部命令或是外部命令。若为外部命令则跳转外部程序执行,若为内部命令(history/!!/!N)则直接执行。
  1. history的数据结构我采用pair<int,list>,简单明了,int为程序history记录的起始id(因为要求只存10条消息),list则为历史输入字符串链表。
    当list内不足10条时,则直接在list内添加;若已经为10条,则int存储的id++,list链表pop出头信息,把新信息push进链表尾,即可实现history的储存。
    当执行history命令时,直接遍历list即可。执行!!与!N时,通过list读取输入内容,随后执行该命令即可。
  1. 这里用到了切割命令字符串通过判定空格来切割。
  1. fork和exec原理啥的,这是linux下自带的函数,以便于我们去很好的模拟linux的原理操作。
  1. &后台执行:如果检测到最后一个参数为&,便执行&后台并行操作。具体使用到create新线程来保证输入不会被阻塞,从而实现后台执行。

五.Ls,ls -l 及ls -l|more: ls用于显示指定工作目录下之内容(列出目前工作目录所含之文件及子目录)。Ls -l是除了文件名称外,也将文件形态,权限,拥有者,文件大小等详细资讯列出。Ls -l|more用于将ls -l的内容显示出来后,可以按回车enter来显示下一页

1.主要代码实现:
/*
* 简单实现ls命令
* 1509寝W组
* 2021/6/27
*/
#include <stdio.h>
#include <dirent.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <sys/stat.h>   // 这个头文件用来得到文件的详细信息
#include <time.h>       // 时间头文件
#include <pwd.h>        // 用来得到用户名
#include <grp.h>        // 用来取得组名// argv, argc
// 结构体,用来存储要输出的每个属性值
struct attribute
{char mode[10];       // 文件属性和权限int  links;          // 链接数char user_name[20];  // 用户名char group_name[20]; // 所在的用户组grouplong size;           // 文件大小char mtime[20];      // 最后修改的时间char filename[255];  // 文件名char extra[3];       // 用来显示时候要加 "*"(可以执行的文件) 或者 "/" (目录) 的额外字符串
};// 计算整数 n 有几位
int f(long n)
{int ret = 0;while(n) {n = n / 10;++ ret;}return ret;
}// 得到终端的列数和行数./
void getTerminatorSize(int *cols, int *lines)
{#ifdef TIOCGSIZE  struct ttysize ts;  ioctl(STDIN_FILENO, TIOCGSIZE, &ts);  *cols = ts.ts_cols;  *lines = ts.ts_lines;
#elif defined(TIOCGWINSZ)  struct winsize ts;  ioctl(STDIN_FILENO, TIOCGWINSZ, &ts);  *cols = ts.ws_col;  *lines = ts.ws_row;
#endif /* TIOCGSIZE */  }// 由 int 型的 mode,得到实际要显示的字符串
void mode2str(int mode, char str[])
{strcpy(str, "----------\0"); if(S_ISDIR(mode)) str[0] = 'd';if(S_ISCHR(mode)) str[0] = 'c';if(S_ISBLK(mode)) str[0] = 'b';if(S_ISLNK(mode)) str[0] = 'l';if(mode & S_IRUSR) str[1] = 'r';if(mode & S_IWUSR) str[2] = 'w';if(mode & S_IXUSR) str[3] = 'x';if(mode & S_IRGRP) str[4] = 'r';if(mode & S_IWGRP) str[5] = 'w';if(mode & S_IXGRP) str[6] = 'x';if(mode & S_IROTH) str[7] = 'r';if(mode & S_IWOTH) str[8] = 'w';if(mode & S_IXOTH) str[9] = 'x';
}// 根据用户的 id 值,得到用户名 user name
void uid2str(uid_t uid, char *user_name) /* 将uid转化成username */
{struct passwd *pw_ptr;pw_ptr = getpwuid(uid);if( pw_ptr == NULL) {sprintf(user_name, "%d", uid);} else {strcpy(user_name, pw_ptr->pw_name);}
}// 根据用户组的 id 值,得到用户组名 group name
void gid2str(gid_t gid, char *group_name) /* 将uid转化成username */
{struct group *grp_ptr;grp_ptr = getgrgid(gid);if( grp_ptr == NULL) {sprintf(group_name, "%d", gid);} else {strcpy(group_name, grp_ptr->gr_name);}
}// 时间的格式化字符串, 注意这里我把前面的星期和后面的年份都去掉了
void time2str(time_t t, char *time_str)
{strcpy( time_str, ctime(&t) + 4);time_str[12] = '\0';
}// 要显示的某一个文件详细信息,并把信息放在结构体 attribute 中
void ls_long_file(char *dirname, char *filename, struct attribute *file_attri)
{// 根据文件夹名和文件名得到全名char fullname[256];strcpy(fullname, dirname);strcpy(fullname + strlen(dirname), filename);struct stat mystat;if ( stat(fullname, &mystat) == -1) {printf("ls_long_file: stat error\n");       } else {// 这里参考 <stat.h> 头文件int mode   = (int)  mystat.st_mode;int links  = (int)  mystat.st_nlink;int uid    = (int)  mystat.st_uid;int gid    = (int)  mystat.st_gid;long size  = (long) mystat.st_size;long mtime = (long) mystat.st_mtime;char str_mode[10];          /* 文件类型和许可权限, "drwxrwx---" */char str_user_name[20];char str_group_name[20];char str_mtime[20];mode2str(mode, str_mode);uid2str(uid, str_user_name);gid2str(gid, str_group_name);time2str(mtime, str_mtime);char extra[3] = "\0\0";if (str_mode[0] == 'd') {extra[0] = '/';} else if (str_mode[0] == '-' && str_mode[3] == 'x') {extra[0] = '*';}// 存储在结构体中strcpy(file_attri->mode, str_mode);file_attri->links = links;strcpy(file_attri->user_name, str_user_name);strcpy(file_attri->group_name, str_group_name);file_attri->size = size;strcpy(file_attri->mtime, str_mtime);strcpy(file_attri->filename, filename);strcpy(file_attri->extra, extra);}
}// struct
struct attribute file_attribute[200]; // maximum 200void lsLong(char *dirname, int mode)
{DIR *mydir = opendir( dirname );            /* directory */ char filename[20];int file_num = 0;if (mydir == NULL) {// 显示单个文件的详细信息strcpy(filename, dirname);ls_long_file("./", filename, &file_attribute[0]);++ file_num;} else {// 考虑用户输入文件夹没有输入反斜杠的情况int len = strlen(dirname);if (dirname[len - 1] != '/'){dirname[len] = '/';dirname[len+1] = '\0';}// 循环得到当前目录下的所有文件名,并存储在自定义的结构体中struct dirent *mydirent;            /* file */while ( (mydirent = readdir( mydir )) != NULL) {char filename[20];strcpy(filename, mydirent->d_name);// 不能为隐藏文件if (!strcmp(filename, ".") || !strcmp(filename, ".") || filename[0] != '.') {ls_long_file(dirname, filename, &file_attribute[file_num ++]);}   }closedir( mydir );}// 按照文件名排序struct attribute temp;char filename1[20];char filename2[20];int i, j;for (i = 0; i < file_num; ++i){for (j = i + 1; j < file_num; ++ j){strcpy(filename1, file_attribute[i].filename);strcpy(filename2, file_attribute[j].filename);if ( strcmp(filename1, filename2) > 0){temp = file_attribute[i];file_attribute[i] = file_attribute[j];file_attribute[j] = temp;}}           }// 格式化输出时,考虑每个属性值的范围int max_mode = 0;int max_links = 0;int max_user_name = 0;int max_group_name = 0;int max_size = 0;int max_mtime = 0;int max_filename = 0;int max_extra = 0;for (i = 0; i < file_num; ++ i){if ( max_mode < strlen(file_attribute[i].mode) ) {max_mode = strlen(file_attribute[i].mode);}if (max_links < f(file_attribute[i].links)) {max_links = f(file_attribute[i].links);}if ( max_user_name < strlen(file_attribute[i].user_name) ) {max_user_name = strlen(file_attribute[i].user_name);}if ( max_group_name < strlen(file_attribute[i].group_name) ) {max_group_name = strlen(file_attribute[i].group_name);}if (max_size < f(file_attribute[i].size)) {max_size = f(file_attribute[i].size);}if ( max_mtime < strlen(file_attribute[i].mtime) ) {max_mtime = strlen(file_attribute[i].mtime);}if ( max_filename < strlen(file_attribute[i].filename) ) {max_filename = strlen(file_attribute[i].filename);}if ( max_extra < strlen(file_attribute[i].extra) ) {max_extra = strlen(file_attribute[i].extra);}   }//ls普通输出if(mode == 1){for (i = 0; i < file_num; ++i){char format[50];// 定义输出的格式sprintf(format, "%%%ds %%%dd %%%ds %%%ds %%%dld %%%ds %%s%%s\n", max_mode, max_links, max_user_name, max_group_name, max_size,max_mtime);// 按照定义的输出格式输出printf(format, file_attribute[i].mode, file_attribute[i].links, file_attribute[i].user_name, file_attribute[i].group_name, file_attribute[i].size, file_attribute[i].mtime, file_attribute[i].filename, file_attribute[i].extra);}}//ls |more 输出if(mode == 2){//当前默认设置为初始显示10行 随后一行一行显示int page = file_num > 9 ? 9 : file_num;for (i = 0; i < page; ++i){char format[50];// 定义输出的格式sprintf(format, "%%%ds %%%dd %%%ds %%%ds %%%dld %%%ds %%s%%s\n", max_mode, max_links, max_user_name, max_group_name, max_size,max_mtime);// 按照定义的输出格式输出printf(format, file_attribute[i].mode, file_attribute[i].links, file_attribute[i].user_name, file_attribute[i].group_name, file_attribute[i].size, file_attribute[i].mtime, file_attribute[i].filename, file_attribute[i].extra);}for(i = page; i < file_num; ++i){char format[50];// 定义输出的格式sprintf(format, "%%%ds %%%dd %%%ds %%%ds %%%dld %%%ds %%s%%s", max_mode, max_links, max_user_name, max_group_name, max_size,max_mtime);// 按照定义的输出格式输出printf(format, file_attribute[i].mode, file_attribute[i].links, file_attribute[i].user_name, file_attribute[i].group_name, file_attribute[i].size, file_attribute[i].mtime, file_attribute[i].filename, file_attribute[i].extra);getchar();} }
}// 处理不带 -l 参数的 ls 命令
void lsShort(char *dirname)
{DIR *mydir = opendir(dirname);          /* directory */ // 用来暂时存储要显示的目录下的所有文件名,可以看到最大可以支持200个文件,但是每个文件名最长为20char filenames[200][20];int file_num = 0;if (mydir == NULL) {// 直接显示该文件printf("%s\n\n", dirname);return ;} else {// 循环检查下面有多少文件,并把文件名全部放到filenames数组里struct dirent *mydirent;            /* file */while ( (mydirent = readdir( mydir )) != NULL) {char fname[20];strcpy(fname, mydirent->d_name);    if (fname[0] != '.' ) {strcpy(filenames[file_num], mydirent->d_name);file_num ++;}   }closedir( mydir );}// 文件名排序int i, j;char temp[20];for(i = 0; i < file_num; ++ i) {for(j = i+1; j < file_num; ++ j) {if(strcmp(filenames[i], filenames[j]) > 0) {strcpy(temp, filenames[i]);strcpy(filenames[i], filenames[j]);strcpy(filenames[j], temp);}}}// 确定所有文件里面最长的文件名的长度int max_len = 0;for(i = 0; i < file_num; ++ i) {int len = strlen(filenames[i]);if(len > max_len) {max_len = len;}}// 得到当前终端的分辨率int cols = 80;int lines = 24;getTerminatorSize(&cols, &lines);char format[20];sprintf(format, "%%-%ds  ", max_len);// 格式化输出,当长度大于终端的列数时,换行int current_len = 0;for(i = 0; i < file_num; ++ i) {printf(format, filenames[i]);current_len += max_len + 2;if(current_len + max_len + 2 > cols) {printf("\n");current_len = 0;}   }printf("\n");
} int main(int argc, char **argv)
{int i;//lsif (argc == 1) {                   lsShort("./");              return 0;}//ls -lif (argc == 2 && !strcmp(argv[1], "-l") ) {           lsLong("./", 1);return 0;}//ls -l | more   ls -l |moreif ( (argc == 4 && !strcmp(argv[1], "-l") && !strcmp(argv[2], "|") && !strcmp(argv[3], "more")) || (argc == 3 && !strcmp(argv[1], "-l") && !strcmp(argv[2], "|more")) ){lsLong("./", 2);return 0;}//ls -l directory nameif (argc > 2 && !strcmp(argv[1], "-l") ) {           for(i = 2; i < argc; ++ i) {printf("%s:\n", argv[i]);lsLong(argv[i], 1);if(i != argc - 1)printf("\n");   }return 0;   } //ls directory nameelse {for (i = 1; i < argc; ++ i) {printf("%s:\n", argv[i]);lsShort(argv[i]);if(i != argc - 1)printf("\n");   }return 0;}return 0;
}
2.具体分析:

新建结构体attribute储存各个系统函数获取的内容

检测argc和argv,若命令为ls则简单遍历目标文件夹内的内容输出,命令为ls-l则通过上面的结构体储存的内容,遍历输出,若为ls -l | more,则对命令的显示做出限制,随着回车来一条一条的显示。

创建一个结构体,在其中分别定义文件属性和权限,链接数,用户名,所在的用户组,文件大小,最后的修改时间,文件名,用来显示的时候要加“*”(可执行的文件)或者“/”(目录)的额外字符。
写一个f()函数计算传进来的整数n有几位。
实现一个getTerminatorSize()来得到终端的列数和行数。
再由int型的mode,得到实际要显示的字符串,如dcbl,rwx等。
随后又根据用户的id值,得到用户名user name,根据用户组的id值,得到用户组名group name。
实现一个time2str()函数将时间的格式化字符串,注意,这里我把前面的星期和后面的年份都去掉了
要显示的某一个文件的详细信息,并把信息放在结构体attribute中。
最后再判断ls普通输出或者是ls |more输出,再单独处理不带 -l参数的ls命令,将文件名排序后输出打印到屏幕上。

其他小细节实现,注释中已经写的很清楚了,这里不再过多重复。
(ls代码借鉴了老师发的代码,ls -l与ls -l|more为自己实现)

六.mkdir: 创建一个目录

1.主要代码实现:
/*
* 简单实现mkdir命令
* 1509寝W组
* 2021/6/28
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>//main
int main(int argc,char *argv[])
{if(argc != 2){fprintf(stdout, "参数错误:参数数量错误\n");return 0;}//创建权限为0600的文件夹if(mkdir(argv[1], O_CREAT | 0600) == -1){fprintf(stdout, "创建失败\n");return 0;}printf("your operation is successful!\n");return 0;
}
2.具体分析:

因为老师要求实现的功能里面没有mkdir,所以此功能主要是在linux下完成了内置函数mkdir()的调用。Mkdir需要传递两个参数,一个是mkdir命令,另一个是需要创建的文件名。判断一下如果argc不等于2,则打印参数错误:参数数量错误,结束掉程序。否则,也就是参数正确,则创建一个权限为0600的文件夹,如果创建失败,抛出创建失败,如果成功,抛出your operation is successful !

七.rmdir: 删除一个空目录

1.主要代码实现:
/*
* 简单实现rmdir命令
* 1509寝W组
* 2021/6/28
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <dirent.h>
#include <unistd.h>
#define PATH_SIZE 4094
//rmdir
void my_rmdir(const char * path);
//main
int main(int argc, char const *argv[])
{if (argc != 2){fprintf(stdout, "参数错误:参数数量错误\n");return 0;}//rmdirmy_rmdir(argv[1]);return 0;
}
//rmdir
void my_rmdir(const char * path)
{DIR *dirp;//打开文件夹dirp = opendir(path);if (NULL == dirp){fprintf(stdout, "参数错误:路径出错\n");return;}//rm文件struct dirent *entry;int ret;while (1){entry = readdir(dirp);//文件夹为空if (NULL == entry){break;}//skip . & ..if (0 == strcmp(".", entry->d_name) || 0 == strcmp("..", entry->d_name)){continue;}//删文件char buf[PATH_SIZE];snprintf(buf, PATH_SIZE, "%s/%s", path, entry->d_name);ret = remove(buf);if (-1 == ret){//若为目录删子目录if (ENOTEMPTY == errno){my_rmdir(buf);continue;}perror(buf);return;}fprintf(stdout, "rm file: %s\n", buf);}//closeclosedir(dirp);//删除文件夹ret = rmdir(path);if (-1 == ret){perror(path);return;}fprintf(stdout, "rm dir: %s\n", path);
}
2.具体分析:

与mkdir类似,因为老师要求实现的功能里面没有rmdir,为了配合已经是新年的mkdir,故又再实现了一个rmdir,同理,rmdir需要传递两个参数,一个是rmdir命令,另一个是删除创建的文件名。判断一下如果argc不等于2,则打印参数错误:参数数量错误,结束掉程序。否则,也就是参数正确,则执行 my_rmdir(argv[1])函数,rmdir首先跳过./ …/ ,如果有子文件,snprintf拼接目标文件路径,remove删除文件。如果为子文件夹则递归循环删除,最后删除总文件夹。更细致的分析在注释中都有提到。

八.(1)ps:观察系统所有的进程数据。 (2)ps axjf:连同部分进程树状态显示。 a:不与terminal有关的所有process。 u:有效使用者(effective user)相关的process。 x:通常与a这个参数一起使用,可列出较完整信息。 l:较长、较详细的将该PID的的信息列出。 j:工作的格式(jobs format)。 f:做一个更为完整的输出。 (3)ps aux:ps aux会依照PID的顺序来排序显示

1.主要代码实现加具体分析:

建立一个ps_info结构体来存储信息

//信息结构体
typedef struct ps_info
{char pname[MAX_LEN];char user[MAX_LEN];int  pid;int  ppid;char state;struct ps_info *next;
}mps;

建立一个结构体指针来获取信息,将各种相关的函数布局完成,例如用来read,由进程uid得到进程的所有者user,判断name是否合法,将结果进行显示,最后是对exec的系统调用。

//获取信息
mps* trav_dir(char dir[]);//read
int read_info(char d_name[],struct ps_info *p1);//由进程uid得到进程的所有者user
void uid_to_name(uid_t uid, struct ps_info *p1);//判断name是否合法
int is_num(char *);//显示
void print_ps(struct ps_info *head);//exec
void exec(int argc, char* argv[]);

trav_dir()函数的具体实现:调用DIR结构体创建一个结构体指针,建立一个链表,遍历/proc目录下所有进程目录,判断目录名字是否为纯数字,如果是且p1== NULL,则抛出分配失败。如果read_info(direntp->d_name,p1)!=0为真,则抛出读取进程信息error,否则便可插入新节点。

//获取信息
mps* trav_dir(char dir[])
{DIR *dir_ptr;mps *head,*p1,*p2;struct dirent *direntp;struct stat infobuf;if((dir_ptr=opendir(dir))==NULL)fprintf(stderr,"dir error %s\n",dir);else{head=p1=p2=(struct ps_info *)malloc(sizeof(struct ps_info));    //建立链表while((direntp=readdir(dir_ptr)) != NULL)               //遍历/proc目录所有进程目录{if((is_num(direntp->d_name))==0)                   //判断目录名字是否为纯数字{if(p1==NULL){printf("malloc error!\n");exit(0);}if(read_info(direntp->d_name,p1)!=0)         //获取进程信息{printf("read_info error\n");exit(0);}p2->next=p1;                        //插入新节点p2=p1;p1=(struct ps_info *)malloc(sizeof(struct ps_info));}}}p2->next=NULL;return head;
}

由进程uid可以得到进程所有者user

//由进程uid得到进程的所有者user
void uid_to_name(uid_t uid, struct ps_info *p1)
{struct passwd *pw_ptr;static char numstr[10];if((pw_ptr = getpwuid(uid)) == NULL){sprintf(numstr,"%d",uid);strcpy(p1->user, numstr);}elsestrcpy(p1->user, pw_ptr->pw_name);
}

更为具体的分析我都放在了对应的.cpp文件注释里,这里不再过多说明。
总的来说,ps就是新建信息结构体struct ps_info储存获取的各进程内信息
通过argc和argv的内容,决定输出的内容。

九.top:用来监控linux的系统状况,是常用的性能分析工具,能够实时显示系统中各个进程的资源占用情况

1.主要代码实现:
/*
* 简单实现top命令
* 1509寝W组
* 2021/6/28
*/
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<wait.h>//main
int main(int argc,char * argv[])
{/*/fork新的进程int id = fork();if(id == 0){//child,执行替换操作//系统调用execvp(argv[0], argv);perror("error");exit(1);}else{//fatherwait(NULL);}*///系统调用execvp(argv[0], argv);return 0;
}
2.简单说明:

top命令我们是直接通过系统调用的,execvp(argv[0],argv);top命令我们尝试了很久,苦于没有办法才出此下策,希望老师能体谅。
虽然没有通过代码很好的模拟实现出来,但我们有自己的一点想法可以分享:
Linux中的top命令究竟是怎么是实现的呢?
top 是procs的一部分,常用来查看系统的负载情况。Procs中除了top外,还包括ps,free,w,uptime,watch,sysctl等常用的命令。了解top命令除了直接在terminal使用之外,就是top的官方文档和源代码了。
不过在此之前,我们可以用strace top看下top命令到底做了些什么?
1.首先会读取一系列的依赖文件:

2.然后会读取一些系统配置文件:

3.最后就是从/proc目录下读取进程的statm信息:

十.cal: cal命令可以用来显示公历(阳历)的日历

1.主要代码实现:
//获取本月有几天
int monthOfDay(int year, int month);//获取从1900/1/1到目标日期的天数 (1900/1/1 周一)
int getDay(int year, int month);//打印表
void showCal(int year, int month, int day, int days);
2.具体分析:

实现monthOfDay()函数来获取本月有多少天,定义一个day[]数组用于存放1到12月每个月的天数,也就是31,28,31,30,31,30,31,31,30,31,30,31,这里我们默认是平年,当然我们要考虑到闰年的情况,当年份是4的倍数同时又不是100的倍数,或者年份是400的倍数时,可以判定该年是闰年,此时day[1]=29;最后返回该月的天数。

//获取本月有几天
int monthOfDay(int year, int month)
{int day[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };if(year % 400 == 0 || (year % 4 == 0 && year % 100 != 0)){day[1] = 29;//二月}return day[month - 1];
}

获取从1900/1/1到目标日期的天数(1900/1/1 周一),时间戳是从1900年一月一日开始的。(注:时间戳有两种,一种是1970/1/1,一种是1900/1/1) 定义day=0,从1900到当前年份进行for循环,判断当i到达的年份是闰年的话,day+=366天,否则平年+365天。同时,我们也要将当前年份的已经经过的月份遍历,最后day+1就是当前日期,返回该日期,我们用了绿色来使其区别于shell自带的cal与其他日期。

//获取从1900/1/1到目标日期的天数 (1900/1/1 周一)
int getDay(int year, int month)
{int days = 0;for(int i = 1900; i < year; ++i)//年{if(i % 400 == 0 || (i % 4 == 0 && i % 100 != 0)){days += 366;}else{days += 365;}}for(int i = 1; i < month; i++)//月{days = days + monthOfDay(year, i);}days += 1;//日return days;//绿色
}

同时把整个得到的日期表打印出来。获取本月有多少天,整理好打印的位置,循环输出日期,当期日期用绿色标识。

//打印表
void showCal(int year, int month, int day, int days)
{int mdays;//获取本月有几天mdays=monthOfDay(year, month);//打印头printf("      %02d月 %d      \n", month, year);printf("日 一 二 三 四 五 六\n");//把1号置于正确的位置for(int i = 0; i < days % 7; ++i){printf("%2s "," ");}//循环输出日期for(int i = 1; i <= mdays; ++i){if(i == day){printf("\033[1;40;32m%2d\033[0m ",i);//绿色}else{printf("%2d ",i);}if(days % 7 == 6){printf("\n");}days++;}printf("\n\n");
}

总结:cal:

通过time.h内的time_t及tm结构体,获取系统当前时间。
首先计算出1900/1/1到目标日期的天数,从而计算出日历第一行的空格数。
随后计算本月有几天,最后循环输出日期,高亮当前日期即可实现。

十一. whoami:用于显示自身用户名称

1.主要代码实现:
/*
* 简单实现whoami命令
* 1509寝W组
* 2021/6/27
*/
#include<stdio.h>
#include<unistd.h>
#include<pwd.h>int main(int argc, char* argv[])
{struct passwd* pass;  //passwd结构体,内置name,uid,gid,dir等信息pass = getpwuid(getuid());printf("%s\n",pass->pw_name);  //pw_name为用户名return 0;
}
2.具体实现:

Struct passwd结构体中包含:

#include <sys/types.h>
#include <pwd.h>
struct passwd {char *pw_name;                /* 用户登录名 */char *pw_passwd;              /* 密码(加密后) */__uid_t pw_uid;               /* 用户ID */__gid_t pw_gid;               /* 组ID */char *pw_gecos;               /* 详细用户名 */char *pw_dir;                 /* 用户目录 */char *pw_shell;               /* Shell程序名 */
};

新建passwd结构体,getuid获取当前进程uid,getpwuid获取passwd结构体
pw_name即为用户名。
创建一个结构体指针指向该结构体,调用getpwuid()函数,该函数根据传入的用户ID返回指向passwd的结构体 该结构体初始化了里面的所有成员,返回值为一个结构体指针,传入参数为getuid,返回值赋值给创建的指针,最后打印用户名,用pass->pw_name获取用户名。

十二. date: date可以用来显示或设定系统的日期与时间

1.主要代码实现:
/*
* 简单实现date命令
* 1509寝W组
* 2021/6/27
*/
#include<stdio.h>
#include<stdlib.h>
#include<time.h>int main(int argc, char* argv[])
{time_t t;struct tm *p;time(&t);p = gmtime(&t);printf("%04d年 %02d月 %02d日 星期%d %02d:%02d:%02d CST\n",p->tm_year+1900, p->tm_mon+1, p->tm_mday, p->tm_wday==0 ? p->tm_wday+7 : p->tm_wday, p->tm_hour+8, p->tm_min, p->tm_sec);return 0;
}
2.具体分析:

date利用time.h内的time_t和tm结构体获取时间,随后输出。
time_t为长整型的别名,typedef long time_t;
struct tm为定义在time.h中的结构体:

struct tm{int tm_sec;           /* Seconds. [0-60] (1 leap second) */int tm_min;           /* Minutes. [0-59] */int tm_hour;          /* Hours.   [0-23] */int tm_mday;          /* Day.     [1-31] */int tm_mon;           /* Month.   [0-11] */int tm_year;          /* Year - 1900.  */int tm_wday;          /* Day of week. [0-6] */int tm_yday;          /* Days in year.[0-365] */int tm_isdst;         /* DST.     [-1/0/1]*/
}

gmtime()函数接受一个time_t类型的参数,返回一个struct tm* 的结构体指针,该指针指向p,由p->tm_year,p->tm_mon,p->tm_day等可以输出对应的时间信息。

十三. pwd: 用来查看当前工作目录的完整路径

1.主要代码实现:
int main(int argc, char* argv[])
{int bufsize = 128;char *buffer = (char*)malloc(sizeof(char)*bufsize);//动态开辟一个大小为128字节的空间if (!buffer)//当buffer==NULL,分配内存失败,exit(1)退出程序{printf("allocation error1\n");exit(1);}while (1){if(getcwd(buffer, bufsize) == NULL) //getcwd()获取当前工作路径{bufsize += bufsize;buffer = (char*)realloc(buffer, sizeof(char)*bufsize);if (!buffer){printf("allocation error2\n");exit(1);}}else{printf("%s\n", buffer);free(buffer);break;}}return 0;
}
2.具体分析:

主要通过getcwd获取当前工作路径。首先定义一个变量bufsize用来表示开辟内存的大小,动态开辟该大小的空间,若失败,则抛出error。利用getcwd()函数来获取当前目录路径,若失败,则返回false,成功则返回该路径。若size太小会无法保存该地址,返回NULL,这时候便要扩展bufsize,重新realloc()空间,使其能很好地保存该路径,最后打印该路径,不要忘了释放空间。

十四. mv: 用来为文件或目录重命名,或将文件或目录移动到其他目录

1.主要代码实现及具体分析:

首先获取当前文件名 getFileName(),随后将当前文件名拼接到目标路径上(argv[2])。

//获取文件名
char* getFileName(char* fileName)
{char tp[100], *name = (char*)malloc(sizeof(char));//动态开辟内存int i, j=0;for(i = strlen(fileName) - 1; i >= 0; --i){if(fileName[i] == '/'){break;}}

利用rename()函数对文件或文件夹重命名和移动,当新路径文件已存在则会直接覆盖。

//直接使用renameif(rename(argv[1], argv[2]) == -1){fprintf(stdout, "移动失败\n");return 0;}return 0;

很容易实现的功能,不用过多分析,都是常规套路。

十五. cp: 命令主要用于复制文件或者目录

1.主要代码实现:
  //打开原文件if((in_fd = open(argv[1], O_RDONLY)) == -1){fprintf(stdout, "打开文件失败\n");return 0;}//新建复制文件if((out_fd = creat(argv[2], COPY_MODE)) == -1 ){fprintf(stdout, "创建文件失败\n");return 0;}//复制文件内容while((n_chars = read(in_fd, buf, BUFF_SIZE)) > 0 ){if(write(out_fd, buf, n_chars) != n_chars ){fprintf(stdout, "复制中出现错误\n");return 0;}}//读取文件内容错误if(n_chars == -1){fprintf(stdout, "读取文件内容错误\n");return 0;}//关闭文件if(close(in_fd) == -1 || close(out_fd) == -1){fprintf(stdout, "关闭文件出现错误\n");return 0;}
2.具体分析:

Cp:打开原文件,新建复制文件,复制文件内容,关闭原文件,即可实现。
用到creat()函数,可以用来创建一个文件并以只写的方式打开。
用到open()函数,打开和创建文件,可以进行读和写的操作。
用到read()函数,可以读取一个文件,成功则返回读取的字符数,出错返回-1.

十六. file: 命令用于辨识文件类型,通过file指令,我们得以辨识该文件的类型

1.主要代码实现:
/*
* 简单实现file命令
* 1509寝W组
* 2021/6/28
*/
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<wait.h>//main
int main(int argc,char * argv[])
{/*/fork新的进程int id = fork();if(id == 0){//child,执行替换操作//系统调用execvp(argv[0], argv);perror("error");exit(1);}else{//fatherwait(NULL);}*///系统调用execvp(argv[0], argv);return 0;
}
2.具体分析:

file命令我们是直接通过系统调用的,execvp(argv[0],argv);file命令我们尝试了很久,苦于没有办法才出此下策,希望老师能体谅。
虽然没有通过代码很好的模拟实现出来,但我们有自己的一点想法可以分享:
File命令的输出格式是 文件名:文件类型和编码格式,我们可以通过得到文件的文件名与文件类型与编码格式来达到模拟shell的file效果,文件名能很容易的得到,文件类型也应该可以字符串来拼接,唯一没有头绪的是编码格式的get,这个我们小组会好好地想一想解决办法,目前有限的时间内不能实现很抱歉。

十七. cat : 命令用于连接文件并打印到标准输出设备上

1.主要代码实现:
//获取内容
void copy(int fdin, int fdout)
{char buffer[BUFF_SIZE];int size;//读取fdin的内容放在fdout的中while(size = read(fdin, buffer, BUFF_SIZE)){if(write(fdout, buffer, size) != size){fprintf(stdout, "写入错误\n");exit(1);}                                                                                                                                                 }//当读入出现问题if(size<0){fprintf(stdout, "读入错误\n");exit(1);}
}//接收键盘输入与向屏幕输出int fd_in = STDIN_FILENO;int fd_out = STDOUT_FILENO;//1个argcif (argc == 1){copy(fd_in, fd_out);close(fd_in);}//n个argcelse{for(int i = 1; i < argc; ++i){//令输入文件为读入文件fd_in = open(argv[i], O_RDONLY);if(fd_in < 0){fprintf(stdout, "打开%s文件错误\n", argv[i]);continue;}copy(fd_in, fd_out);close(fd_in);}}
2.具体分析:

Cat:STDIN_FILENO;//键盘输入, STDOUT_FILENO;//屏幕输出
通过上面的两个流
将要获取内容的文件的内容read进键盘输入流
随后流入屏幕输出流显示
即可实现cat功能。
定义一个buffer[]数组来储存信息。通过read()函数和write()函数实现将fdin的内容放入fdout中,copy(fd_in,fd_out)接受屏幕输入与输出到屏幕。

十八. rm: 命令用于删除一个文件或者目录

1.主要代码实现:
int rm(char* file_name)
{char file_path[128];strcpy(file_path, file_name);struct stat st;  //找不到文件  if(lstat(file_path, &st) == -1){return -1;}//是否为常规文件if(S_ISREG(st.st_mode)){//unlink失败if(unlink(file_path) == -1){return -1;}    }//是否为文件夹else if(S_ISDIR(st.st_mode)){fprintf(stdout, "无法删除:其为一个目录\n");return -1;}return 0;
}
2.具体实现:

struct stat为sys/types.h内置的一个结构体,作用是找到目标文件。

struct stat
{   dev_t       st_dev;     /* ID of device containing file -文件所在设备的ID*/  ino_t       st_ino;     /* inode number -inode节点号*/    mode_t      st_mode;    /* protection -保护模式?*/    nlink_t     st_nlink;   /* number of hard links -链向此文件的连接数(硬连接)*/    uid_t       st_uid;     /* user ID of owner -user id*/    gid_t       st_gid;     /* group ID of owner - group id*/    dev_t       st_rdev;    /* device ID (if special file) -设备号,针对设备文件*/    off_t       st_size;    /* total size, in bytes -文件大小,字节为单位*/    blksize_t   st_blksize; /* blocksize for filesystem I/O -系统块的大小*/    blkcnt_t    st_blocks;  /* number of blocks allocated -文件所占块数*/    time_t      st_atime;   /* time of last access -最近存取时间*/    time_t      st_mtime;   /* time of last modification -最近修改时间*/    time_t      st_ctime;   /* time of last status change - */
};

S_ISREG判定是否为常规文件,若unlink==-1,则删除失败。

S_ISDIR判定文件夹(不执行),为文件夹则不删除。

(五). 流程图:

(六). 测试结果与说明:

1.cat功能测试:

进入shell,调用自己的cat命令查看cp.cpp里的内容

2.ls功能测试:

进入自己实现的shell,分别调用自己的ls与&后台运行


3.ls -l功能测试:

进入自己实现的shell,分别调用自己的ls-l与&后台运行


4.ls -l |more功能测试:

进入自己实现的shell,分别调用自己的ls -l|more与&后台运行
这里是按照老师要求,先打印出10行,接着按enter回车键一行一行输出后面的信息。


5.cat功能测试:

进入自己实现的shell,分别调用自己的cat与&后台运行
为了有区分度,这里特意把原shell的白底黑字换成了绿色高亮。


6.touch功能测试:

进入自己实现的shell,分别调用自己的touch与&后台运行


7.cp功能测试:

进入自己实现的shell,分别调用自己的cp与&后台运行
操作失败则抛出提示:创建文件失败



8.date功能测试:

进入自己实现的shell,分别调用自己的date与&后台运行
为了有区分度,这里特意把原shell星期几的中文改成了数字。

9.file功能测试:

进入自己实现的shell,分别调用自己的file与&后台运行



10.mkdir功能测试:

进入自己实现的shell,分别调用自己的mkdir与&后台运行
这里为了跟原shell有一定的区分度,特意在创建文件夹成功时抛出一句提示:your operation is successful!



11.mv功能测试:

进入自己实现的shell,分别调用自己的mv与&后台运行



12.ps功能测试:

进入自己实现的shell,分别调用自己的ps与&后台运行

13.pwd功能测试:

进入自己实现的shell,分别调用自己的pwd与&后台运行

14.rm功能测试:

进入自己实现的shell,分别调用自己的rm与&后台运行

15.rmdir功能测试:

进入自己实现的shell,分别调用自己的rmdir与&后台运行

16.whoami功能测试:

进入自己实现的shell,分别调用自己的whoami与&后台运行

17.top功能测试:

进入自己实现的shell,分别调用自己的top与&后台运行

18.history+!!+!N功能测试:

进入自己实现的shell,分别调用自己的history与&后台运行
严格按照老师的要求,拉取最近十条执行过的命令,并标注其id号,若执行的命令数超过十条,则舍弃前面的指令,保留最近十条。

进入自己实现的shell,分别调用自己的!!与&后台运行
!!实现的是执行最近执行过的指令,这里为执行ls。

进入自己实现的shell,分别调用自己的!N与&后台运行
!N为依照id号拉取该id号对应的指令,并执行。

19.exit功能测试:

进入自己实现的shell,分别调用自己的exit与&后台运行
抛出一句提示:I am Chinese!再见

(七). 写在最后:

源码:放在Gayhub上~~

点我跳转到github仓库!!

点我跳转到gitee仓库~~

see you next blog~~

操作系统课设:实现一个mini_shell相关推荐

  1. 操作系统课设(ljh老师版)

    操作系统课设 完成一个文件模拟系统,做一个记录 #include<iostream> #include<string> #include<stdio.h> #inc ...

  2. 操作系统课设--虚拟内存

    山东大学操作系统课设lab7 实验七 虚拟内存(lab7) 实验目的 实验环境 实验思路 关键源代码注释以及程序说明 调试记录 实验七 虚拟内存(lab7) 实验目的 在未实现虚拟内存管理之前,Nac ...

  3. 操作系统课设--系统调用

    山东大学操作系统课设lab6 实验六 系统调用(lab6) 实验目的 实验环境 实验思路 调试记录 实验六 系统调用(lab6) 实验目的 扩展现有的class AddrSpace的实现,使得Nach ...

  4. 操作系统课设--具有二级索引的文件系统

    山东大学操作系统课设lab5 实验五 具有二级索引的文件系统(lab5) 实验目的 实验环境 实验思路 调试记录 实验五 具有二级索引的文件系统(lab5) 实验目的 Nachos系统原有的文件系统只 ...

  5. 操作系统课设--扩展文件系统

    山东大学操作系统课设lab4 实验四 扩展文件系统(lab4) 概念欠缺 实验目的 实验环境: 实验思路: 关键源代码注释以及程序说明: 调试记录: 实验四 扩展文件系统(lab4) 概念欠缺 ifd ...

  6. 操作系统课设--使用信号量解决生产者/消费者同步问题

    山东大学操作系统课设lab3 实验三 使用信号量解决生产者/消费者同步问题(lab3) 实验目的 理解Nachos的信号量是如何实现的 生产者/消费者问题是如何用信号量实现的 在Nachos中是如何创 ...

  7. 操作系统课设--具有优先级的线程调度

    山东大学操作系统课设lab2 实验二 具有优先级的线程调度(lab2) 概念欠缺 实验环境 实验目的 1. 熟悉Nachos原有的线程调度策略 2. 设计并实现具有优先级的线程调度策略 实验二 具有优 ...

  8. 操作系统课设--NACHOS试验环境准备、安装与MAKEFILE分析

    山东大学操作系统课设lab1 实验一 NACHOS试验环境准备.安装与MAKEFILE分析(lab1) 实验环境: 分析记录: 1. 准备虚拟机下LINUX宿主操作系统环境 2. NACHOS实验代码 ...

  9. 操作系统课设之简单 shell 命令行解释器的设计与实现

    前言 课程设计开始了,实验很有意思,写博客总结学到的知识 白嫖容易,创作不易,学到东西才是真 本文原创,创作不易,转载请注明!!! 本文链接 个人博客:https://ronglin.fun/arch ...

  10. 操作系统课设之基于信号量机制的并发程序设计

    前言 课程设计开始了,实验很有意思,写博客总结学到的知识 白嫖容易,创作不易,学到东西才是真 本文原创,创作不易,转载请注明!!! 本文链接 个人博客:https://ronglin.fun/arch ...

最新文章

  1. 【数据结构与算法】之深入解析“环形链表II”的求解思路与算法示例
  2. 360回归A股,周鸿祎来给BAT和小米添堵了
  3. can硬件结构和工作原理_汽车CAN总线工作原理及测量方法详解
  4. oem718d 基准站设置_华测口袋RTK正式发布!-华测导航
  5. 三星i9158刷机教程
  6. 红米android怎么开机画面,开机画面顶端出现 kernel is not seandroid enforcing 怎么解决...
  7. 路由跟踪之tcptraceroute IP延时之tcpping
  8. python实例 优化目标函数_python scipy optimize.minimize用法及代码示例
  9. Windows系统注册表
  10. 英语语法快速入门1--简单句(附思维导图)
  11. tensorflow2系类知识-4 :RNN
  12. dev C++遇到endl无法调试的解决方法
  13. Wireshark 抓取 iphone 测试机的数据包
  14. 一个简单的点对点文件传输程序
  15. 《Pytorch 模型推理及多任务通用范式》第三节作业
  16. Linux无法显示ip的解决办法
  17. 智能合约—简单的公开拍卖合约
  18. rootfs根文件系统扫盲
  19. 智慧农业APP软件应用价值
  20. arduino控制小车转向_同济大学2020年创新制作成果展示 (势能小车项目)

热门文章

  1. 美团推出语音应用平台 已与奔驰、小米等企业达成合作
  2. 联想成为中国女排主赞助商,却被自媒体攻击?网友:还好没赞助国足
  3. 卢伟冰:Redmi K30会支持全网通5G 雷军:必须的!
  4. 大众考虑投资中国汽车零部件供应商 潜在目标包括国轩高科
  5. B站COO李旎:超2000万人在B站看纪录片
  6. 又烧一辆!蔚来ES8车库夜里起火 两个半小时才熄灭:现场惨烈
  7. OPPO首部5G手机亮相 10倍混合光学变焦技术惊艳MWC
  8. 08年最佳短篇小说《误解》
  9. mysql5.6 load_MySQL 5.6 dump/load buffer pool实验
  10. 创科视觉软件说明书_【拓斯达 | GGII】20192023年中国机器视觉行业调研