【Linux 进程】 自定义shell

目录

关于shell

1.打印提示符&&获取用户命令字符​编辑

2.分割字符串

3.检查是否为内建命令

cd命令

export命令

echo命令

1.输出最后一个执行的命令的状态退出码(返回码)

2.输出指定环境变量

4.执行外部命令


关于shell

Shell 是计算机操作系统的一种命令行解释器,它允许用户与操作系统进行交互,执行各种操作和任务。Shell 接受用户输入的命令,并将其解释成操作系统能够理解的形式,然后将这些命令发送给操作系统内核执行。

Shell 的作用包括但不限于:

  1. 命令执行: 用户可以使用 Shell 来执行各种命令,包括系统命令、应用程序命令、脚本等,以完成各种任务和操作。

  2. 文件操作: 用户可以使用 Shell 进行文件和目录的创建、复制、移动、删除等操作,以及文件内容的查看、编辑等操作。

  3. 系统管理: 用户可以使用 Shell 进行系统资源的管理,包括进程管理、用户管理、权限管理等。

  4. 环境配置: 用户可以使用 Shell 来配置系统环境,包括设置环境变量、执行初始化脚本等。

  5. 脚本编程: 用户可以使用 Shell 编写脚本,实现自动化任务和流程控制,提高工作效率。

总的来说,Shell 提供了一个灵活而强大的界面,使用户能够通过简单的命令和脚本来与操作系统进行交互和控制,从而完成各种任务和操作。

1.打印提示符&&获取用户命令字符

首先,要自定义shell就必须要接收命令,可以看到我们在系统的shell中提示符有三部分:1.用户名 2.主机名 3.所在目录。所以我们自己的shell就必须先把这三个提示符打印出来,然后是接收用户输入的命令字符。而考虑到接收命令是一直持续的,所以我们用死循环来控制。

那如何在程序中获取用户名、主机名以及所在目录呢?这就需要环境变量了。

环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数,是操作系统为了满足不同的应用场景预先在系统内预先设置的一大批全局变量。

在操作系统里,我们可以输入env、printenv、echo等命令来打印出环境变量:

可以看到系统输出了很多我们不认识的东西,但我们只需要找到需要的用户名(USER)、主机名(HOSTNAME)以及当前目录(PWD)。

然后我们就可以编写程序了。

从程序中获取环境变量,我们需要用到一个函数

char *getenv(const char *name);

这个函数将返回我们传给的 name,然后它将环境列表中搜索名为name的环境变量,如果name不是环境列表的一部分,它将返回 NULL,否则返回具有所求环境变量值的C字符串。

 const char *getUsername()
  {
      const char *name = getenv("USER");
      if(name) return name;
      else return NULL;
  }
  const char *getHostname()
  {
      const char* hostname = getenv("HOSTNAME");
      if(hostname) return hostname;
      else return NULL;
  }
  const char *getCwd()
  {
      const char* cwd = getenv("PWD");
      if(cwd) return cwd;
      else return NULL;
  }     

我们编写了上面三个函数,分别可以返回用户名、主机名和当前目录路径。

下面我们创建一个函数getUserCommand,使其能够打印出提示符信息并获取用户命令字符,在此之前,我们需要在主函数中创建一个字符数组username以便存放用户命令字符和后续操作。

#define NUM 1024
char usercommand[NUM];

那么如何从进程中接收用户输入的字符呢?有的人可能会想到用scanf,但是我们要直到,scanf碰到空格会自动停止,所以我们需要用到fgets,以便从输入流中截取字符。

fgets函数返回值为我们传过去的s字符指针,如果获取字符失败,则返回NULL,参数中第一个元素是我们存储字符的字符串,第二个是要截取的字符串的长度,第三个是标准流,我们需要的是标准输入流stdin,这样getUserCommand函数的需要的参数也就确定了,一个是存放字符的数组usercommand,另一个是数组的长度sizeof(usercommand)

可这样就结束了吗?我们打印出获取的字符串会发现,总是会多一个换行符,这是因为每次输入的回车也被接收了,只需要将字符数组中的最后一个元素换成'\0'就好,然后可以返回字符数组的长度,以便判断是否成功获取到命令字符。

int getUserCommand(char *command,int num)
  {
      printf("[%s@%s %s] $",getUsername(),getHostname(),getCwd());
      char * r = fgets(command,num,stdin);
      if(r == NULL) return -1;
      command[strlen(command)-1] = '\0';
      return strlen(command);
  }
