Linux:手搓shell

news2024/9/21 16:38:42

之前学了一些和进程有关的特性,什么进程控制啊进程替换啊,我们来尝试自己搓一个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 32

char* 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;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2101934.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C语言代码练习(第十二天)

今日练习&#xff1a; 28、&#xff08;指针&#xff09;将字符串 a 复制为字符串 b &#xff0c;然后输出字符串 b 29、改变指针变量的值 30、输入两个整数&#xff0c;然后让用户选择1或者2&#xff0c;选择1是调用 max &#xff0c;输出两者中的大数&#xff0c;选择2是调用…

Mac M1 安装Hadoop教程(安装包安装)

一、引言 前面一期&#xff0c;我分享了通过homebrew方式安装Hadoop&#xff0c;本期我将通过安装包方式介绍下hadoop如何安装。二、下载open jdk8 官方下载地址 注意如果是x86架构的苹果电脑&#xff0c;Architecture选择x86 64-bit或者 x86-32bit。 下载后&#xff0c;将得…

Axios前后端对接

前端&#xff1a; 通过GET获取元素&#xff1a; console.log(res),接收接口返回的数据并打印出来。 async:是异步的知识。 通过POST修改&#xff0c;更新元素&#xff1a; 后端&#xff1a; 通过前端从后端获取一个对象&#xff1a; 后端执行相应方法&#xff1a; 然后获取L…

Spring6学习笔记2:容器IoC

文章目录 3 容器&#xff1a;IoC3.1 IoC容器3.1.2 依赖注入3.1.3 IoC容器在Spring的实现 3.2 基于XML管理Bean3.2.1 搭建子模块spring6-ioc-xml3.2.2 实验一&#xff1a;获取bean①方式一&#xff1a;根据id获取②方式二&#xff1a;根据类型获取③方式三&#xff1a;根据id和类…

day-48 一个小组的最大实力值

思路 想把所有非零数相乘&#xff0c;再统计负数的个数&#xff0c;如果负数为奇数个&#xff0c;则把乘机除以最大的那个负数即为答案&#xff0c;如果为偶数个&#xff0c;那么乘机即为答案 解题过程 但要考虑特殊情况&#xff1a;1.只有零和一个负数&#xff0c;返回零 2.全…

深入浅出Stream流

Java 8的新特性之一就是流stream&#xff0c;配合同版本出现的 Lambda &#xff0c;使得操作集合&#xff08;Collection&#xff09;提供了极大的便利。 案例引入 在JAVA中&#xff0c;涉及到对数组、Collection等集合类中的元素进行操作的时候&#xff0c;通常会通过循环的…

cuda编程[1]:一二三维网格和块的核函数

目录 前言核函数一维二维三维结果分析 前言 所有的代码下载链接&#xff1a;code。以下代码展示了如何在 CUDA 中打印网格和线程的索引信息。代码包括一维、二维和三维的网格和块的设置&#xff0c;并定义了多个内核函数来输出当前的索引信息。 核函数 打印线程索引 __gl…

七、场景加载

一、新的场景加载方法 Unity在2023更新的一个方法 1、引用命名空间 2、调用代码 传入加载场景SO 注&#xff1a;此方法是 await 方法名 步骤&#xff1a;var s获取返回值&#xff1b;await返回加载内容&#xff1b;if(判断一下) 此时运行会出现&#xff1a;未卸载当前地图…

SPP/SPPF/Focal Module

一、在图像的分类任务重&#xff0c;卷积神经网络&#xff08;CNN&#xff09;一般含有5层&#xff1a; 输入层卷积层激活层池化层全连接层 全连接层通常要求输入为一维向量。在CNN中&#xff0c;卷积层和池化层的输出特征图会被展平&#xff08;flatten&#xff09;为一维…

华为云征文|华为云Flexus云服务器X实例部署Note Mark笔记工具

华为云征文&#xff5c;华为云Flexus云服务器X实例部署Note Mark笔记工具 前言一、Flexus云服务器X实例介绍1.1 Flexus云服务器X实例简介1.2 Flexus云服务器X实例特点1.3 Flexus云服务器X实例使用场景 二、Note Mark 介绍2.1 Note Mark 简介2.2 Note Mark 特点2.3 Note Mark 使…

