当前位置: 首页 > news >正文

Linux:手搓shell

之前学了一些和进程有关的特性,什么进程控制啊进程替换啊,我们来尝试自己搓一个shell()吧

首先我们观察shell的界面,发现centos的界面上有命令提示符:

[主机名@用户名+当前路径]

我们可以通过调用系统函数获取当前路径,调用环境变量来获取我们的用户名、主机名

//打印命令提示行的函数,命令提示行包括用户名+主机名+当前工作目录
//此为获取用户名int checkChild();void Redirection();void checkRedir();int length(char* arr[]);
const char* getUser(){char* user=getenv("USER");if(user){return user;}else{return "None";}
}
//此为获取主机名
const char* getHost(){char* host=getenv("HOSTNAME");if(host){return host;}else{return "None";}
}
//此为获取当前路径
const char* getPwd(){static char cwd[SIZE];if(getcwd(cwd,sizeof(cwd))!=NULL){return cwd;}else{return "None";}
}

大概就长这样:

但是我发现了一个问题:写这个程序的时候我的Linux系统是centos,可以通过环境变量获取

但是我吧系统换成ubantu的时候,ubantu的环境变量设置里默认没有hostname这个环境变量

解决这个办法有两种:

1.在环境变量里添加我们的hostname,手动添加

2.通过C语言的gethostname函数来获取

我们这里用一下第二个吧:

const char* getUser(){static  char hostname[1024];if(gethostname(hostname,sizeof(hostname))==0){return hostname;}else{return "userNone";}}

无语了。。。写错接口了

应该是这样:

const char* getUser(){char* user=getenv("USER");if(user){return user;}else{return "usernameNone";}}
//此为获取主机名
const char* getHost(){static  char hostname[1024];if(gethostname(hostname,sizeof(hostname))==0){return hostname;}else{return "userNone";}
}
//此为获取当前路径
const char* getPwd(){static char cwd[SIZE];if(getcwd(cwd,sizeof(cwd))!=NULL){return cwd;}else{return "None";}
}

然后再加入一个整合上面三个函数的接口:

//这是整合上面三者的函数
void MakeCommandLine(){char line[SIZE];const char* username=getUser();const char* hostname=getHost();const char* cwd=getPwd();snprintf(line,sizeof(line), "[%s@%s %s]#", username, hostname, cwd);//snprintf是给定大小的更安全的向内存空间中写入的printf(写到缓冲区)printf("%s",line);fflush(stdout);//printf("[%s@%s %s]#",getUser(),getHost(),getPwd());
}

这个整合上面三个函数的接口中使用了snprintf(),平常我们打印字符串到显示器上使用的是printf(),根据我们之前学习的缓存区的概念,snprintf其实就是把数据输入到缓存区内,sprintf函数也可以,但ssnprintf函数更安全

然后再刷新输出

这样就可以正常显示了(我恨你ubantu)

命令提示符,就是提示我们输入命令。如何让我们自己写的shell获取我们输入的命令?

我们在输入指令时,指令是有选项和目标文件的,例如:ls的常用选项有ls -a, ls -l,我们发现命令本身和选项之间是有空格的,并且内核在拿到我们输入的指令时,需要先查找指令,再查找选项。两个重要的工作:获取字符串&打散我们的字符串

获取字符串:我们肯定知道不能用scanf,因为scanf不能读取空格,上面函数可以按照行获取输入的数据?

fgets()

如何打散我们的字符串:使用函数strtok()

合起来的效果就是:

getCommand()用来获取命令,其中的ZERO是一个宏定义,在用户输入\n的时候,要将\n当\0对待,使命令有效

commandSplit()来打散字符串,SEP也是一个宏定义,代表空格,意思是在in这个数据中,遇到空格就断开,下面被注释的一段是检验是否打散完成

我们获取完命令后,就要执行。我们之前学习了环境变量和进程替换的知识。我们不能输入的命令替代我们的这个shell的进程,不然就被覆盖了。所以我们要在这个shell里面写一个子进程,这个子进程来执行我们的命令

我们只需要把我们调用的子进程导入就好了

但是子进程除了调用我们本来有的命令,还可以使用我们Linux下的小特性:重定向

所以我们可以把重定向的功能也加进去:

