C语言实现一个精简的shell

时间:2022-12-25 00:01:53

仅供参考

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

/***************************************************************************
*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,退出myshell
void myexit();//exit,直接退出
void myclr();//clr,和BatchShell下的clear一样
void print_continue_info();//打印"continue"相关信息
void mypwd();//pwd,打印当前工作目录
void myecho();//echo,必须支持重定向
void myecho_redirect();//带重定向的echo
void mytime();//time,和"date"类似
void myenviron();//environ,和env一样,必须支持重定向
void myenviron_redirect();//带重定向的environ
void mycd();//cd,切换到某个目录
void myhelp();//help,必须支持重定向
void myhelp_redirect();//带有重定向的help
void print_manual();//打印用户手册,是myhelp()的子函数 
void print_cmdinfo(char* cmdname);//打印每个命令的帮助信息,是myhelp()的子函数
void myexec();//exec,开启一个新进程并替换当前进程
void mytest();//test,检查文件类型,支持-l,-b,-c,-d四个选项
void myumask();//umask,查看默认的umask值或者重置umask
void 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_array
			cmd_array[cnt]=(char*)malloc(sizeof(*helper));
			strcpy(cmd_array[cnt++],helper);//注意:即便直接回车cmd_cnt也是1
			helper=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){//echo
		for(int i=1;i<cmd_cnt;i++){//从第二个字符串开始分析
			if(strcmp(cmd_array[i],">")==0||strcmp(cmd_array[i],">>")==0){//如果有重定向符号
				myecho_redirect();//调用带有重定向的echo
				return 1;
			}
		}
		myecho();//如果没有重定向,直接echo
		return 1;
	}
	else if(strcmp(cmd_array[0],"time")==0){//time,显示当前时间,和date命令相似
		mytime();
		return 1;
	}
	else if(strcmp(cmd_array[0],"environ")==0){//environ
		if(cmd_array[1]!=NULL&&(strcmp(cmd_array[1],">")==0||strcmp(cmd_array[1],">>")==0)){//带有重定向
			myenviron_redirect();//调用带有重定向的environ
			return 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){//help
		if(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{//如果只有一个fg
			printf("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{//只有一个bg
			printf("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){//dir
		if(cmd_array[1]!=NULL&&(strcmp(cmd_array[1],">")==0||strcmp(cmd_array[1],">>")==0)){//有重定向,格式是: dir > filename或者dir >> filename
			mydir_redirect();//调用带有重定向的dir
			return 1;
		}
		else{//没有重定向
			mydir();
			return 1;
		}
	}
	else if(strcmp(cmd_array[0],"set")==0){//I'll try latter
		printf("myshell: set: not supported currently\n");
		return 1;
	}
	else if(strcmp(cmd_array[0],"unset")==0){//I'll try latter
		printf("myshell: unset: not supported currently\n");
		return 1;		
	}
	else if(strcmp(cmd_array[0],"shift")==0){//I'll try latter
		printf("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_TRUNC
				dup2(output,1);//把stdout重定向到output
				close(output);
				cmd_array[i]=NULL;//把>替换成NULL
				i++;
				continue;//跳过
			}
			if(strcmp(cmd_array[i],">>")==0){//>>:在文件内容后追加
				int output=open(cmd_array[i+1],O_WRONLY|O_APPEND|O_CREAT,0666);//必须用O_APPEND
				dup2(output,1);//把stdout重定向到output
				close(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重定向到input
				close(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);//取得hostname
	getcwd(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) {//函数返回有效用户的id
		printf("#");//如果是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){//没有pipe
		return;
	}
	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++){}//什么都不做,只是为了得到i
	lastpos=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){//打印最后的字符时不要空格,直接break
			break;
		}
		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_TRUNC
				if(fd<0){
					perror("myshell: open");
					exit(1);
				}
				if((pid=fork())==0){//子进程
				dup2(fd,1);//把stdout重定向到fd
				for(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_APPEND
				if(fd<0){
					perror("myshell: open");
					exit(1);
				}
				if((pid=fork())==0){//子进程
				dup2(fd,1);//重定向stdout到fd
				for(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比实际的值少了1
	switch(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 Time
	printf("%d\n",1900+tp->tm_year);//必须加上1900,返回的值并不是完整的年份,比真实值少了1900
}

void myenviron(){
	//用environ[]来实现全局变量的打印
	//"environ" 必须实现被申明:exetern char** environ
	for(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_TRUNC
		if(fd<0){
			perror("myshell: open");
			exit(1);
		}
		if((pid=fork())==0){//子进程
			dup2(fd,1);//把stdout重定向到fd
			for(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_APPEND
		if(fd<0){
			perror("myshell: open");
			exit(1);
		}
		if((pid=fork())==0){//子进程
			dup2(fd,1);//把stdout重定向到fd
			for(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_dir
	char pathname[MAX_PATH_LEN];//储存路径名
	pwd=getpwuid(getuid());
	if(cmd_cnt==1){//如果只有一个cd
		strcpy(pathname,pwd->pw_dir);//获取pathname
		if(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){//如果是不带参数的help
		print_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_TRUNC
		if(fd<0){
			perror("myshell: open");
			exit(1);
		}
		if((pid=fork())==0){//子进程
			dup2(fd,1);//把stdout重定向到fd
			print_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_APPEND
		if(fd<0){
			perror("myshell: open");
			exit(1);
		}
		if((pid=fork())==0){//子进程
			dup2(fd,1);//重定向stdout到fd
			print_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,return
		return;
	}
	else{
		pid=fork();//fork一个进程出来,方便模拟exec
		if(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);//储存相关信息到buf
			mode=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退出这种奇怪的bug
	if(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_cnt
			num=0;//num也要清空
			for(int i=0;i<MAX_LINE&&cmdname[i]!='\0';i++){//清空cmdname
				cmdname[i]='\0';
			}
			fgets(cmdname,MAX_LINE,helper);//cmdname每次读取一行
			cmdname[strlen(cmdname)-1]='\0';//必须减1,调整到正确位置
			cptr=strtok(cmdname," ");//用空格分割命令
			while(cptr!=NULL){//然后把分割好的内容存到cmd_array
				cmd_array[num]=(char*)malloc(sizeof(*cptr));
				strcpy(cmd_array[num++],cptr);//需要注意的是即使直接回车num也是1,这样cmd_cnt至少是1		
				cptr=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();//获取子进程pid
				signal(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_TRUNC
			perror("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_APPEND
			perror("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);//这样一来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();//获取子进程pid
			signal(SIGINT,SIG_DFL);//默认:停止进程
			signal(SIGTSTP,SIG_DFL);//默认:终止进程
			signal(SIGCONT,SIG_DFL);//用默认的方式处理SIGCONT
			if(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);//用默认的方式处理SIGCONT
			if(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;
}