仅供参考
短学期作业之一,放上来做个纪念:
/***************************************************************************
*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;
}