//进程替换
int execute(){pid_t id=fork();//创建子进程if(id==0){int fd;printf("redir=%d\n",redir);printf("filename=%s",filename);if(redir==3){fd=open(filename,O_RDONLY);if(fd<0){perror("open output file");exit(EXIT_FAILURE);}dup2(fd,STDIN_FILENO);close(fd);}else if(redir==2){fd=open(filename,O_WRONLY | O_CREAT | O_TRUNC,0666);if (fd < 0) {perror("open output file");dup2(fd,STDIN_FILENO);close(fd);}else if(redir==2){fd=open(filename,O_WRONLY | O_CREAT | O_TRUNC,0666);if (fd < 0) {perror("open output file");exit(EXIT_FAILURE);}dup2(fd,STDOUT_FILENO);close(fd);}else if(redir==1){fd=open(filename,O_WRONLY | O_CREAT | O_APPEND,0666);if (fd < 0) {perror("open output file");exit(EXIT_FAILURE);}dup2(fd,STDOUT_FILENO);close(fd);}else{printf("son process dafult");}// printf("son process begining...");execvp(argv[0],argv);//替换进程exit(EXIT_FAILURE);//替换失败就会退出}else{int status=0;pid_t rid=waitpid(-1,&status,0);if(rid==id){//父进程在这里只需要等待子进程就好了printf("wait success\n");lastcode=WEXITSTATUS(status);printf("%d",lastcode);return 0;}}
return 0;}

其中有一个bug是我一直在改的,在执行echo 1234 > log.txt 的时候,是应该echo的基本命令:打印到显示器上,还是执行重定向,把结果输出在文件内呢?

所以其中的redir就是一个判定的标准,在执行命令的时候,先判定它是不是Echo。如果是Echo则子执行我们设定的Echo命令如果不是Echo,那么执行重定向。

void checkRedir(){//ls -a -l > log.txt//ls -a -l >> log.txt// char* filename=NULL;int len=strlen(userCommand);char* start=userCommand;char* end=userCommand+len-1;while(end>start){if((*end)=='>'){if(*(end-1)=='>'){*(end-1)='\0';filename=end+1;SkipSpace(filename);//如果有空格,就跳过redir=1;break;}else{*end='\0';filename=end+1;SkipSpace(filename);redir=2;break;}}else if(*end=='<'){*end='\0';filename=end+1;SkipSpace(filename);redir=3;break;}else{end--;}}
}

判断是不是echo:

//echo的内建命令
int echo(){if(strcmp(argv[0],"echo")==0){if(argv[1]==NULL){printf("\n");return 1;}if(*(argv[1])=='$'&&strlen(argv[1])>1){char *val=argv[1]+1;if(strcmp(val,"?")==0){printf("%d\n",lastcode);lastcode=0;}else{char* enval=getenv(val);if(enval){printf("%s\n",enval);}else{printf("\n");}}return 1;}if(redir!=0)return 0;}return 1;
}

除了Echo命令和重定向,我们还需要实现CD命令,也就是切换当前目录,并且在切换当前目录的同时改变我们命令提示符的当前路径选项:

