如果学习了常见的Linux命令,exec*函数,环境变量,那你就可以尝试一下写一个简单的shell;
下面跟着我的步骤来吧!!🤩🤩
输入命令行
既然要写一个shell,我们第一步先把这个输入命令行打印出来:
观察一下:命令行中有三个环境变量,我们如何查找它们呢?
这就需要使用env查找:发现USER、HOSTNAME、PWD;
怎么用代码来获取这三个环境变量对应的值呢?
用:getenv函数
有关函数更详细的内容可以区man手册上区查看
接下来,我们就可以写代码了:(主要代码,完整代码在文章最后)
void Makecommandline(){char cmd[SIZE];const char *name=Getname();const char *hostname = Gethostname();const char *cwd =Getcwd();snprintf(cmd,sizeof(cmd),"[%s@%s %s]> ",name,hostname,cwd);printf("%s",cmd);fflush(stdout);}
获取用户命令
想一下,我们输入一个命令时,有时候是ls,有时候是ls -l -a ,输入的有空格怎么办呢?🤖
这时就可以用fgets函数来输入命令;
void Getusercommand(char Usercmd[]){char *s = fgets(Usercmd,sizeof(Usercmd),stdin);if(s==NULL){perror("fgets");exit(1);}Usercmd[strlen(Usercmd)-1]='\0';}
命令行字符串分割
获取到用户命令后,接下来我们要对这个命令进行分割:
分割用的是strtok函数;
代码:
void Splitcommand(char Usercmd[]){argv[0]=strtok(Usercmd,SEP);int index=1;while(argv[index++]=strtok(NULL,SEP));}
执行命令
完成上述的准备工作后,我们要执行我们输入的命令,那要怎么执行呢?
🤡当然是用的我们的exec*函数喽,这里根据实际情况,我们选择的应该是execvp()函数;
💥💥注意:我们使用exec*函数时,要创建一个子进程来进行,这样才不会使父进程中后续代码被覆盖!!
代码:
void Executecommand(){pid_t id =fork();if(id<0){exit(1);}else if(id==0){execvp(argv[0],argv);exit(errno);}else{int status=0;pid_t rid =waitpid(id,&status,0);if(rid>0){//wait sucesslastcode = WEXITSTATUS(status);if(lastcode!=0){printf("%s:%s:%d\n",argv[0],strerror(lastcode),lastcode);}}}}
检测命令是否是内建命令
😺😺完成上述的四部后,你就已经完成了一个简单的shell,但是并不完整;哪里不完整呢?我们可以输入一个内建命令,比如cd命令,这时我们就找到了要完善的地方;
怎么完善呢?
我们要判断一下这个命令是不是内建命令,怎么判断呢,非常简单,直接if语句:
如果有其他的内建命令,直接添加即可;
Cd():如果是内建cd命令,看一下argv[1]是不是空,
(1)如果是空,我们输入的命令就是cd,那不就是直接回到家目录了嘛,如果不为空,就不要变了;
(2)接下来,直接改变cwd即可,用chdir函数;
(3)修改环境变量PWD的值,用putenv函数;
💥💥注意:putenv
用于将一个字符串添加到环境变量中,或者修改已经存在的环境变量。这个字符串的格式通常是 "NAME=VALUE"
,其中 NAME
是环境变量的名称,VALUE
是其对应的值。
你已经完成了一个基础的shell,更完善的shell还会继续更新!!
完整代码:
1 #include<stdio.h>2 #include<unistd.h>3 #include<stdlib.h>4 #include<string.h>5 #include<sys/types.h>6 #include<sys/wait.h>7 #include<errno.h>8 9 # define SkipPath(p) do{p+=(strlen(p)-1);while(*p!='/')p--;}while(0)10 11 12 #define SIZE 51213 #define SEP " "14 15 16 char cwd[SIZE*2];17 int lastcode =0;18 char *argv[SIZE*2];19 20 const char *Getname()21 {22 const char *name=getenv("USER");23 if(name==NULL)return "None";24 return name;25 }26 27 28 const char *Gethostname()29 {30 const char *hostname=getenv("HOSTNAME");31 if(hostname==NULL)return "None";32 return hostname;33 }34 35 36 const char *Getcwd()37 {38 const char *pwd=getenv("PWD");39 if(pwd==NULL)return "None";40 return pwd;41 }42 43 void Makecommandline()44 {45 char cmd[SIZE];46 const char *name=Getname();47 const char *hostname = Gethostname();48 const char *cwd =Getcwd();49 SkipPath(cwd);50 snprintf(cmd,sizeof(cmd),"[%s@%s %s]> ",name,hostname,strlen(cwd)==1 ? "/":cwd+1);51 printf("%s",cmd);52 fflush(stdout);53 }54 55 void Getusercommand(char Usercmd[])56 {57 char *s = fgets(Usercmd,sizeof(Usercmd),stdin);58 if(s==NULL)59 {60 perror("fgets");61 exit(1);62 }63 Usercmd[strlen(Usercmd)-1]='\0';64 }65 66 void Splitcommand(char Usercmd[])67 {68 argv[0]=strtok(Usercmd,SEP);69 int index=1;70 while(argv[index++]=strtok(NULL,SEP));71 }72 const char *Gethome()73 {74 const char *home =getenv("HOME");75 if(home==NULL)return "/root";76 return home;77 }78 void Cd()79 {80 const char *path =argv[1];81 if(path==NULL)82 {83 //返回家目录84 path = Gethome();85 }86 chdir(path);87 char tmp[SIZE*2];88 getcwd(tmp,sizeof(tmp));89 snprintf(cwd,sizeof(cwd),"PWD=%s",tmp);90 putenv(cwd);91 //printf("%s\n",cwd);92 }93 int Checkbuildin()94 {95 int yes=0;96 if(strcmp(argv[0],"cd")==0)97 {98 yes=1;99 Cd();
100 }
101 return yes;
102 }
103 void Executecommand()
104 {
105 pid_t id =fork();
106 if(id<0)
107 {
108 exit(1);
109 }
110 else if(id==0)
111 {
112 execvp(argv[0],argv);
113 exit(errno);
114 }
115 else
116 {
117 int status=0;
118 pid_t rid =waitpid(id,&status,0);
119 if(rid>0)
120 {
121 //wait sucess
122 lastcode = WEXITSTATUS(status);
123 if(lastcode!=0)
124 {
125 printf("%s:%s:%d\n",argv[0],strerror(lastcode),lastcode);
126 }
127 }
128 }
129 }
130 int main()
131 {
132 while(1)
133 {
134 //1、我们自己输入一个命令行
135 Makecommandline();
136
137 //2、获取用户命令字符串分割
138 char Usercmd[SIZE];
139 Getusercommand(Usercmd);
140 //printf("%s\n",Usercmd);
141 //3、命令行字符串分割
142 Splitcommand(Usercmd);
143 //4、检测命令是否是内建命令
144 int n = Checkbuildin();
145 if(n)continue;
146 //5、执行命令
147 Executecommand();
148 }
149 return 0;
150 }