//如果函数返回 -1 或 0 ,则代表获取命令字符失败

2.分割字符串

在C++里,我们通常可以用substr来进行字符串的分割,但是在这我们用的是C,所以只能用strtok来对字符串进行分割。

str是我们想要分割的字符串,delim是我们想要在哪截断的符号,如果没有找到delim或者扫描的字符串到达末尾空字符时,strtok将返回NULL,否则返回指向以delim开头的字符串(不包括delim)。

需要注意的是,strtok会在内部保留一个静态指针,用于记录当前分割的位置,通过将第一个参数设置为 NULL,来告诉strtok函数接着上次的位置进行分割。

在此之前,我们需要在主函数中创建一个存储字符串指针的数组,以便后续操作,定义SIZE代表这个数组最多能存储多少个命令。

#define SIZE 64
char *argv[SIZE];  

接着我们确定函数的参数,第一个是之前接收的字符数组command,第二个是存储命令的argv数组,这里in代表command,out代表argv。

void commandSplit(char *in,char* out[])
  {
      int argc = 0;
      out[argc++] = strtok(in,SEP);
      while(out[argc++] = strtok(NULL,SEP));
  #ifdef DEBUG
      //用于测试 commandSplit是否生效,如果DEBUG被定义,则运行代码,未定义不运行
      for(int i = 0;out[i];i++)
      {
          printf("%d:%s\n",i,out[i]);
      }
 #endif
 }
通过定义DEBUG来测试commandSplit函数能否成功分割字符串。

3.检查是否为内建命令

内建命令是指直接内置在操作系统的命令行解释器(如Bash、CMD)中的命令,而不是外部可执行文件。这些命令不需要从磁盘加载,而是作为解释器的一部分而存在。它们通常提供了一些基本的操作,比如文件系统操作、环境控制等。在linux系统中,常见的内建命令有:cd、echo、pwd、exit、export等。

没有内建命令,我们的shell可以说就是个空格,接下来我们将在我们的shell中中加入内建命令,来让其可以做到一些基本的功能。

int doBuildin(char *argv[]);
//返回值
cd命令

因为正常用户输入的命令都是第一个空格前的字符串代表的是命令,后面的代表命令选项,例如ls -a -l,所以我们需要先判断命令的字符串是否等于"cd",后面的命令也同样如此,如果相等,开始下一步。

因为cd命令后面跟的都是路径,所以我们判断argv中的第二个字符串是否为空,如果为空的话,就让path等于家目录,如果不为空,就让path等于它,然后进行我们的cd命令。

if(strcmp(argv[0],"cd") == 0)
    {
       char *path = NULL;                                                                                                                                                      
       if(argv[1] == NULL) path = homepath();
       else path = argv[1];
       cd(path);
       return 1;
     }

进入自己的cd函数后,我们需要通过一个函数chdir来改变当前的目录地址

然后我们需要记录下已经改变后的目录地址,然后将当前目录地址设置为环境变量

然后我们需要记录下已经改变后的目录地址,然后将当前目录地址设置为环境变量

如此我们就实现了第一个内建命令cd,它可以在改变目录地址的同时,修改环境变量中的PWD变量,使其跟当前目录同步。

export命令

export 是一个命令行命令,用于设置环境变量。在Unix/Linux系统中,环境变量是在操作系统中存储的一组键值对,它们可以影响系统和用户进程的行为。通过使用 export 命令,你可以将一个变量设置为环境变量,使其在当前会话中对所有后续运行的程序可见。

export命令的实现相对简单,只需判断第二个字符是否为空,如果为空的话,直接返回,不为空就设置一个全局变量enval,先将argv[1]拷贝给enval,再用putenv命令将enval中的内容添加到环境变量中。

else if(strcmp(argv[0],"export") == 0)
    {
       if(argv[1] == NULL) return 1;
       strcpy(enval,argv[1]);
       putenv(enval);
       return 1;
   }
echo命令

echo 是一个命令行命令,用于在终端输出文本或变量的值。它是一个非常简单但功能强大的命令,常用于脚本编程、调试以及与其他命令的组合使用。

首先要判断第二个字符串的第一个字符是否为$,并且第二个字符串的长度要大于1,如果满足继续向下走,不满足直接输出argv[1]的内容并返回。

