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

Linux之实现简易的shell

1.打印提示符并获取命令行

我们在使用shell的时候,发现我们在输入命令是,前面会有:有用户名版本当前路径等信息,这里我们可以用环境变量去获取:

  1 #include <stdio.h>2 #include <stdlib.h>3 4 const char* getUsername()5 {6     const char* name = getenv("USER");7     if(name) return name;8     else return "none";9 }10 11 const char* getHostname()12 {13     const char* hostname = getenv("HOSTNAME");14     if(hostname) return hostname;15     else return "none";16 }17 18 const char* getCwd()19 {20     const char* cwd = getenv("PWD");21     if(cwd) return cwd;22     else return "none";23 }24 25 int main()26 {27    printf("%s@%s %s\n",getUsername(),getHostname(),getCwd());                                                                                                                                                 28     return 0;29 }

写。

看到我们打印出来的是绝对路径, 而shell显示的相对路径, 但为了区分先这样不去裁剪. 

 1 #include <stdio.h>2 #include <stdlib.h>3 #include <string.h>4                                                                                                            5 #define NUM 1024                                                                                           6                                                                                                            7 const char* getUsername()                                                                                  8 {                                                                                                          9     const char* name = getenv("USER");                                                                     10     if(name) return name;                                                                                  11     else return "none";                                                                                    12 }                                                                                                          13                                                                                                            14 const char* getHostname()                                                                                  15 {                                                                                                          16     const char* hostname = getenv("HOSTNAME");                                                             17     if(hostname) return hostname;                                                                          18     else return "none";                                                                                    19 }                                                                                                          20                                                                                                            21 const char* getCwd()                                                                                       22 {                                                                                                          23     const char* cwd = getenv("PWD");                                                                       24     if(cwd) return cwd;                                                                                    25     else return "none";                                                                                    26 }                                                                                                          27                                                                                                            28 int getUsercommand(char* command, int num)                                                                 29 {                                                                                                          30     printf("[%s@%s %s]",getUsername(),getHostname(),getCwd()); 31     char* r = fgets(command,num,stdin);//最终还是会输入\n32     if(r == NULL) return 1;33                          34     command[strlen(command)-1] = '\0';//去除输入的换行35     return 0;            36 }                        37                          38 int main()               39 {                        40     char usercommand[NUM];41     //1.打印提示符并且获取命令字符串42     getUsercommand(usercommand,sizeof(usercommand));43     //2.                 44     //3.                                                                                                                                                                                                      45     printf("%s",usercommand);//回显命令,用于测试46     return 0;47 }

 由于用scanf接收的遇到空格就会停止读取, 所以用fgets, 而且用户输入完命令一定会输入回车, 所以把最后一个回车符删掉.


2.解析命令行

我们在输入命令时, 可能不仅仅只是一段,比如说:"ls -a -l "。但是命令行解释器内部在解析指令时应该传递的是"ls" "-a" "-l"这样的多个个字符串, 所以我们还需要以空格来分割字符串。

    1 #include <stdio.h>2 #include <stdlib.h>3 #include <string.h>4 5 #define DEBUG 16 #define NUM 10247 #define SIZE 648 #define SEP " "41 void commandSplit(char* in, char* out[])42 {43     int argc = 1;44     out[0] = strtok(in,SEP);
W> 45     while(out[argc++] = strtok(NULL,SEP));//报警不需要处理46 47 #ifdef DEBUG 48     for(int i = 0; out[i]; i++)49         printf("%d:%s\n",i,out[i]);50 #endif51 }52 53 int main()54 {55     char usercommand[NUM];56     char* argv[SIZE];57     //1.打印提示符并且获取命令字符串58     getUsercommand(usercommand,sizeof(usercommand));59     //2.分割字符串60     commandSplit(usercommand, argv); 61     //3.                                                                                                                                                                                                    62     return 0;63 }            


3.执行对应的命令 

创建子进程和进程替换, 为了不影响shell, 我们将大部分指令的执行让子进程去完成, 父进程只要阻塞等待子进程完成就好了。

    1 #include <stdio.h>2 #include <stdlib.h>3 #include <string.h>4 #include <unistd.h>5 #include <sys/types.h>6 #include <sys/wait.h>7 8 //#define DEBUG 19 #define NUM 102410 #define SIZE 6411 #define SEP " "12 13 const char* getUsername()14 {15     const char* name = getenv("USER");16     if(name) return name;17     else return "none";18 }19 20 const char* getHostname()21 {22     const char* hostname = getenv("HOSTNAME");23     if(hostname) return hostname;24     else return "none";25 }26 27 const char* getCwd()28 {29     const char* cwd = getenv("PWD");30     if(cwd) return cwd;31     else return "none";32 }33 34 int getUsercommand(char* command, int num)                                                                                             35 {36     printf("[%s@%s %s]",getUsername(),getHostname(),getCwd()); 37     char* r = fgets(command,num,stdin);//最终还是会输入\n38     if(r == NULL) return -1;39                                                                                                                                       40     command[strlen(command)-1] = '\0';//去除输入的换行41     return strlen(command);42 }43 44 void commandSplit(char* in, char* out[])45 {46     int argc = 1;47     out[0] = strtok(in,SEP);
