手机网站模板网深圳小企业网站建设
news/
2025/9/22 17:48:44/
文章来源:
手机网站模板网,深圳小企业网站建设,网上做设计兼职哪个网站好点,如何进行网站备案本文旨在编写一个简单的shell外壳程序#xff01;功能类似于shell的一些基本操作#xff01;虽然不能全部实现shell的一些功能#xff01;但是通过此文章#xff0c;自己写一个简单的shell程序也是不成问题#xff01;并且通过此文章#xff0c;可以让读者对linux中一些环…本文旨在编写一个简单的shell外壳程序功能类似于shell的一些基本操作虽然不能全部实现shell的一些功能但是通过此文章自己写一个简单的shell程序也是不成问题并且通过此文章可以让读者对linux中一些环境变量等基本概念有更深的理解希望读完本篇文章能对读者有一定的收获文末会附带自己编写shell的源码 好的废话少说正文开始
首先我们先来看一下linux中的shell长什么样子 这是其shell刚启动的时候的样子其外貌就是一个中括号内部加上一系列的东西其实当我们认真观察不难发现里面包括的就是“用户名““”“主机名字”“当前工作路径”那么发现了此规律之后我们不难实现此描述框那么接下来我们就着手与这些描述框的实现
linux描述框的实现
其中要想获得我们的用户名我们其实可以通过环境变量进行获取那么该如何获取环境变量的值呢这里就不得不引进一个获得环境变量的值的函数了
getenv(“USER”) 通过查询man手册我们可以发现getenv函数只需要传递一个参数即可那么此参数是什么呢其实此参数就是我们想要获得环境变量的值的名字所以要想获得用户名我们可以直接使用getenv(USER)即可获得我们想要的用户名我们可以验证一下USER对应的环境变量是否真的是我们所要的环境变量名我们可以通过echo $USER 此命令来判断是否真的是我们想要的用户名 不难看出USER对应的环境变量确实是我们的用户名 getenv(“HOSTNAME”)
既然有了用户名那么我们的主机名如何获得呢思路还是调用getenvHOSTNAME操作获取主机名同样的也可以通过echo命令进行验证这里就不再累赘了 getenv(“PWD”)
最后再来获取我们的当前工作目录也是调用getenv函数同样的可以通过echo命令进行验证 那么这些基本的环境变量都出来了,我们是否可以通过上述思路来创建一个简单的描述框呢
代码如下 其中这里为了方便起见直接将各个函数进行封装保证代码的健壮性
其中还需要扩充的几点有
为了区别与系统的shell我们在描述框后面加上一个#以区分系统的$ 这样我们的描述框已经基本实现了
获取用户指令以及将其分割
那么基本的描述框已经实现了我们还需要做的一点就是获取用户输入的指令那么如何获取用户的指令呢思路很简单定义一个数组然后将用户输入的字符放到数组中即可那么能否用scanf函数呢答案是肯定不行因为用户输入的指令一般都是指令选项其中指令和选项之间都是有着空格来间隔区分的那么应该如何获取用户的输入呢答案很简单用fgets函数即可那么接下来我们就来介绍一下fgets函数的用法
fgets函数 通过查询man手册可以看出其中fgets函数中有三个参数第一个是就是缓冲区即将要被写到哪里的地址第二个参数表示此缓冲区的大小第三个参数是用哪些流进行写入一般第三个参数我们都选择stdin标准输入流进行写入
既然介绍了fgets函数的用法那么我们就知道我们需要创建一个数组来存放即将要写入的数据数组的大小自己来定义即可
那么用户的指令获取成功之后我们需要将用户的指令进行打散然后利用execvp进行替换即可那么如何进行打散这段字符串呢这里就不得不引进我们C语言中的strtok函数了
strtok()函数 查询man手册可以得知strtok有两个参数其中第一个参数是将要打散的原字符串第二个字符串指的是用于打散的标记符都有哪些。
返回值第一次调用返回标记符第一次出现的位置然后并将标记符转化为\0此时会记住此位置然后再次使用的使用第一个参数只需要传NULL指针即可如果最终不可再进行分割的时候返回值就会返回NULL这样就可以将原字符串进行打散我们的目的是想要将其打散放在一个数组中方便之后使用所以我们还需要自己再定义一个指针数组用于存放分割后的各个字符串
通过以上的思路我们就可以将用户命令和将命令打散此功能进行实现了
代码如下 其中第60行是将最后的\n转化为\0防止其进行跳行其中在commandSplit函数中我们还设置了条件宏用于检查我们的代码是否将原字符串进行正确的打断如果最后不想要打印出分割后的字符串可以将宏定义取消即可其中char*out[]表示打散后的数组char *in 表示的是原字符串spint是一个宏定义用来标明分割字符串都有哪些,这里分割符只要空格 至此描述框和获取用户指令都已经实现了
完成进程替换
那么我们如何将用户的指令转化为shell的操作呢这里就得引进进程替换的概念我们需要将进程进行替换来让他执行我们想让他执行的代码
那么进程替换有很多中调用方式我们应该选择那种呢其实很简单我们已经将用户的命令行进行打断分散处理了所以我们完全可以根据v的特性来进行选择又因为我们并不知道用户以后需要输入的指令所以我们也不知道其指令所在路径所以我们就可以使用execvp这个系统调用来进行进程替换其中v我们已经有了p默认为我们提供了路径所以用户的指令肯定是存放在v[0]上的所以我们的进程替换就可以写出来了
需要注意的是我们要进行进程替换的时候一定不要让我们的父进程进行替换因为一旦父进程进行替换的时候如果进程挂掉了那么我们的shell不就是结束了么所以我们可以使用fork来创建子进程来进行进程替换而父进程只需要等待子进程退出回收其资源即可
下面来看一下进程替换的代码 至此进程替换的指令也可以实现了我们自定义的shell程序也能实现ls top pwd 等操作了但是对于其他命令我们自定义的shell程序却不能正确的执行了例如cd命令还有export命令这是为什么呢这就不得不引进内建命令了
内建命令
何为内建命令呢内建命令就是这些命令只能由bash自己执行而不能让子进程进行执行那么我们常见的linux中有哪些命令是内建命令呢下面就来简单的介绍几个内建命令并且在我们自定义的shell中实现这些内建命令
cd命令
其中cd是一种常见的内建命令这个指令只能交付给父进程自己执行而不能交付给子进程让子进程执行因为cd指的就是改变当前的路径如果交给子进程进行执行那么父进程的路径将不会修改那么该如何进行编写我们shell中的cd命令呢
代码如下 其中cd主要进行的操作就是将当前的工作目录进行修改那么如何修改当前的工作目录呢这里就不得不引进chdir这个系统调用了
chdir 其中chdir函数只有一个参数这个参数代表的是将要修改的路径我们只需要定义一个字符数组然后将我们要修改的路径存放到此数组中然后将此数组就进行传递即可完成改变当前的路径其中还需要将当前的环境变量PWD也进行修改创建一个临时数组和全局数组全局用于存放环境变量的值然后将修改后的环境变量的值写入到全局数组中最后再将环境变量进行同步只需要调用putenv就可以将环境变量进行修改
export命令
还有一个常见的内建命令就是export那么什么是export呢export命令就是将我们定义的变量导入到环境变量之中下面来看一下如何实现我们自己shell的export命令
代码如下 其中我们需要定义一个全局变量的数组用于存放我们的环境变量的值如果我们使用的是局部变量的话就会导致每次用户输入命令的时候我们不更新环境变量其环境变量就会自动消失这是因为局部变量的局部性所以定义一个全局变量是最为合适的但是此代码也有一个小bug就是当再导入一个新的环境变量的时候之前的那个环境变量就会消失
既然环境变量也能导出了那么我们总得知道是否真正的将其导出了这里就得引出了echo命令了因为此命令也是内建命令所以也得交给我们的父进程自己执行下面就来写一下关于echo命令的代码
ehco命令 其中echo命令简单分为三个功能第一个是回显出退出码第二个是显示出环境变量的值第三个就是普通的回显字符串
对于第一个回显错误码我们只需要判断其分割后的第二个字符串是否是“$”即可然后根据$后面跟的字符即可判断出来如果$后面跟的是?字符的话那就是显示出退出码的信息如果“$”后面跟的不是?而是一个字符串那么就是显示出其环境变量的值最后如果连$字符都没有的话那就是简单的回显字符串了
这就是echo命令实现的简单逻辑了 但是我们写的shell还有大多数功能没有实现比如本地变量的存储以及重定向的操作对于本地变量的存储我们可以用malloc在堆内申请空间来存储变量中的值对于重定向我们可以利用dup函数进行重定向的操作下面来看一下简单的检查是否有重定向的函数吧
重定向 其中SkipSpace是一个宏其作用就是跳过空格的我们检查是否有重定向顺便也能将文件名与命令相分割然后在execu函数体内进行重定向的操作 这样我们的shell也能支持重定向的功能了 至此我们自己的shell已经初步完成了它能完成一些简单的操作希望读完本文读者也尝试写一下shell的实现
下面将源码附在下面
源码
#includestdio.h
#includestring.h
#includesys/types.h
#includefcntl.h
#includesys/stat.h
#includesys/wait.h
#includeunistd.h
#includestdlib.h
#define Size 50
#define NUM 1024
#define spint
//#define debug 1#define NOredir 0
#define AppendRedir 3
#define InputRedir 1
#define OutputRedir 2#define SkipSpace(pos) do{ while(isspace(*pos)) pos; }while(0)char *filenameNULL;
int redirNOredir;
int lastcode0;
char enval[1024];
char cwd[1024];
// char eni[1024];
const char* getUser()
{char* usergetenv(USER);if(user){return user;}else{return none;}}const char*getHost()
{char *hostgetenv(HOSTNAME);if(host){return host;}else{return none;}
}char*gethome()
{char *pwdgetenv(PWD);if(pwd){return pwd;}else{return none;}
}int getcommand(char*command,int n)
{printf([%s%s %s]#,getUser(),getHost(),gethome());char*rfgets(command,n,stdin);if(rNULL) return 0 ;command[strlen(command)-1]\0;return 1;
}void commandSplit(char *in,char *out[])
{int argc0;out[argc] strtok(in,spint);while(out[argc]strtok(NULL,spint));
#ifdef debug int i0;for(i0;out[i];i){// printf(%d:%s\n,i,out[i]);printf(%s\n,out[i]);}// printf(\n);
#endif
}//只需要将用户的命令行数组指令传递过来即可
int execute(char* argv[])
{pid_t ritfork();if(rit0){int fd0;if(redirInputRedir){fd open(filename, O_RDONLY); // 差错处理我们不做了dup2(fd, 0);}else if(redirOutputRedir){fd open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666);dup2(fd, 1);}else if(redirAppendRedir){fd open(filename, O_WRONLY | O_CREAT | O_APPEND, 0666);dup2(fd, 1);}else{//do nothing}//子进程用于进程切换而不是让父进程bash直接自己运行//其中进程替换直接用execvp函数即可因为我们有了用户的命令行了execvp(argv[0],argv);exit(0);//如果替换失败就会退出负责代表进程替换成功}else{int status0;//父进程只需要等待子进程退出即可!pid_t retwaitpid(rit,status,0);if(retrit){// printf(wait success\n);lastcode WEXITSTATUS(status);// printf(%d,lastcode);// return 0;}}return 0;
}void cd(const char*path)
{chdir(path);char tem[1024];getcwd(tem,sizeof(tem));sprintf(cwd,PWD%s,tem);putenv(cwd);}
//检查是否为内建命令并执行
int dobuildin(char*argv[])
{//cd命令if(strcmp(argv[0],cd)0){char *pathNULL;if(argv[1]NULL) {pathgethome(); }else pathargv[1];cd(path);return 1;}else if(strcmp(argv[0],export)0){ if(argv[1]NULL) return 1; strcpy(enval,argv[1]);// strcpy(envir,argv[1]);// putenv(envir);//此处需要用全局变量数组来存储env 因为一旦使用局部变量的时候会随着用户输入的指令putenv(enval);//此处需要用全局变量数组来存储env 因为一旦使用局部变量的时候会随着用户输入的指令//环境变量会消失return 1;}else if(strcmp(argv[0],echo)0){//与系统中的echo保持一致if(argv[1]NULL){printf(\n);return 1;}if(*(argv[1])$strlen(argv[1])1){char *valargv[1]1;if(strcmp(val,?)0){printf(%d\n,lastcode);lastcode0;}else{char *envalgetenv(val);if(enval) printf(%s\n,enval);else{printf(\n);}// return 1;}return 1;}else{printf(%s\n,argv[1]);return 1;}// return 1;}else if(0){}return 0;
}void checkRedir(char usercommand[], int len)
{// ls -a -l log.txt// ls -a -l log.txtchar *end usercommand len - 1;char *start usercommand;while(endstart){if((*end) ){if(*(end-1) ){*(end-1) \0;filename end1;SkipSpace(filename);redir AppendRedir;break;}else{*end \0;filename end1;SkipSpace(filename);redir OutputRedir;break;}}else if(*end ){*end \0;filename end1;SkipSpace(filename); // 如果有空格就跳过redir InputRedir;break;}else{end--;}}
}int main()
{while(1){char userCommand[NUM];char* argv[Size];//显示框架!获取用户输入的指令int n getcommand(userCommand,sizeof(userCommand));// if(n0) continue;// printf(%s)//将用户的命令进行切割checkRedir(userCommand,strlen(userCommand));commandSplit(userCommand,argv);//判断命令是否为内建命令1int kdobuildin(argv);if(k) continue;//创建子进程用于进行进程替换execute(argv);}// return 0;
}
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/909869.shtml
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!