iOS分渠道统计不再难,Xinstall帮你轻松搞定

在App推广和运营的过程中&#xff0c;iOS分渠道统计一直是一个令人头疼的问题。如何准确追踪各个渠道的推广效果&#xff1f;如何优化投放策略以提高转化率&#xff1f;这些问题困扰着无数推广者。今天&#xff0c;我们就来聊聊Xinstall这款强大的分渠道统计工具&#xff0c;看…

【自由能系列(中级)】自由能与变分自由能——从状态到配置的效益最大化【代码模拟】

自由能与变分自由能——从状态到配置的效益最大化 关键词提炼 #自由能 #变分自由能 #状态函数 #配置函数 #效益最大化 #物理系统 #优化问题 第一节&#xff1a;自由能与变分自由能的类比与核心概念 1.1 自由能与变分自由能的类比 自由能和变分自由能可以被视为物理系统的“…

Mysql高阶语句(1)

一、常用查询 1. 按关键字排序 使用 ORDER BY 语句对查询结果进行排序&#xff0c;可以按照一个或多个字段进行升序&#xff08;ASC&#xff09;或降序&#xff08;DESC&#xff09;排列 语法 SELECT column1, column2, ... FROM table_name ORDER BY column1 [ASC|DESC], c…

sqli-lab靶场学习(一)——Less1

前言 最近一段时间想切入安全领域&#xff0c;因为本身有做数据库运维工作&#xff0c;就打算从sql注入方向切入。而sql注入除了学习日常书本上的概念外&#xff0c;需要有个实践的环境&#xff0c;刚好看到sqli-lab这个靶场&#xff0c;就打算先用这个来学习。 安装部署 网上…

智能提醒助理系列-协作工具,一站式软件研发管理平台

本系列文章记录“智能提醒助理”wx公众号 建设历程。 一、需求分析 当前智能提醒产品体系为微信公众号小程序的模式。 以小程序为操作主体&#xff0c;公众号作为用户接收提醒的方式之一&#xff0c;还有短信和电话。 开发方式为自研&#xff0c;需要前端小程序服务端三方对…

天津自学考试转考流程及免冠照片处理方法说明

自学考试省际转考是指考生因为工作、生活或其他原因&#xff0c;需要将自学考试的考籍从一个省份转移到另一个省份继续参加自学考试的情况。在中国&#xff0c;自学考试是一种国家承认的学历教育形式&#xff0c;由各省、自治区、直辖市的教育考试机构负责组织实施。下面详细介…

必看|助攻|2024“高教社杯“全国大学生数学建模竞赛冲刺建议

数模国赛冲刺阶段小Tips tips0:赛中小天为大家带来助攻&#xff0c;请关注主页 赛前准备 01 加强赛前训练 写作队员&#xff1a;阅读往年获奖论文&#xff0c;关注思路、表达和排版。建模队员&#xff1a;了解各类模型特点和应用场景。编程队员&#xff1a;多做算法编程和数…

Datawhale X 李宏毅苹果书 AI夏令营(深度学习 之 实践方法论)

1、模型偏差 模型偏差是指的是模型预测结果与真实值之间的差异&#xff0c;这种差异不是由随机因素引起的&#xff0c;而是由模型本身的局限性或训练数据的特性所导致的。 简单来讲&#xff0c;就是由于初期设定模型&#xff0c;给定的模型计算能力过弱&#xff0c;导致在通过…

vector的实现

目录 1.vector的底层 2.vector构造函数的实现 ①构造函数 ②拷贝构造 3.访问函数实现 3.1迭代器iterator 3.2下标[]访问 4.析构函数和计算size、capacity、swap简单函数的实现 ①析构函数&#xff1a; ②计算size&#xff1a; ③计算capacity&#xff1a; ④swap函…

嵌入式软件--51单片机 DAY 3

一、独立按键 按键的作用相当于一个开关&#xff0c;按下时接通&#xff08;或断开&#xff09;&#xff0c;松开后断开&#xff08;或接通&#xff09;。 &#xff08;1&#xff09;需求 通过SW1、SW2、SW3、SW4四个独立按键分别控制LED1、LED2、LED3、LED4的亮灭&#xff0…