C语言实现一个精简的shell

来源:互联网 发布:暴风tv root软件 编辑:程序博客网 时间:2024/05/17 08:59

仅供参考

短学期作业之一,放上来做个纪念:

/****************************************************************************Project Name: myshell*Description: a reduced shell program implemented by C*Auther:lishichengyan*Student ID:omitted*Last Modified: 2017.08.03***************************************************************************/ /*必要的头文件包含*/#include<stdio.h>#include<stdlib.h>#include<unistd.h>#include<memory.h>#include<string.h>#include<sys/types.h>#include<sys/wait.h>#include<sys/stat.h>#include<sys/param.h>#include<pwd.h>#include<errno.h>#include<time.h>#include<fcntl.h>#include<dirent.h>#include<signal.h>/*和长度有关的宏定义*/#define MAX_LINE 80//最大命令长度#define MAX_NAME_LEN 100//最大用户名长度#define MAX_PATH_LEN 1000//最大路径长度/*全局变量申明*/extern char **environ;//必须用extern申明,否则会报错char *cmd_array[MAX_LINE/2+1];//保存命令输入,就是原框架的char* args[]int pipe_fd[2];//和管道有关的数组,作为pipe()的参数int cmd_cnt;//命令中字符串的个数/*老师框架里的函数(有改动)*/void readcommand();//读取用户输入int is_internal_cmd();//处理内部命令int is_pipe();//分析管道命令void do_redirection();//分析重定向,对内部命令无效/*自己定义的函数*/void welcome();//打印欢迎信息,带有颜色void printprompt();//打印提示符,必须包含当前路径名int getcommandlen();//计算命令长度void do_pipe(int pos);//执行管道命令void run_external_cmd(int pos);//执行外部命令int is_bg_cmd();//判断是否有后台运行符号&void myquit();//quit,退出myshellvoid myexit();//exit,直接退出void myclr();//clr,和BatchShell下的clear一样void print_continue_info();//打印"continue"相关信息void mypwd();//pwd,打印当前工作目录void myecho();//echo,必须支持重定向void myecho_redirect();//带重定向的echovoid mytime();//time,和"date"类似void myenviron();//environ,和env一样,必须支持重定向void myenviron_redirect();//带重定向的environvoid mycd();//cd,切换到某个目录void myhelp();//help,必须支持重定向void myhelp_redirect();//带有重定向的helpvoid print_manual();//打印用户手册,是myhelp()的子函数 void print_cmdinfo(char* cmdname);//打印每个命令的帮助信息,是myhelp()的子函数void myexec();//exec,开启一个新进程并替换当前进程void mytest();//test,检查文件类型,支持-l,-b,-c,-d四个选项void myumask();//umask,查看默认的umask值或者重置umaskvoid myjobs();//jobs,查看正在运行的=进程void myfg(pid_t pid);//fg,切换进程到前台void mybg(pid_t pid);//bg,切换进程到后台void mybatch();//实现命令批处理,一次性执行保存在文件里的命令void mydir();//dir,显示当前目录下的所有文件void mydir_redirect();//带有重定向的dir/*老师所给函数的实现(框架有调整)*//*实现顺序和定义顺序相同*/void readcommand(){//这个函数用来读取用户输入int cnt=0;//记录cmd_array[]中字符串的个数char str[MAX_LINE];char* helper;memset(cmd_array,0,MAX_LINE/2+1);//每次必须清空!fgets(str,MAX_LINE,stdin);//用fgets代替gets,因为gets不检查溢出,比较危险if(str[strlen(str)-1]=='\n'){str[strlen(str)-1]='\0';//fgets会补'\n',这里必须把'\n'替换成'\0'}helper=strtok(str," ");//用空格分割这个命令while(helper!=NULL){//将分割后得到的结果写进cmd_arraycmd_array[cnt]=(char*)malloc(sizeof(*helper));strcpy(cmd_array[cnt++],helper);//注意:即便直接回车cmd_cnt也是1helper=strtok(NULL," ");}cmd_cnt=cnt;//cmd_cnt的值就是cnt的值}int is_internal_cmd(){//这个函数用来解析内部命令//根据不同的结果来调用不同函数来达到目的if(cmd_array[0]==NULL){//如果没有命令(只是回车)return 0;//返回0使得主函数的continue不执行}else if(strcmp(cmd_array[0],"quit")==0){//quit,退出之前有“Thank you...”提示信息myquit();}else if(strcmp(cmd_array[0],"exit")==0){//exit,直接退出myexit();}else if(strcmp(cmd_array[0],"clr")==0){//clr,清屏myclr();return 1;}else if(strcmp(cmd_array[0],"continue")==0){//continue命令,它只在循环里有效print_continue_info();return 1;}else if(strcmp(cmd_array[0],"pwd")==0){//pwd,打印当前工作目录mypwd();return 1;}else if(strcmp(cmd_array[0],"echo")==0){//echofor(int i=1;i<cmd_cnt;i++){//从第二个字符串开始分析if(strcmp(cmd_array[i],">")==0||strcmp(cmd_array[i],">>")==0){//如果有重定向符号myecho_redirect();//调用带有重定向的echoreturn 1;}}myecho();//如果没有重定向,直接echoreturn 1;}else if(strcmp(cmd_array[0],"time")==0){//time,显示当前时间,和date命令相似mytime();return 1;}else if(strcmp(cmd_array[0],"environ")==0){//environif(cmd_array[1]!=NULL&&(strcmp(cmd_array[1],">")==0||strcmp(cmd_array[1],">>")==0)){//带有重定向myenviron_redirect();//调用带有重定向的environreturn 1;}else{myenviron();//没有重定向return 1;}}else if(strcmp(cmd_array[0],"cd")==0){//cd,切换目录mycd();return 1;}else if(strcmp(cmd_array[0],"help")==0){//helpif(cmd_array[1]!=NULL&&(strcmp(cmd_array[1],">")==0||strcmp(cmd_array[1],">>")==0)){//带有重定向myhelp_redirect();//注意:格式是 "help > filename"或者"help >>filename"return 1;}else{myhelp();//没有重定向return 1;}}else if(strcmp(cmd_array[0],"exec")==0){//exec,开启一个新进程替换当前进程myexec();return 1;}else if(strcmp(cmd_array[0],"test")==0){//test,用来查看文件属性,支持[-l],[-d],[-c],[-b]四个选项mytest();return 1;}else if(strcmp(cmd_array[0],"umask")==0){//umask,查看或者设置umask值myumask();return 1;}else if(strcmp(cmd_array[0],"jobs")==0){//jobs,查看运行的进程myjobs();return 1;}else if(strcmp(cmd_array[0],"fg")==0){//fg,将进程切换到前台pid_t pid;if(cmd_array[1]!=NULL){pid=atoi(cmd_array[1]);//用atoi转换,获取pid}else{//如果只有一个fgprintf("myshell: fg: no job assigned\n");//打印提示信息return 1;}myfg(pid);return 1;}else if(strcmp(cmd_array[0],"bg")==0){pid_t pid;if(cmd_array[1]!=NULL){pid=atoi(cmd_array[1]);//用atoi转换,获取pid}else{//只有一个bgprintf("myshell: bg: no job assigned\n");//打印提示信息return 1;}mybg(pid);return 1;}else if(strcmp(cmd_array[0],"myshell")==0){if(cmd_cnt==1){//只有一个myshell命令printf("myshell: myshell: too few arguments\n");//打印提示信息return 1;}else if(cmd_cnt==2){//输入格式是:myshell [filename]mybatch();return 1;}else{//参数过多的情况printf("myshell: myshell: too many arguments\n");return 1;}}else if(strcmp(cmd_array[0],"dir")==0){//dirif(cmd_array[1]!=NULL&&(strcmp(cmd_array[1],">")==0||strcmp(cmd_array[1],">>")==0)){//有重定向,格式是: dir > filename或者dir >> filenamemydir_redirect();//调用带有重定向的dirreturn 1;}else{//没有重定向mydir();return 1;}}else if(strcmp(cmd_array[0],"set")==0){//I'll try latterprintf("myshell: set: not supported currently\n");return 1;}else if(strcmp(cmd_array[0],"unset")==0){//I'll try latterprintf("myshell: unset: not supported currently\n");return 1;}else if(strcmp(cmd_array[0],"shift")==0){//I'll try latterprintf("myshell: shift: not supported currently\n");return 1;}else{return 0;//返回0使得主函数的continue不执行}}int is_pipe(){for(int i=1;i<cmd_cnt;i++){//从第二个字符串开始分析if(cmd_array[i]!=NULL&&strcmp(cmd_array[i],"|")==0){cmd_array[i]=NULL;//把管道符替换成NULL,因为已经不再需要,避免对命令执行造成影响return i+1;//返回下一个命令的位置}}return 0;//没有pipe,返回0}void do_redirection(){//这个函数仅用来实现外部命令的重定向//对于:dir, environ, echo, help命令//有专门的函数体执行它们的重定向for(int i=1;i<cmd_cnt;i++){if(cmd_array[i]!=NULL){if(strcmp(cmd_array[i],">")==0){//>:重写文件int output=open(cmd_array[i+1],O_WRONLY|O_TRUNC|O_CREAT,0666);//必须用O_TRUNCdup2(output,1);//把stdout重定向到outputclose(output);cmd_array[i]=NULL;//把>替换成NULLi++;continue;//跳过}if(strcmp(cmd_array[i],">>")==0){//>>:在文件内容后追加int output=open(cmd_array[i+1],O_WRONLY|O_APPEND|O_CREAT,0666);//必须用O_APPENDdup2(output,1);//把stdout重定向到outputclose(output);cmd_array[i]=NULL;//用NULL代替>>i++;continue;//跳过}if(strcmp(cmd_array[i],"<")==0){//<:输入重定向int input=open(cmd_array[i+1],O_CREAT|O_RDONLY,0666);dup2(input,0);//把stdin重定向到inputclose(input);cmd_array[i]=NULL;//用NULL替换<i++;}}}}/*自己定的函数的实现*//*实现顺序和定义顺序相同*/void welcome(){//如下是欢迎信息//为了是程序更友好,加入了颜色//颜色是紫色,背景色与shell相同printf("\e[35mwelcome to myshell\e[0m\n");printf("\e[35mit's a unix-like shell program made by WuYusong\e[0m\n");printf("\e[35mhope you have a good time with it :-)\e[0m\n");}void printprompt(){//这个函数用来打印命令提示符//为了使程序更友好,加入了颜色//颜色是蓝色char hostname[MAX_NAME_LEN];char pathname[MAX_PATH_LEN];struct passwd *pwd;pwd=getpwuid(getuid());//通过pid获取用户信息gethostname(hostname,MAX_NAME_LEN);//取得hostnamegetcwd(pathname,MAX_PATH_LEN);//获取绝度路径把它储存到第一个参数pathname[]printf("\e[34mmyshell>%s@%s:\e[0m",pwd->pw_name,hostname);//打印提示符,颜色是蓝色if (strncmp(pathname,pwd->pw_dir,strlen(pwd->pw_dir))==0){//比较两条路径printf("~%s",pathname+strlen(pwd->pw_dir));//打印路径}else {printf("%s",pathname);//打印当前工作路径}if (geteuid()==0) {//函数返回有效用户的idprintf("#");//如果是root用户,打印#提示符}else {printf("$");//普通用户打印$提示符}}int getcommandlen(){int tot_len=0;for(int i=0;i<cmd_cnt;i++){tot_len+=strlen(cmd_array[i]);//注意:空格没有算进去}return tot_len+cmd_cnt-1;//因此这里要把空格的长度加进去,直接回车返回-1}void do_pipe(int pos){int pid;if(pos==0){//没有pipereturn;}if((pid=fork())==0){//子进程close(pipe_fd[1]);//关闭写dup2(pipe_fd[0],0);//重定向stdin到pipe_fd[0]run_external_cmd(pos);//执行外部命令}else{//父进程close(pipe_fd[1]);//关闭写waitpid(pid,NULL,0);//阻塞父进程等待子进程}}void run_external_cmd(int pos){int res;res=execvp(cmd_array[pos],cmd_array+pos);//用execvp执行命令if(res<0){//如果执行失败printf("myshell: command not found\n");//打印提示信息}}int is_bg_cmd(){int i,lastpos;if(cmd_cnt==0){//直接回车的情况return 0;}for(i=0;i<cmd_cnt&&cmd_array[i]!=NULL;i++){}//什么都不做,只是为了得到ilastpos=i-1;//最后一个位置if(strcmp(cmd_array[lastpos],"&")==0){//命令最末有&cmd_array[lastpos]=NULL;//用NULL替换&cmd_cnt--;//必须减掉1,因为&已经被替换了return 1;//返回1表示这个命令需要在后台执行}else{return  0;//否则返回0}}void myquit(){printf("Thanks for your using,bye-bye!\n");sleep(1);//暂停1s,看上去视觉效果好一些exit(0);}void myexit(){exit(0);//直接退出}void myclr(){printf("\033[2J");//清屏printf("\033[H");//把光标移动到合适的位置}void print_continue_info(){//对于只在脚本循环中有效的continue//在终端下给出提示信息printf("myshell: continue: only meaningful in a 'for','while' or 'until' loop\n");}void mypwd(){char pathname[MAX_PATH_LEN];if(getcwd(pathname,MAX_PATH_LEN)){//获取路径名printf("%s\n",pathname);}else{//如果出错perror("myshell: getcwd");//报错exit(1);}}void myecho(){for(int i=1;i<cmd_cnt;i++){printf("%s",*(cmd_array+i));if(i==cmd_cnt-1){//打印最后的字符时不要空格,直接breakbreak;}printf(" ");}printf("\n");//然后换行}void myecho_redirect(){//echo的内容可以被重定向到文件//这个函数虽然长但是并不复杂//核心是使用dup2()来进行重定向//重定向以后的标准输出(stdout)就会被输出到指定的文件int fd;//文件描述符pid_t pid;char filename[MAX_NAME_LEN];//用来保存文件名for(int i=1;i<cmd_cnt;i++){if(strcmp(cmd_array[i],">")==0||strcmp(cmd_array[i],">>")==0){if(cmd_array[i+1]==NULL){//如果在>和>>之后没有路径名printf("myshell: syntax error\n");//如果出现错误}else{strcpy(filename,cmd_array[i+1]);//获取文件名}if(strcmp(cmd_array[i],">")==0){fd=open(filename,O_CREAT|O_TRUNC|O_WRONLY,0600);//因为要重写,所以必须用O_TRUNCif(fd<0){perror("myshell: open");exit(1);}if((pid=fork())==0){//子进程dup2(fd,1);//把stdout重定向到fdfor(int j=1;j<i;j++){//开始把内容写进文件printf("%s ",cmd_array[j]);//打印的内容其实去到了文件(因为已经重定向了)}exit(0);}else if(pid>0){//父进程waitpid(pid,NULL,0);//等待子进程}else{//如果出现错误perror("myshell: fork");exit(1);}close(fd);}else{//如果是>>,实现追加的重定向fd=open(filename,O_CREAT|O_APPEND|O_WRONLY,0600);//这样一来就必须用O_APPENDif(fd<0){perror("myshell: open");exit(1);}if((pid=fork())==0){//子进程dup2(fd,1);//重定向stdout到fdfor(int j=1;j<i;j++){//向文件写内容printf("%s ",cmd_array[j]);}exit(0);}else if(pid>0){//父进程waitpid(pid,NULL,0);//等待子进程}else{perror("myshell: fork");exit(1);}close(fd);//最后不要忘了关闭文件}}}}void mytime(){int weekday;int month;time_t tvar;struct tm *tp;time(&tvar);tp=localtime(&tvar);//获取本地时间weekday=tp->tm_wday;switch(weekday){//根据不同的值打印不同的星期case 1:printf("Mon ");break;case 2:printf("Tues ");break;case 3:printf("Wed ");break;case 4:printf("Thur ");break;case 5:printf("Fri ");break;case 6:printf("Sat ");break;case 7:printf("Sun ");break;default:break;}month=1+tp->tm_mon;//必须要加1,经过查阅资料:tm_mon比实际的值少了1switch(month){//根据不同的值打印月份名case 1:printf("Jan ");break;case 2:printf("Feb ");break;case 3:printf("Mar ");break;case 4:printf("Apr ");break;case 5:printf("May ");break;case 6:printf("Jun ");break;case 7:printf("Jul ");break;case 8:printf("Aug ");break;case 9:printf("Sep ");break;case 10:printf("Oct ");break;case 11:printf("Nov ");break;case 12:printf("Dec ");break;default:break;}printf("%d ",tp->tm_mday);//日期printf("%d:",tp->tm_hour);//小时printf("%d:",tp->tm_min);//分钟printf("%d ",tp->tm_sec);//秒printf("CST ");//CST,意思是China Standard Timeprintf("%d\n",1900+tp->tm_year);//必须加上1900,返回的值并不是完整的年份,比真实值少了1900}void myenviron(){//用environ[]来实现全局变量的打印//"environ" 必须实现被申明:exetern char** environfor(int i=0;environ[i]!=NULL;i++){printf("%s\n",environ[i]);}}void myenviron_redirect(){//把环境变量重定向到文件//思路是用dup2()//这和上面的重定向相似,但是一些关键的代码是不一样的int fd;//文件描述符pid_t pid;char filename[MAX_NAME_LEN];if(cmd_array[2]==NULL){//如果出现错误printf("error occurs when redirecting\n");exit(1);}else{strcpy(filename,cmd_array[2]);//获取文件名}if(strcmp(cmd_array[1],">")==0){//>:重写文件内容fd=open(filename,O_CREAT|O_TRUNC|O_WRONLY,0600);//必须用O_TRUNCif(fd<0){perror("myshell: open");exit(1);}if((pid=fork())==0){//子进程dup2(fd,1);//把stdout重定向到fdfor(int i=0;environ[i]!=NULL;i++){//向文件写内容printf("%s\n",environ[i]);}exit(0);}else if(pid>0){//父进程waitpid(pid,NULL,0);//等待子进程}else{perror("myshell: fork");exit(1);}close(fd);}else{//如果是>>fd=open(filename,O_CREAT|O_APPEND|O_WRONLY,0600);//必须用O_APPENDif(fd<0){perror("myshell: open");exit(1);}if((pid=fork())==0){//子进程dup2(fd,1);//把stdout重定向到fdfor(int i=0;environ[i]!=NULL;i++){printf("%s\n",environ[i]);}exit(0);}else if(pid>0){//父进程waitpid(pid,NULL,0);//等待子进程}else{perror("myshell: fork");exit(1);}close(fd);}}void mycd(){struct passwd *pwd;//用来获取参数pw_dirchar pathname[MAX_PATH_LEN];//储存路径名pwd=getpwuid(getuid());if(cmd_cnt==1){//如果只有一个cdstrcpy(pathname,pwd->pw_dir);//获取pathnameif(chdir(pathname)==-1){//如果有错perror("myshell: chdir");//报错exit(1);}}else{//如果有路径if(chdir(cmd_array[1])==-1){//如果chdir执行失败printf("myshell: cd: %s :No such file or directory\n",cmd_array[1]);//打印提示信息}}}void myhelp(){if(cmd_cnt==1){//如果是不带参数的helpprint_manual();//调用子函数print_manual打印用户帮助手册}else if(cmd_cnt==2){//如果格式是"help [command]"print_cmdinfo(cmd_array[1]);//打印单个命令的帮助信息}else{//如果有错printf("myshell: help: Invalid use of help command\n");//打印提示信息}}void myhelp_redirect(){//重定向帮助信息到文件//这个函数并不支持"help [command] > filename"这样的格式//因为其实和help直接重定向比起来是大同小异int fd;//文件描述符pid_t pid;char filename[MAX_NAME_LEN];if(cmd_array[2]==NULL){printf("error occurs when redirecting\n");exit(1);}else{strcpy(filename,cmd_array[2]);//获取文件名}if(strcmp(cmd_array[1],">")==0){//>:重写文件内容fd=open(filename,O_CREAT|O_TRUNC|O_WRONLY,0600);//必须用O_TRUNCif(fd<0){perror("myshell: open");exit(1);}if((pid=fork())==0){//子进程dup2(fd,1);//把stdout重定向到fdprint_manual();//打印内容重定向到了文件exit(0);}else if(pid>0){//父进程waitpid(pid,NULL,0);//阻塞父进程,等待子进程}else{perror("myshell: fork");exit(1);}close(fd);}else{//如果是>>重定向fd=open(filename,O_CREAT|O_APPEND|O_WRONLY,0600);//必须用O_APPENDif(fd<0){perror("myshell: open");exit(1);}if((pid=fork())==0){//子进程dup2(fd,1);//重定向stdout到fdprint_manual();//调用print_manual,信息打印到文件exit(0);}else if(pid>0){//父进程waitpid(pid,NULL,0);//等待子进程}else{perror("myshell: fork");exit(1);}close(fd);//不要忘记关闭文件}}void print_manual(){//这个函数很“无聊”但是很重要//它为用户打印myshell命令的帮助信息//直接使用help就可以进行查看printf("welcome to the manual of myshell, hope it's useful for you\n");printf("the following are the BUILT-IN commands supported by myshell\n");printf("\n");printf("NAMES      FORMATS                         DESCRIPTIONS\n");printf("bg:        bg [job_spec]                   execute commands in background\n");printf("cd:        cd [dir]   go to a specified directory\n");printf("continue:  continue [n]                    valid only in for, while, or until loop\n");printf("echo:      echo [arg ...]                  print strings after echo,redirection is supported\n");printf("exec:      exec [command]                  execute a command and replace the current process\n");printf("exit:      exit                            quit the shell directly\n");printf("fg:        fg [job_spec]                   execute commands in foreground\n");printf("jobs:      jobs                            check the processes running in the system\n");printf("pwd:       pwd                             print the current working directory\n");printf("set:       set [-$] [command or arg ...]   set shell variables\n");printf("shift:     shift [n]                       shift user's inputs\n");printf("test:      test [arg ...]                  check file attributes, 4 options are supported so far\n");printf("time:      time                            show the current time in an elegant format\n");printf("umask:     umask [-p] [-S] [mode]          change the value of umask\n");printf("unset:     unset [name]                    unset shell variables\n");printf("clr:       clr                             clear the screen\n");printf("dir:       dir [dir]                       list the file names in the target directory\n");printf("environ:   environ                         list all the environment variables\n");printf("help:      help/help [command]             show the manual of help/get help info of a sepcified command\n");printf("quit:      quit                            quit the shell with thank-you information\n");printf("myshell:   myshell [filename]              execute a batchfile\n");printf("for more information, use help [command] to see diffirent options of each command\n");fflush(stdout);}void print_cmdinfo(char* cmdname){//这个函数显示myshell命令的option //需要说明的是[command] --help这样的格式是无效的//因为这会带来分析命令是不必要的麻烦//正确的格式是help [command]if(strcmp(cmdname,"bg")==0){printf("usage:execute commands in background\n");printf("options            descriptions\n");printf("none               see the manual,plz\n");}else if(strcmp(cmdname,"cd")==0){printf("usage:go to a specified directory\n");printf("options            descriptions\n");printf("none               see the manual,plz\n");}else if(strcmp(cmdname,"continue")==0){printf("usage:valid only in for, while, or until loop\n");printf("options            descriptions\n");printf("none               see the manual,plz\n");}else if(strcmp(cmdname,"echo")==0){printf("usage:print strings after echo,redirection is supported\n");printf("options            descriptions\n");printf("none               see the manual,plz\n");}else if(strcmp(cmdname,"exec")==0){printf("usage:execute a command and replace the current process\n");printf("options            descriptions\n");printf("none               see the manual,plz\n");}else if(strcmp(cmdname,"exit")==0){printf("usage:quit the shell directly\n");printf("options            descriptions\n");printf("none               see the manual,plz\n");}else if(strcmp(cmdname,"fg")==0){printf("usage:execute commands in foreground\n");printf("options            descriptions\n");printf("none               see the manual,plz\n");}else if(strcmp(cmdname,"jobs")==0){printf("usage:check the processes running in the system\n");printf("options            descriptions\n");printf("none               see the manual,plz\n");}else if(strcmp(cmdname,"pwd")==0){printf("usage:print the current working directory\n");printf("options            descriptions\n");printf("none               see the manual,plz\n");}else if(strcmp(cmdname,"set")==0){printf("usage:set shell variables\n");printf("options            descriptions\n");printf("/   not supported currently\n");}else if(strcmp(cmdname,"shift")==0){printf("usage:shift user's inputs\n");printf("options            descriptions\n");printf("/               not supported currently\n");}else if(strcmp(cmdname,"test")==0){printf("usage:check file attributes, 4 options are supported so far\n");printf("options            descriptions\n");printf("[-l]               test if the file is a symbolic link\n");printf("[-b]               test if the file is a block device\n");printf("[-c]               test if the file is a character device\n");printf("[-d]               test if the file is a directory\n");}else if(strcmp(cmdname,"time")==0){printf("usage:show the current time in an elegant format\n");printf("options            descriptions\n");printf("none               see the manual,plz\n");}else if(strcmp(cmdname,"umask")==0){printf("usage:change the value of umask\n");printf("options            descriptions\n");printf("none               see the manual,plz\n");}else if(strcmp(cmdname,"unset")==0){printf("usage:unset shell variables\n");printf("options            descriptions\n");printf("/   not supported currently\n");}else if(strcmp(cmdname,"clr")==0){printf("usage:clear screen\n");printf("options            descriptions\n");printf("none               see the manual,pls\n");}else if(strcmp(cmdname,"dir")==0){printf("usage:list the file names in the target directory\n");printf("options            descriptions\n");printf("none               see the manual,plz\n");}else if(strcmp(cmdname,"environ")==0){printf("usage:list all the environment variables\n");printf("options            descriptions\n");printf("none               see the manual,plz\n");}else if(strcmp(cmdname,"help")==0){printf("usage:show the manual of help/get help info of a sepcified command\n");printf("options            descriptions\n");printf("none               see the manual,plz\n");}else if(strcmp(cmdname,"quit")==0){printf("usage:quit the shell with thank-you information\n");printf("options            descriptions\n");printf("none               see the manual,plz\n");}else if(strcmp(cmdname,"myshell")==0){printf("usage:execute a batchfile\n");printf("options            descriptions\n");printf("none               see the manual,plz\n");}else if(strcmp(cmdname,"mylove")==0){printf("wish you find your Mr/Miss.Right :-)\n");}}void myexec(){//这有点像daemon(守护进程)//但是两者是有区别的//在这里我们要替换的是父进程int res;pid_t pid;if(cmd_cnt==1){//如果只有一个exec,returnreturn;}else{pid=fork();//fork一个进程出来,方便模拟execif(pid<0){perror("fork");exit(1);}else if(pid==0){//子进程exit(0);//直接退出}else{//父进程res=execvp(cmd_array[1],cmd_array+1);//用execvp()来执行这个命令if(res<0){//如果执行失败printf("myshell: command not found\n");//打印提示信息}exit(0);}}}void mytest(){if(cmd_cnt!=3){//命令中的字符串格式只有3个printf("myshell: test: incorrect number of arguments\n");printf("the format is 'test [op] [filename]'\n");printf("for more information: use 'help test'\n");}else{FILE* fp;struct stat buf;char filename[MAX_NAME_LEN];int mode;//用来保存调用stat()以后的返回结果strcpy(filename,cmd_array[2]);//获取文件名称fp=fopen(filename,"r");//以只读方式打开文件if(fp==NULL){//如果打不开printf("myshell: test: file named %s doesn't exist\n",filename);//文件不存在}else{//如果文件存在stat(filename,&buf);//储存相关信息到bufmode=buf.st_mode;if(strcmp(cmd_array[1],"-l")==0){//如果是符号链接(symbolic link)mode=mode&S_IFLNK;//位与操作if(mode==S_IFLNK){//检查得到的新值和S_IFLNK是否匹配printf("yes,this is a symbolic link\n");}else{printf("no,this is NOT a symbolic link\n");}}else if(strcmp(cmd_array[1],"-b")==0){//检查是不是块设备(block device)mode=mode&S_IFBLK;//位与操作if(mode==S_IFBLK){//检查与S_IFBLK是否匹配printf("yes,this is a block device\n");}else{printf("no,this is NOT a block device\n");}}else if(strcmp(cmd_array[1],"-c")==0){//检查是不是字符设备(character device)mode=mode&S_IFCHR;//位与操作if(mode==S_IFCHR){//检查与S_IFCHR是否匹配printf("yes,this is a character device\n");}else{printf("no,this is NOT a character device\n");}}else if(strcmp(cmd_array[1],"-d")==0){//检查是不是目录文件(directory)mode=mode&S_IFDIR;//位与操作if(mode==S_IFDIR){//判断是不是和S_IFDIR匹配printf("yes,this is a directory\n");}else{printf("no,this is NOT a directory\n");}}else{//其他的非法输入printf("myshell: test: only 4 options are allowed:\n");printf("[-l],[-b],[-c],[-d]\n");printf("for more information: use 'help test'\n");}}}}void myumask(){int bit1,bit2,bit3,bitsum;mode_t new_umask,old_umask;if(cmd_cnt==1){//只用一个umask可以查看默认值printf("myshell: default umask value: %o\n",2);return;}if(strlen(cmd_array[1])!=4||cmd_array[1][0]!='0'){//对于不正确的格式打印提示信息printf("myshell: umask: the format is umask 0[digit1][digit2][digit3], eg., umask 0002\n");return;}else{//获取每一位的值(用来得到最终的umask)switch(cmd_array[1][1]){case'0':bit1=0000;break;case'1':bit1=0100;break;case'2':bit1=0200;break;case'3':bit1=0300;break;case'4':bit1=0400;break;case'5':bit1=0500;break;case'6':bit1=0600;break;case'7':bit1=0700;break;default:printf("myshell: umask: out of range\n");break;}switch(cmd_array[1][2]){case'0':bit2=0000;break;case'1':bit2=0010;break;case'2':bit2=0020;break;case'3':bit2=0030;break;case'4':bit2=0040;break;case'5':bit2=0050;break;case'6':bit2=0060;break;case'7':bit2=0070;break;default:printf("myshell: umask: out of range\n");break;}switch(cmd_array[1][3]){case'0':bit3=0000;break;case'1':bit3=0001;break;case'2':bit3=0002;break;case'3':bit3=0003;break;case'4':bit3=0004;break;case'5':bit3=0005;break;case'6':bit3=0006;break;case'7':bit3=0007;break;default:printf("myshell: umask: out of range\n");break;}bitsum=bit1+bit2+bit3;new_umask=bitsum;//新的umask是这三个值的和printf("sum:%o\n",bitsum);old_umask=umask(new_umask);//这个函数返回旧的umask值}printf("myshell: umask changed successfully\n");printf("myshell: old value: %o\n",old_umask);//打印旧的值printf("myshell: new value: %o\n",new_umask);//打印当前的新值}void myjobs(){//可以使用ps命令来实现查看进程pid_t pid;pid=fork();//必须fork,否则会出现myshell退出这种奇怪的bugif(pid<0){perror("myshell: fork");}else if(pid==0){//子进程if(cmd_cnt>1){printf("myshell: jobs: incorrect use of jobs\n");}else{execlp("ps","ps","ax",NULL);//使用ps}}else{//父进程waitpid(pid,NULL,0);}}void myfg(pid_t pid){setpgid(pid,pid);    if (tcsetpgrp(1,getpgid(pid))== 0){        kill(pid,SIGCONT);//向对应的进程发送SIG_CONT信号        waitpid(pid,NULL,WUNTRACED);//必须使用WUNTRACED    }else{printf("myshell: fg: no such job\n");}}void mybg(pid_t pid){if(kill(pid,SIGCONT)<0){//发送SIGCONT信号printf("myshell: bg: no such job\n");//如果有错就打印提示信息}else{waitpid(pid,NULL,WUNTRACED);//和myfg()一样,必须用WUNTRACED}}void mybatch(){//这个函数用来支持命令"myshell"//命令的格式是"myshell [filename]"//您可以把需要执行的命令存入一个文件//再用"myshell [filename]"一次性执行它们FILE *fp,*helper;char filename[MAX_NAME_LEN];char cmdname[MAX_LINE];int fmark,cnt=0;strcpy(filename,cmd_array[1]);//获取文件名fp=fopen(filename,"r");//以只读方式打开文件helper=fopen(filename,"r");//helper在后面也要用到,这里再打开一遍if(fp==NULL||helper==NULL){//如果打开失败printf("myshell: myshell: no file named %s",filename);}else{//如果成功int pos_after_pipe,bg,num=0;pid_t pid;char* cptr;while((fmark=fgetc(fp))!=EOF){//获取文件中的命令个数if(fmark=='\n'){cnt++;}}for(int i=0;i<cnt;i++){memset(cmd_array,0,MAX_LINE/2+1);//必须要每次清空!cmd_cnt=0;//同时也要情况cmd_cntnum=0;//num也要清空for(int i=0;i<MAX_LINE&&cmdname[i]!='\0';i++){//清空cmdnamecmdname[i]='\0';}fgets(cmdname,MAX_LINE,helper);//cmdname每次读取一行cmdname[strlen(cmdname)-1]='\0';//必须减1,调整到正确位置cptr=strtok(cmdname," ");//用空格分割命令while(cptr!=NULL){//然后把分割好的内容存到cmd_arraycmd_array[num]=(char*)malloc(sizeof(*cptr));strcpy(cmd_array[num++],cptr);//需要注意的是即使直接回车num也是1,这样cmd_cnt至少是1cptr=strtok(NULL," ");}cmd_cnt=num;//cmd_cnt取得它的值,和num是相等的if(is_internal_cmd()){continue;}if(pos_after_pipe=is_pipe()){pipe(pipe_fd);}if((pid=fork())==0){//子进程int thispid=getpid();//获取子进程pidsignal(SIGINT,SIG_DFL);//默认是终止进程signal(SIGTSTP,SIG_DFL);//默认是暂停进程signal(SIGCONT,SIG_DFL);//默认是继续这个进程if(pos_after_pipe){//如果有管道close(pipe_fd[0]);//关闭读dup2(pipe_fd[1],1);//把stdout重定向到pipe_fd[1]}if(bg==1){//如果命令需要在后台执行printf("myshell: in background: the job's pid: [%d]\n",thispid);run_external_cmd(0);exit(0);}do_redirection();run_external_cmd(0);break;}else if(pid>0){//父进程signal(SIGINT,SIG_IGN);//忽视信号signal(SIGTSTP,SIG_IGN);//忽视信号signal(SIGCONT,SIG_DFL);//默认是继续if(bg==1){//如果命令要在后台执行signal(SIGCHLD,SIG_IGN);//忽视SIGCHLD信号}else{waitpid(pid,NULL,WUNTRACED);//后台执行的关键代码,必须用WUNTRACED}do_pipe(pos_after_pipe);//如果有pipe,在这里执行它}else{perror("myshell: fork");break;}}}}void mydir(){char pathname[MAX_PATH_LEN];//保存当前路径DIR *dir;//DIR struct保存关于目录的信息struct dirent *dp;if(!getcwd(pathname,MAX_PATH_LEN)){//获取路径名perror("myshell: getcwd");exit(1);}dir=opendir(pathname);//返回指向DIR struct的指针printf("the directory(ies) under the current path is(are):\n");while((dp=readdir(dir))!=NULL){//列出得到的信息printf("%s\n",dp->d_name);}}void mydir_redirect(){//这个函数支持了mydir()的重定向//>>向文件末尾追加内容(如果文件存在的话,不存在就新建)//而>是重写了整个文件内容//下面的一些逻辑和mydir()类似//但还要加上文件操作int fd;//文件描述符pid_t pid;char pathname[MAX_PATH_LEN];char filename[MAX_NAME_LEN];DIR *dir;struct dirent *dp;if(!getcwd(pathname,MAX_PATH_LEN)){//获取路径名perror("myshell: getcwd");exit(1);}dir=opendir(pathname);//返回指向DIR struct的指针strcpy(filename,cmd_array[2]);//获取文件名if(strcmp(cmd_array[1],">")==0){if((fd=open(filename,O_CREAT|O_TRUNC|O_WRONLY,0600))<0){//必须用O_TRUNCperror("myshell: open");exit(1);}else{pid=fork();//fork一个子进程执行重定向if(pid==0){//子进程while((dp=readdir(dir))!=NULL){//读取目录信息dup2(fd,1);//把stdout重定向到文件printf("%s\n",dp->d_name);//这样一来打印内容其实去到了文件}exit(0);//必须exit}else if(pid>0){//父进程waitpid(pid,NULL,0);//等待子进程}else{printf("fork failed\n");exit(1);}}close(fd);//别忘了关闭文件}else{//带有>>的重定向,添加内容到文件if((fd=open(filename,O_CREAT|O_APPEND|O_WRONLY,0600))<0){//必须使用O_APPENDperror("myshell: open");exit(1);}else{pid=fork();//为了做重定向,forkif(pid==0){//子进程while((dp=readdir(dir))!=NULL){//读取目录dup2(fd,1);//重定向stdout到文件printf("%s\n",dp->d_name);//这样一来print的内容就写进了文件}exit(0);}else if(pid>0){//父进程waitpid(pid,NULL,0);//阻塞父进程,等待子进程}else{printf("fork failed\n");exit(1);}}close(fd);//别忘了关闭文件}}int main(){int should_run=1;//标记什么时候退出大循环pid_t pid;//fork的时候要用int cmd_len=0;//记录命令长度int pos_after_pipe;//记录重定向符号|的位置int bg;//后台执行的标记welcome();//欢迎信息while(should_run){printprompt();//打印命令提示符readcommand();//读取命令,同时也取得了cmd_cnt的值cmd_len=getcommandlen();if(cmd_len>MAX_LINE){//如果用户输入长度超过规定的长度printf("the length of your input is too long to be read in\n");exit(1);}bg=is_bg_cmd();//如果bg=1,说明该命令需要在后台执行if(is_internal_cmd()){//处理内部命令continue;}if(pos_after_pipe=is_pipe()){pipe(pipe_fd);}if((pid=fork())==0){//子进程int thispid=getpid();//获取子进程pidsignal(SIGINT,SIG_DFL);//默认:停止进程signal(SIGTSTP,SIG_DFL);//默认:终止进程signal(SIGCONT,SIG_DFL);//用默认的方式处理SIGCONTif(pos_after_pipe){//如果有pipe close(pipe_fd[0]);//关闭读dup2(pipe_fd[1],1);//把stdout重定向到pipe_fd[1]}if(bg==1){//如果需要在后台执行printf("myshell: in background: the job's pid: [%d]\n",thispid);run_external_cmd(0);//执行外部命令return 0;}do_redirection();//执行重定向(如果有的话)run_external_cmd(0);break;}else if(pid>0){//父进程signal(SIGINT,SIG_IGN);//忽视这个信号signal(SIGTSTP,SIG_IGN);//忽视这个信号signal(SIGCONT,SIG_DFL);//用默认的方式处理SIGCONTif(bg==1){//如果需要在后台执行signal(SIGCHLD,SIG_IGN);//忽视SIGCHLD}else{waitpid(pid,NULL,WUNTRACED);//后台执行的关键代码}do_pipe(pos_after_pipe);//在这里处理带pipe的命令}else{perror("myshell: fork");break;}}return 0;}


原创粉丝点击