向下走:将指针argv[1]+1,使其跳过第一个字符$,然后将剩下的指针赋值给val,然后对val进行判断,如果val的值等于"?",执行下面第一个,否则执行第二个。

1.输出最后一个执行的命令的状态退出码(返回码)
2.输出指定环境变量
else 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
            {
                const char *enval = getenv(val);
                if(enval) printf("%s\n",enval);
                else printf("\n");
            }
            return 1;
        }

但是这个函数不足的点在于,无论用户输入的内建命令成功与否,输入echo $?的结果都是0,除非上一个执行的命令是外部命令。

4.执行外部命令

上面的代码处理了当用户输入的命令是内建命令的情况,那么当用户输入外部命令时应该怎么办呢?

首先,我们创建一个execute函数用来封装处理外部程序,参数为命令字符数组argv,

然后我们可以用exec系列的函数来实现在程序内处理外部命令,那么这几个exec函数应该选哪个呢?

我们选择execvp函数,因为我们可以直接传递命令名称,让execvp去系统里找可执行文件,所以选择'p',而我们的命令名臣存储在了argv这一个数组中,所以选择'v'。

在execute函数中,我们先用fork创建子进程,让子进程去执行命令,然后父进程用waitpid来等待子进程的退出,并获取其退出码,无论子进程是否正常退出,则其状态退出码将存储在status中,然后用WEXITSTATUS来获取子进程的退出状态码。

int execute(char *argv[])
{
        pid_t id = fork();
        if(id < 0) return -1;
        else if(id == 0)  //child
        {
            //exec command
            execvp(argv[0],argv);
            exit(1);
        }
        else    //father
        {
            int status;
            pid_t rid = waitpid(id,&status,0);
            if(rid>0)
            {
                lastcode = WEXITSTATUS(status);
            }                 
        }
}

总代码

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<sys/wait.h>
#define NUM 1024
#define SIZE 64
#define SEP " "
//#define DEBUG 1
//
char cwd[1024];

char enval[1024];  // for test

int lastcode = 0; 


char *homepath()
{
    char *home = getenv("HOME");
    if(home)  return home;
    else
        return (char*)".";
}
const char *getUsername()
{
    const char *name = getenv("USER");
    if(name) return name;
    else return NULL;
}
const char *getHostname()
{
    const char* hostname = getenv("HOSTNAME");
    if(hostname) return hostname;
    else return NULL;
}
const char *getCwd()
{
    const char* cwd = getenv("PWD");
    if(cwd) return cwd;
    else return NULL;
}
int getUserCommand(char *command,int num)
{
    printf("[%s@%s %s] $",getUsername(),getHostname(),getCwd());
    char * r = fgets(command,num,stdin);
    if(r == NULL) return -1;
    command[strlen(command)-1] = '\0';
    return strlen(command);
}
void commandSplit(char *in,char* out[])
{
    int argc = 0;
    out[argc++] = strtok(in,SEP);
    while(out[argc++] = strtok(NULL,SEP));
#ifdef DEBUG
    //用于测试 commandSplit是否生效,如果DEBUG被定义,则运行代码,未定义不运行
    for(int i = 0;out[i];i++)
    {
        printf("%d:%s\n",i,out[i]);
    }
#endif
}

int execute(char *argv[])
{
        pid_t id = fork();
        if(id < 0) return -1;
        else if(id == 0)  //child
        {
            //exec command
            execvp(argv[0],argv);
            exit(1);
        }
        else    //father
        {
            int status;
            pid_t rid = waitpid(id,&status,0);
            if(rid>0)
            {
                lastcode = WEXITSTATUS(status);
            }                 
        }
}
void cd(const char *path)
{
    chdir(path);
    char tmp[1024];
    getcwd(tmp,sizeof(tmp));
    sprintf(cwd,"PWD=%s",tmp);
    putenv(cwd);
}