W> 48     while(out[argc++] = strtok(NULL,SEP));//报警不需要处理49 50 #ifdef DEBUG 51     for(int i = 0; out[i]; i++)52         printf("%d:%s\n",i,out[i]);53 #endif54 }55 56 int execute(char* argv[])57 {58     pid_t id = fork();59     if(id < 0) return 1;60     else if(id == 0)61     {62         //child63         //exec commond64         execvp(argv[0],argv);65         exit(1);66     }67 68     else69     {70         //father71         pid_t rid = waitpid(id,NULL,0);72         if(rid < 0)73             printf("wait fail\n");74     }75 76     return 0;77 }78 79 int main()80 {81     while(1)82     {83         char usercommand[NUM];84         char* argv[SIZE];85         //1.打印提示符并且获取命令字符串86         int n = getUsercommand(usercommand,sizeof(usercommand));87         if(n <= 0) continue;//如果得到的是空串或者获取失败,不要往后执行88         //2.分割字符串89         commandSplit(usercommand, argv);90         //3.执行命令91         execute(argv);                                                                                                                 92     }93     return 0;94 }

 由于shell要一直运行, 所以要循环执行, 这里程序替换用execvp函数比较合适, 因为argv数组就是我们分割出的一个个命令的子串, argv[0]就是程序名, argv就是指令集. 父进程只进行wait即可. 

此外, getUsercommand函数可以优化一下, 返回的是输入的指令的长度, 如果接收失败(返回值为-1或者返回值是0只打印了空行)就不需要往下执行了, 直接continue进行下一轮.


4.特殊处理

 有一批命令, 不能让子进程执行, 必须让父进程自己执行, 这些命令叫内建命令.

1) cd指令

可以看到cd .. 之后并没有发生什么异常, 但是pwd之后发现路径没有发生变化. 

我们为什么能在linux中进入某个目录, 就是因为我们改变了shell的工作目录. 每个进程都有自己的工作目录, 我们想让父进程的工作目录发生改变, 但是程序替换之后都是子进程在执行cd .., 改变的都是子进程的工作目录, 子进程改变完了又被回收了, 父进程完全没发生变化, 所以cd应该实现成内建命令。

   79 void cd(const char* path)80 {81     chdir(path);82 }83 84 //1->yes,0->no85 int doBuildin(char* argv[])86 {87     if(strcmp(argv[0],"cd") == 0)88     {89         char* path = NULL;
W> 90         if(argv[1] == NULL) path = ".";91         else path = argv[1];92         cd(path);93         return 1;94     }95     else if(strcmp(argv[0],"ls")==0)96     {97         return 1;98     }99     return 0;                                                                                                                         100 }101 102 int main()103 {104     while(1)105     {106         char usercommand[NUM];107         char* argv[SIZE];108         //1.打印提示符并且获取命令字符串109         int n = getUsercommand(usercommand,sizeof(usercommand));110         if(n <= 0) continue;//如果得到的是空串或者获取失败,不要往后执行111         //2.分割字符串112         commandSplit(usercommand, argv);113         //3.检查是不是内建命令,是的话直接执行114         n = doBuildin(argv);115         if(n) continue;//是内建命令不用往后执行了116         //4.执行命令117         execute(argv);118     }119     return 0;120 }

所以在执行命令前先检查是不是内建命令, 用返回值接收, 如果是就直接执行并返回1, continue不往下执行, 如果不是就返回0, 执行命令. 

 既然当前的工作目录改变了, 那么环境变量PWD也要改变: 

chdir改变当前工作目录, getcwd获取当前的工作路径, sprintf将tmp中的内容输出到cwd中, putenv将cwd导入环境变量. 


2) export命令 

 

创建一个数组env储存要导入的环境变量, 设置size指向导入到第几个环境变量.

如果argv[1]是空就直接返回, 否则就导入环境变量, 注意不能直接把argv[1]导入进去, 因为argv[1]随着指令的输入时刻在变化, 需要开辟额外的空间去存储.


3)echo指令


4)ls指令

我们执行的ls指令中不同的文件都有不同的颜色,所以对于ls我们可以在分割命令的时候加上一个“--color=auto”.


http://www.lryc.cn/news/240441.html

相关文章:

  • 如何实现在公网下使用navicat图形化工具远程连接本地内网的MariaDB数据库
  • MySQL InnoDB 引擎底层解析(三)
  • 浅析基于智能音视频技术的城市重要场馆智能监控系统设计
  • hdu-lcy算法培训班 入门第一讲 数学基础
  • 获取ip属地(ip2region本地离线包-超简单)
  • 主流的低代码平台有哪些?程序员应该如何与低代码相处?
  • 华为---OSPF网络虚连接(Virtual Link)简介及示例配置
  • Python函数式编程:让你的代码更优雅更简洁
  • 艺术作品3D虚拟云展厅能让客户远程身临其境地欣赏美
  • 负载均衡简介
  • 【高级网络程序设计】Week2-1 Sockets
  • quickapp_快应用_requestHeader
  • FPGA----ZCU106使用petalinux 2019.1的第一个app开发
  • 华为ac+fit漫游配置案例
  • Jenkins 配置节点交换内存
  • 二百零七、Flume——Flume实时采集5分钟频率的Kafka数据直接写入ODS层表的HDFS文件路径下
  • 【实验】配置用户自动获取IPv6地址的案例
  • 手撕A*算法(详解A*算法)
  • 1688API如何获取商品详情信息(关键词搜索商品列表),1688API接口开发系列
  • 〖大前端 - 基础入门三大核心之JS篇㊶〗- DOM事件传播和事件监听方法addEventListener()
  • Cartographer实现双雷达建图
  • (离散数学)主析取范式
  • Communications link failure
  • XC3320 离线式、无电感交流输入线性稳压器 可替代KP3310 固定5V输出电压
  • 导购APP、淘客查券机器人与淘客系统:全面对比与选择
  • 飞翔的鸟游戏
  • 【SpringCloud】为什么选择微服务?
  • 基于Python实现汽车销售数据可视化+预测【500010086.1】
  • 干货分享:好用的两款封面制作工具
  • 模版模式 设计模式