bash就是命令行解释器,就是Linux操作系统让我们看到的,与用户进行交互的一种外壳(shell),当然了bash也是一个进程,它有时候就是通过创建子进程来执行我们输入的命令的。这无疑就离不开我们上篇博客所说的进程程序替换,就是让子进程去替换我们的命令进程,知道了它的原理,我们就可以试着自己写一个bash进程。
我们可以大体上对于整个过程来细分一下:
1.bash就是先打印命令行提示符,就是下面这个东西,获取用户输入的命令
命令行提示符由 [用户名@主机名 路径]# 这些构成
2.对于用户输入的字符串进行分割成命令
3.执行这个命令
其实大体上就是这三步,下面还有很多的细节问题,我们遇到再说
我们目前写的这个代码的结果是
为什么打印了两个换行?我们了解一下fgets这个函数
因为fgets会读入一个换行,就是我们敲完abc后的换行,它是不应该有的,我们要给它去掉
这样就可以把换行改成0了。
因为bash就是一只在等待用户输入指令,所以我们要把我们的程序写成一个循环。
还有一个问题,就是当我们什么都不输入,直接回车时,我们其实就没必要在执行下边了,直接回到循环最开始就可以,于是我们可以做一个判断就是如果输入的命令的长度为0,那么就直接回到循环最开始处,这里的长度可以让Interactive的返回值来给。
下一步就是对于字符串进行分割:
我们分割的话,函数可以将收到的字符串分割后放到一个全局变量中,方便后续的使用,我们C语言有一个分割字符串的函数,叫strtok
分割完了之后,我们就可以开始执行命令了,就是让子进程执行,父进程等待。我们这里先是一些普通的需要子进程去执行的命令,因为有的命令需要父进程去执行,比如cd,需要父进程去切换路径,子进程是不行的
那么截止到现在,我们的程序已经可以像bash一样处理一些命令了,但是还不够,因为有一些内建命令,就是需要父进程去执行的:
这些命令,我们要在代码被子进程执行前先行判断,如果是内建命令,那么让父进程去执行,否则再让子进程去执行
我们这里先以cd命令为例,如果判断出来了用户就是要输入cd命令,如果后边什么都没有,那么默认是回到家目录,如果有,那就chdir到那个目录,并且不要忘记命令行提示符可是一直通过环境变量PWD来打印我们当前所在目录的,所以我们还要通过putenv把PWD环境变量改一下,它默认是会覆盖的。
之后我们要知道export也是一个内建命令,export的作用是给自己设置一个环境变量,如果给子进程设置,那显然是不合理的,所以我们也需要处理一下。
下一个就是echo,我们的echo通常会有这么几种用法:
1.echo后什么都不加
2.后加$?表示打印最近一次进程的退出码
3.后随便打印一串字符
4.后加$环境变量,就打印环境变量的内容
下面是所有的代码,有不足的可以添加:
1 #define _XOPEN_SOURCE 2 #include<stdio.h>3 #include<stdlib.h>4 #include<string.h>5 #include<unistd.h>6 #include<sys/types.h>7 #include<sys/wait.h>8 #include<stdlib.h>9 #define SIZE 102410 #define MAX_ARGC 30//最大命令行字符串个数11 #define SEP " "//设置分隔符为空格12 char*argv[MAX_ARGC];13 int lastcode;//最近一次进程退出码14 int Interactive(char commandline[],int size)15 {16 printf("[%s@%s %s]$ ",getenv("USER"),getenv("HOSTNAME"),getenv("PWD"));17 fgets(commandline,size,stdin);18 commandline[strlen(commandline)-1]='\0';19 return strlen(commandline);20 }21 void Splitstr(char commandline[])22 {23 int i=0;24 argv[i++]=strtok(commandline,SEP);25 while(argv[i++]=strtok(NULL,SEP));26 if(strcmp("ls",argv[0])==0)27 {28 argv[i-1]="--color";29 argv[i]=NULL;30 }31 }32 void Execute()33 { 34 if(strcmp("ll",argv[0])==0&&!argv[1])//特别处理ll35 {36 argv[0]="ls";37 argv[1]="-l";38 }39 pid_t id=fork();40 if(id==0)41 {42 execvp(argv[0],argv);43 printf("mybash: ");44 for(int i=0;argv[i];i++)printf("%s ",argv[i]);45 printf(": not found your command\n");46 exit(2);47 }48 int status=0;49 pid_t rid=waitpid(id,&status,0);50 if(rid>0)lastcode=WEXITSTATUS(status);51 }52 int Bulidincmd()53 {54 int ret=0;55 if(strcmp("cd",argv[0])==0)56 {57 ret=1;58 char*target=argv[1];59 if(!target)target=getenv("HOME");60 chdir(target);61 char tmp[SIZE];62 snprintf(tmp,SIZE,"PWD=%s",target);63 putenv(tmp); 64 }65 else if(strcmp("export",argv[0])==0)66 {67 ret=1;68 if(argv[1])69 {70 char tmp[SIZE];71 strncpy(tmp,argv[1]+1,strlen(argv[1])-2);72 putenv(tmp);73 }74 }75 else if(strcmp("echo",argv[0])==0)76 {77 ret=1;78 if(argv[1]&&argv[1][0]=='$')79 {80 if(argv[1][1]=='?'&&!argv[2])81 {82 printf("%d\n",lastcode);83 lastcode=0;84 }85 else86 {87 char* tmp=getenv(argv[1]+1); 88 if(tmp)printf("%s\n",tmp);89 }90 }91 else92 {93 for(int i=1;argv[i];i++)printf("%s ",argv[i]);94 printf("\n");95 }96 }97 lastcode=0;98 return ret;99 }100 int main()101 {102 while(1)103 {104 char commandline[SIZE];105 //打印命令行提示符,获取用户输入的命令字符串106 int n=Interactive(commandline,SIZE);107 if(n==0)continue;108 //分割字符串成命令行参数109 Splitstr(commandline);110 //处理内建命令111 n=Bulidincmd();112 if(n==1)continue;113 //执行命令114 Execute(); 115 }116 return 0;117 }