//什么叫做内建命令:内建命令就是bash自己执行的,类似与自己内部的一个函数
//1->yes 0->no  -1->error
int doBuildin(char *argv[])
{
    if(strcmp(argv[0],"cd") == 0)
    {
        char *path = NULL;
        if(argv[1] == NULL) path = homepath();
        else path = argv[1];
        cd(path);
        return 1;
    }
    else if(strcmp(argv[0],"export") == 0)
    {
        if(argv[1] == NULL) return 1;
        strcpy(enval,argv[1]);
        putenv(enval);
        return 1;
    }
    else 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
            {
                const char *enval = getenv(val);
                if(enval) printf("%s\n",enval);
                else printf("\n");
            }
            return 1;
        }
        else
        {
            printf("%s\n",argv[1]);
            return 1;
        }
    }
    else if(0){}
    return 0;
}
int main()
{
    while(1)
    {
        char usercommand[NUM];
        char *argv[SIZE];
        //1.打印提示符&&获取用户命令字符串获取成功
        int n = getUserCommand(usercommand,sizeof(usercommand));
        if(n <= 0) continue;  //跳出循环
        //2.分割字符串
        commandSplit(usercommand,argv);
        //3.check build-in command
        n = doBuildin(argv);
        if(n) continue;
        //4.执行对应的命令
        execute(argv);
    }
}

这样我们就完成了一个简单的自定义shell程序了,还有很多内容可以进行开发。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C语言数组介绍

文章目录 一、数组的概念二、一维数组1.一维数组的创建2.一维数组的初始化3.数组的类型4.一维数组的使用5.一维数组在内存中的存储6.sizeof计算数组元素个数 三、二维数组1.二维数组的概念2.二维数组的创建3.二维数组的初始化4.二维数组的使用5.二维数组的输入和输出6.二维数组…

【教学类-50-09】20240505“数一数”图片样式09:数一数(几何图案——透明颜色重叠+纯黑边框+黑框粗细)

背景需求&#xff1a; 【教学类-50-03】20240408“数一数”图片样式03&#xff1a;透明图形与边框不相交&#xff0c;透明图形和其他透明图形重叠-CSDN博客文章浏览阅读867次&#xff0c;点赞28次&#xff0c;收藏25次。【教学类-50-03】20240408“数一数”图片样式03&#xf…

银行ETL-监管报送

1104报表 1104报表主要包括&#xff1a;资产负债&#xff0c;表外业务、流动性风险、贷款质量、投向行业和地区、重点客户等。 1104报表分类 普通报表、机构特色类报表。 反洗钱 大额交易、可疑交易。标签分类&#xff1a;疑似犯罪、疑似毒品、疑似传销。 反洗钱—接口报…

tomcat+maven+java+mysql图书管理系统2-完善项目结构,添加相关依赖

1.创建java目录 接着选择java&#xff0c;回车&#xff0c;则创建成功&#xff0c;成功后在左侧栏能看见 2.修改pom.xml文件,(添加依赖) <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi…

pandas读取文件导致jupyter内核崩溃如何解决

读取execl文件出现以下问题: str_name "D:\\cao_use\\2017_2021(new).xlsx" train_df pd.read_excel(str_name, usecols[0])崩溃的指示图如下所示: bug原因:读入的文件太大&#xff0c;所需时间过长&#xff0c;在读取的过程中&#xff0c;使用中断按钮暂停会直…

mac监听 linux服务器可视化(Grafana+Promethus+Node_exporter)

Grafana和promethus(普罗米修斯)的安装和使用 监控系统的Prometheus类似于一个注册中心&#xff0c;我们可以只需要配置一个Prometheus,而在其他服务器&#xff0c;只需要安装node_exporter,它们的数据流转就是通过exporter采集数据信息&#xff0c;然后告诉prometheus它的位置…

AI视频教程下载:用 ChatGPT 和 WordPress 创建赚钱网站

您是否有兴趣开设网站&#xff08;博客&#xff09;&#xff0c;但不知道从何入手&#xff1f; 或者您已经开设了网站&#xff08;博客&#xff09;&#xff0c;但难以从中获利&#xff1f; 别找啦&#xff01; 本课程旨在教授您使用 WordPress 创建成功盈利网站&#xff08;博…

OpenCV(五) —— 人脸识别模型训练与 Windows 下的人脸识别

本文主要内容&#xff1a; 如何训练 OpenCV 的人脸识别模型如何在 Windows 下利用 OpenCV 进行人脸识别 1、概述 人脸识别需要人脸模型&#xff08;特征集合&#xff09;的支持&#xff0c;人脸定位的速度与准确度取决于模型。 OpenCV 提供了已经训练好的模型&#xff0c;无…

【莫比乌斯变换-04】求解莫比乌斯变换系数