//切换home路径
const char*Home(){const char* home=getenv("Home");if(home==NULL){return "/";}return home;
}//改变路径的函数
void cd(){const char* path=argv[1];if(path==NULL){path=Home();//如果为空回到家目录}if(chdir(path)==0){setenv("PWD",getcwd(NULL,0),1);//setenv函数会修改进程的环境变量;修改后只有当前进程及其子进程能够看到这些变化}else{perror("cd faild");}
}

判断内建命令、环境变量里的命令的函数:

int checkChild(){int yes=0;const char* enter_cmd=argv[0];if(strcmp(enter_cmd,"cd")==0){yes=1;cd();}else{if(strcmp(enter_cmd,"echo")==0){if(redir==0){return echo();}}}return 0;
}

大概就是这样。。。

放一下源码:

#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
#include<sys/types.h>
#include<errno.h>
#include<sys/wait.h>
#include<ctype.h>
#include<fcntl.h>//open函数的库,是POSIX的系统调用函数#define SIZE 512
#define ZERO '\0'//剔除\n
#define SEP " "
#define NUM 32char* filename;
int lastcode=0;
char* argv[SIZE];//被打散的命令存这里
char userCommand[SIZE];//输入的命令存这里       
//打印命令提示行的函数,命令提示行包括用户名+主机名+当前工作目录
//此为获取用户名
int redir=0;
#define SkipSpace(pos) do{while(isspace(*pos)) pos++; }while(0)//跳过空格的函数int checkChild();void Redirection();void checkRedir();int length(char* arr[]);const char* getUser(){char* user=getenv("USER");if(user){return user;}else{return "usernameNone";}}
//此为获取主机名
const char* getHost(){static  char hostname[1024];if(gethostname(hostname,sizeof(hostname))==0){return hostname;}else{return "userNone";}
}
//此为获取当前路径
const char* getPwd(){static char cwd[SIZE];if(getcwd(cwd,sizeof(cwd))!=NULL){return cwd;}else{return "None";}
}
//这是整合上面三者的函数
void MakeCommandLine(){char line[SIZE];const char* username=getUser();const char* hostname=getHost();const char* cwd=getPwd();snprintf(line,sizeof(line), "[%s@%s %s]#", username, hostname, cwd);//snprintf是给定大小的更安全的向内存空间中写入的printf(写到缓冲区)printf("%s",line);fflush(stdout);//printf("[%s@%s %s]#",getUser(),getHost(),getPwd());
}
//获取用户命令
int getCommand(char userCommand[],int n){char* s=fgets(userCommand,n,stdin);//使用fgets()函数获取命令if(s==NULL){                                  return -1;}userCommand[strlen(userCommand)-1]=ZERO;return strlen(userCommand);
}                         
//分散字符串
void commandSplit(char* in,char* out[]){//in是输入的字符串,out[]是打散的字符数组int argc=0;out[argc++]=strtok(in,SEP);//此处的SEP是宏定义,SEP是空格的意思while((out[argc++]=strtok(NULL,SEP))!=NULL);out[argc]=NULL;
#ifdef debug int i=0;for(i=0;out[i];i++){printf("%s\n",out[i]);}
#endif
}
//进程替换
int execute(){pid_t id=fork();//创建子进程if(id==0){int fd; printf("redir=%d\n",redir);printf("filename=%s",filename);if(redir==3){fd=open(filename,O_RDONLY);if(fd<0){perror("open output file");exit(EXIT_FAILURE);}dup2(fd,STDIN_FILENO);close(fd);}else if(redir==2){fd=open(filename,O_WRONLY | O_CREAT | O_TRUNC,0666);if (fd < 0) {perror("open output file");exit(EXIT_FAILURE);}dup2(fd,STDOUT_FILENO);close(fd);}else if(redir==1){fd=open(filename,O_WRONLY | O_CREAT | O_APPEND,0666);if (fd < 0) {perror("open output file");exit(EXIT_FAILURE);}dup2(fd,STDOUT_FILENO);close(fd);}else{printf("son process dafult");}// printf("son process begining...");execvp(argv[0],argv);//替换进程exit(EXIT_FAILURE);//替换失败就会退出}else{int status=0;pid_t rid=waitpid(-1,&status,0);if(rid==id){//父进程在这里只需要等待子进程就好了printf("wait success\n");lastcode=WEXITSTATUS(status);printf("%d",lastcode);return 0;}   }
return 0;  }//切换home路径
const char*Home(){const char* home=getenv("Home");if(home==NULL){return "/";}return home;
}//改变路径的函数
void cd(){const char* path=argv[1];if(path==NULL){path=Home();//如果为空回到家目录}if(chdir(path)==0){setenv("PWD",getcwd(NULL,0),1);//setenv函数会修改进程的环境变量;修改后只有当前进程及其子进程能够看到这些变化}else{perror("cd faild");}
}
//echo的内建命令
int echo(){if(strcmp(argv[0],"echo")==0){if(argv[1]==NULL){printf("\n");return 1;}if(*(argv[1])=='$'&&strlen(argv[1])>1){char *val=argv[1]+1;if(strcmp(val,"?")==0){printf("%d\n",lastcode);lastcode=0;}else{char* enval=getenv(val);if(enval){printf("%s\n",enval);}else{printf("\n");} }return 1;}if(redir!=0)return 0;}return 1;
}
void checkRedir(){//ls -a -l > log.txt//ls -a -l >> log.txt// char* filename=NULL;int len=strlen(userCommand);char* start=userCommand;char* end=userCommand+len-1;while(end>start){if((*end)=='>'){if(*(end-1)=='>'){*(end-1)='\0';filename=end+1;SkipSpace(filename);//如果有空格,就跳过redir=1;break;}else{*end='\0';filename=end+1;SkipSpace(filename);redir=2;break;}}else if(*end=='<'){*end='\0';filename=end+1;SkipSpace(filename);redir=3;break;}else{end--;}}
}int checkChild(){int yes=0;const char* enter_cmd=argv[0];if(strcmp(enter_cmd,"cd")==0){yes=1;cd();}else{if(strcmp(enter_cmd,"echo")==0){if(redir==0){return echo();}}} return 0;
}int length(char* arr[]){int i=0;while(arr[i]!=NULL){i++;}return i;}int main(){while(1){MakeCommandLine();getCommand(userCommand,sizeof(userCommand));redir=0;filename=NULL;checkRedir();commandSplit(userCommand,argv);if(checkChild())continue;execute();} return 0;
}

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 研究生深度学习入门的十天学习计划------第六天
  • 基于激光雷达的无人机相互避障
  • Linux---FTP文件服务器搭建及实战
  • Unity URP支持多光源阴影
  • mpc_local_planner的编译问题
  • 搭建和使用OnFinality?
  • ML16_转移矩阵、平稳分布和详细平衡条件
  • uniapp插槽用法
  • 【出行计划 / 2】
  • 在SpringMVC中用fmt标签实现国际化/多语言
  • Express与SQLite集成教程:轻松实现数据库操作
  • 每周12600元奖金池,邀你与昇腾算力共舞,openMind开发者盛宴启幕!
  • 专利申请全攻略:一步一步详解申请流程
  • “Flash闪存”介绍 及 “SD NAND Flash”产品的测试含例程
  • 手撕Python之散列类型
  • Date型的使用
  • Flex布局到底解决了什么问题
  • Git 使用集
  • GitUp, 你不可错过的秀外慧中的git工具
  • Hexo+码云+git快速搭建免费的静态Blog
  • Javascript基础之Array数组API
  • JS基础篇--通过JS生成由字母与数字组合的随机字符串
  • miniui datagrid 的客户端分页解决方案 - CS结合
  • Python学习笔记 字符串拼接
  • vue2.0一起在懵逼的海洋里越陷越深(四)
  • 分享几个不错的工具
  • 开源SQL-on-Hadoop系统一览
  • 如何使用 JavaScript 解析 URL
  • 吐槽Javascript系列二:数组中的splice和slice方法
  • 运行时添加log4j2的appender
  • 自定义函数
  • 【干货分享】dos命令大全
  • 阿里云服务器如何修改远程端口?
  • 积累各种好的链接
  • ​经​纬​恒​润​二​面​​三​七​互​娱​一​面​​元​象​二​面​
  • # 职场生活之道:善于团结
  • ###项目技术发展史
  • $.extend({},旧的,新的);合并对象,后面的覆盖前面的
  • (1)常见O(n^2)排序算法解析
  • (26)4.7 字符函数和字符串函数
  • (31)对象的克隆
  • (C++哈希表01)
  • (delphi11最新学习资料) Object Pascal 学习笔记---第7章第3节(封装和窗体)
  • (备忘)Java Map 遍历
  • (附源码)小程序儿童艺术培训机构教育管理小程序 毕业设计 201740
  • (个人笔记质量不佳)SQL 左连接、右连接、内连接的区别
  • (十八)Flink CEP 详解
  • (图)IntelliTrace Tools 跟踪云端程序
  • (学习日记)2024.01.19
  • (一)python发送HTTP 请求的两种方式(get和post )
  • (转)程序员疫苗:代码注入
  • (转)项目管理杂谈-我所期望的新人
  • ****** 二十三 ******、软设笔记【数据库】-数据操作-常用关系操作、关系运算
  • .bat批处理(四):路径相关%cd%和%~dp0的区别
  • .NET CORE 第一节 创建基本的 asp.net core