求解莫比乌斯变换系数 文章目录 一、说明二、如何确定双线性变换系数2.1 变换基本形式2.2 通过三点确定 三、一般情况的变换3.1 最简单的情况&#xff1a;无穷大3.2 处理无穷大 四、Python 代码 一、说明 上一篇文章是对双线性变换的视觉介绍&#xff0c;又名莫比乌斯变换或分…

ThreeJS:补间动画与Tween.JS

补间动画 补间动画指的是做FLASH动画时&#xff0c;在两个关键帧中间需要做“补间动画”&#xff0c;才能实现图画的运动&#xff1b;插入补间动画后两个关键帧之间的插补帧是由计算机自动运算而得到的。 ——摘自《百度百科&#xff1a;补间动画_百度百科》 Tween.js Tween.js…

Python-VBA函数之旅-oct函数

目录 一、oct函数的常见应用场景 二、oct函数使用注意事项 三、如何用好oct函数&#xff1f; 1、oct函数&#xff1a; 1-1、Python&#xff1a; 1-2、VBA&#xff1a; 2、推荐阅读&#xff1a; 个人主页&#xff1a;神奇夜光杯-CSDN博客 一、oct函数的常见应用场景 oc…

W801学习笔记十七:古诗学习应用——上

硬件驱动以及软件架构大体上已经完成&#xff0c;尚存一些遗漏之处&#xff0c;后续会寻找合适的时机进行补充。自此章起&#xff0c;将正式迈入软件应用阶段&#xff0c;尤其是游戏开发领域。 关于第一个应用&#xff0c;此前已有一些构想&#xff1a; 其一&#xff0c;随机…

IO流-其他流:数据流,序列化流

import java.io.DataOutputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream;public class DataOutputStream1 {public static void main(String[] args) {//创建一个数据输出流包装一个低级的字节输出流try (DataOutputStream dosnew DataOutp…

9.3.k8s的控制器资源(deployment部署控制器)

目录 一、deployment部署控制器概念 二、deployment资源的清单编写 三、小结 功能 使用场景 原理 四、deployment实现升级和回滚 1.编辑deployment资源清单&#xff08;v1版本&#xff09; 2.创建service资源用于访问 ​编辑 3.修改deploy清单中pod镜像版本为V2 4…

「C/C++ 01」scanf()与回车滞留问题

目录 〇、scanf()接收用户输入的流程 一、回车的缓冲区滞留问题是什么&#xff1f; 二、为什么&#xff1f; 三、四个解决方法&#xff1a; 1. 在前面的scanf()中加上\n 2. 在scanf("%c")中添加空格 3. 使用getchar()来吸收回车 4. 使用fflush()清空缓冲区 〇、scan…

2.spring security 简单入门

创建springboot 项目&#xff0c;引入spring security坐标 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--spring security坐标--><dependency&g…

leecode每日一练

我一开始的思路也是dp&#xff0c;但是转移方程想错了&#xff0c;这个题目转移方程应该是dp[i] max(dp[i-2]nums[i],dp[i-1]) class Solution { public:int rob(vector<int>& nums) {int len nums.size();vector<int> dp(len);int ans 0;if(len>1)dp[0]…

IoTDB 入门教程 基础篇①——时序数据库为什么选IoTDB ?

文章目录 一、前文二、性能排行第一三、完全开源四、数据文件TsFile五、乱序数据高写入六、其他七、参考 一、前文 IoTDB入门教程——导读 关注博主的同学都知道&#xff0c;博主在物联网领域深耕多年。 时序数据库&#xff0c;博主已经用过很多&#xff0c;从最早的InfluxDB&a…

《Fundamentals of Power Electronics》——脉宽调制器建模

下图给出了一个简单脉宽调制器电路的原理图。 脉宽调制器电路产生一个用于指令转换器功率管导通和关断的逻辑信号δ(t)。该逻辑信号δ(t)是周期性的&#xff0c;其频率为fs&#xff0c;占空比为d(t)。脉宽调制器的输入是一个模拟控制信号vc(t)。脉宽调制器的作用是产生一个与模…

观测与预测差值自动变化系统噪声Q的自适应UKF(AUKF_Q)MATLAB编写

简述 基于三维模型的UKF&#xff0c;设计一段时间的输入状态误差较大&#xff0c;此时通过对比预测的状态值与观测值的残差&#xff0c;在相应的情况下自适应扩大系统方差Q&#xff0c;构成自适应无迹卡尔曼滤波&#xff08;AUKF&#xff09;&#xff0c;与传统的UKF相比&…