自行编写一个简单的shell!

本文旨在编写一个简单的shell外壳程序!功能类似于shell的一些基本操作!虽然不能全部实现shell的一些功能!但是通过此文章,自己写一个简单的shell程序也是不成问题!并且通过此文章,可以让读者对linux中一些环境变量等基本概念有更深的理解!希望读完本篇文章能对读者有一定的收获!文末会附带自己编写shell的源码!


好的废话少说,正文开始!

首先我们先来看一下linux中的shell长什么样子!

这是其shell刚启动的时候的样子!其外貌就是一个中括号内部加上一系列的东西!其实当我们认真观察,不难发现,里面包括的就是“用户名“+“@”+“主机名字”+“当前工作路径!”那么发现了此规律之后我们不难实现此描述框!那么接下来我们就着手与这些描述框的实现!

linux描述框的实现!

其中要想获得我们的用户名!我们其实可以通过环境变量进行获取,那么该如何获取环境变量的值呢?这里就不得不引进一个获得环境变量的值的函数了!

getenv(“USER”)

通过查询man手册,我们可以发现getenv()函数只需要传递一个参数即可!那么此参数是什么呢?其实此参数就是我们想要获得环境变量的值的名字!所以要想获得用户名,我们可以直接使用getenv(USER),即可获得我们想要的用户名!我们可以验证一下USER对应的环境变量是否真的是我们所要的环境变量名!我们可以通过echo $USER  此命令来判断是否真的是我们想要的用户名!

不难看出,USER对应的环境变量确实是我们的用户名!


getenv(“HOSTNAME”)

既然有了用户名,那么我们的主机名如何获得呢?思路还是调用getenv(HOSTNAME)操作!获取主机名!同样的也可以通过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的实现!

下面将源码附在下面!

源码

#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<sys/wait.h>
#include<unistd.h>
#include<stdlib.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 *filename=NULL;
int redir=NOredir;
int lastcode=0;
char enval[1024];
char cwd[1024];
// char eni[1024];
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";
    }
}
 char*gethome()
{
    char *pwd=getenv("PWD");
    if(pwd)
    {
        return pwd;
    }
    else{
        return "none";
    }
}
 
int getcommand(char*command,int n)
{

    printf("[%s@%s %s]#",getUser(),getHost(),gethome());
    char*r=fgets(command,n,stdin);
    if(r==NULL) return 0 ;
    command[strlen(command)-1]='\0';
    return 1;
}

void commandSplit(char *in,char *out[])
{
   int argc=0;
   out[argc++] =strtok(in,spint);
   while(out[argc++]=strtok(NULL,spint));
#ifdef debug 
   int i=0;
   for(i=0;out[i];i++)
   {
      // printf("%d:%s\n",i,out[i]);
       printf("%s\n",out[i]);
   }
  // printf("\n");
#endif
}

//只需要将用户的命令行数组指令传递过来即可!
int execute(char* argv[])
{
    pid_t rit=fork();
    if(rit==0)
    {
        int fd=0;
        if(redir==InputRedir)
        {
            fd = open(filename, O_RDONLY); // 差错处理我们不做了
            dup2(fd, 0);
        }
        else if(redir==OutputRedir)
        {
            fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666);
            dup2(fd, 1);
        }
        else if(redir==AppendRedir)
        {
            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 status=0;
        //父进程!只需要等待子进程退出即可!
        pid_t ret=waitpid(rit,&status,0);
        if(ret==rit)
        {
           // 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 *path=NULL;
        if(argv[1]==NULL)  
        {

           path=gethome(); 
        }
        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]);
      //  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 *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;

             }
             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.txt
    char *end = usercommand + len - 1;
    char *start = usercommand;
    while(end>start)
    {
        if((*end) == '>')
        {
            if(*(end-1) == '>')
            {
                *(end-1) = '\0';
                filename = end+1;
                SkipSpace(filename);
                redir = AppendRedir;
                break;
            }
            else
            {
                *end = '\0';
                filename = end+1;
                SkipSpace(filename);
                redir = OutputRedir;
                break;
            }
        }
        else if(*end == '<')
        {
            *end = '\0';
            filename = end+1;
            SkipSpace(filename); // 如果有空格,就跳过
            redir = InputRedir;
            break;
        }
        else
        {
            end--;
        }
    }
}


int main()
{
    while(1)
    {

       char userCommand[NUM];
       char* argv[Size];
       //显示框架!获取用户输入的指令!
       int n= getcommand(userCommand,sizeof(userCommand));
       // if(n==0) continue;
       // printf("%s")
       //将用户的命令进行切割!
      
      checkRedir(userCommand,strlen(userCommand));
       commandSplit(userCommand,argv);
       //判断命令是否为内建命令1!
       int k=dobuildin(argv);
       if(k) continue;
       //创建子进程用于进行进程替换!
       execute(argv);
          
     }
       // return 0;
}

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

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

相关文章

入门Redis学习总结

记录之前刚学习Redis 的笔记&#xff0c; 主要包括Redis的基本数据结构、Redis 发布订阅机制、Redis 事务、Redis 服务器相关及采用Spring Boot 集成Redis 实现增删改查基本功能 一&#xff1a;常用命令及数据结构 1.Redis 键(key) # 设置key和value 127.0.0.1:6379> set …

[仅供学习,禁止用于违法]编写一个程序来手动设置Windows的全局代理开或关,实现对所有网络请求拦截和数据包捕获(抓包或VPN的应用)

文章目录 介绍一、实现原理二、通过注册表设置代理2.1 开启代理2.2 关闭代理2.3 添加代理地址2.4 删除代理设置信息 三、代码实战3.1 程序控制代理操作控制3.1.1 开启全局代理3.1.2 添加代理地址3.1.3 关闭代理开关3.1.4 删除代理信息 3.2 拦截所有请求 介绍 有一天突发奇想&am…

Avaya Aura Device Services 任意文件上传漏洞复现

0x01 产品简介 Avaya Aura Device Services是美国Avaya公司的一个应用软件。提供一个管理 Avaya 端点功能。 0x02 漏洞概述 Avaya Aura Device Services 系统PhoneBackup接口处存在任意文件上传漏洞&#xff0c;攻击者可绕过验证上传任意文件获取服务器权限。 0x03 影响范围…

代码随想录算法训练营第四十天|139.单词拆分,多重背包,背包问题

139. 单词拆分 - 力扣&#xff08;LeetCode&#xff09; 给你一个字符串 s 和一个字符串列表 wordDict 作为字典。请你判断是否可以利用字典中出现的单词拼接出 s 。 注意&#xff1a;不要求字典中出现的单词全部都使用&#xff0c;并且字典中的单词可以重复使用。 示例 1&a…

【C++】输入输出流 ⑤ ( cin 输入流对象 | cin.ignore() 函数 | cin.peek() 函数 | cin.putback() 函数 )

文章目录 一、cin.ignore() 函数1、cin.ignore() 函数简介2、cin.ignore() 函数原型3、代码示例 - cin.ignore() 函数 二、cin.peek() 函数1、cin.peek() 函数简介2、代码示例 - cin.peek() 三、cin.putback() 函数1、cin.putback() 函数简介2、代码示例 - cin.putback() 一、c…

智能优化算法应用:基于粒子群算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于粒子群算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于粒子群算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.粒子群算法4.实验参数设定5.算法结果6.参考文…

IntelliJ IDEA创建一个Maven项目

在IDEA中创建Maven项目&#xff0c;前提是已经安装配置好Maven环境 。 本文主要使用的是IntelliJ IDEA 2022.2.1 (Community Edition) 1.创建一个新project:File>Project 2.修改Maven配置&#xff1a;File>Settings>搜索maven 创建好的工程如下&#xff1a; src/main…

探索 PDM:新一代的 Python 包管理工具

更多资料获取 &#x1f4da; 个人网站&#xff1a;ipengtao.com PDM&#xff08;Python Development Master&#xff09;是一款新一代的 Python 包管理工具&#xff0c;旨在提供更为现代化、可靠且灵活的解决方案。与传统的 pip 和 Poetry 相比&#xff0c;PDM 在依赖版本管理…

点云 ros PointCloud2格式与livox CustomMsg格式介绍

点云 ros PointCloud2格式与livox CustomMsg格式介绍 PointCloud2 点云格式livox CustomMsg 点云格式 PointCloud2 点云格式 PointCloud2 是ros的一种点云格式 具体官方数据 http://docs.ros.org/en/jade/api/sensor_msgs/html/msg/PointCloud2.html std_msgs/Header header…

Qt/C++音视频开发57-切换音视频轨道/切换节目流/分别切换音频视频轨道

一、前言 对各种音视频文件格式的支持&#xff0c;是一个播放器的基础功能。一般的音视频文件只有1路流&#xff0c;比如音频文件只有1路音频流&#xff0c;视频文件只有1路音频1路视频流&#xff0c;实践过程中发现&#xff0c;还有一种ts格式的文件&#xff0c;可能有多路流…

基于SSM的成绩管理系统的设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;Vue 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#xff1a;是 目录…

qt:QMessageBox的常见用法

头文件&#xff1a;#include <QMessageBox> Infomation消息对话框 初始化格式&#xff1a; QMessageBox * msgBox new QMessageBox(QMessageBox::Information, "我是标题", "我是提示文字", 按钮); 按钮可以是以下取值&#xff0c;会在按键上显示…

C++中STL的容器vector

文章目录 什么是vectorvector与普通顺序表不同的点 vector的成员函数operatoroperator[]begin与end与iteratorsize()capacityresizeemptyreservepush_backpop_backinserteraseswapclear成员变量 总结 什么是vector vector&#xff1a;是数据结构里面的顺序表&#xff0c;开辟一…

JVM类加载器ClassLoader的源码分析

1、ClassLoader与现有类加载器的关系 ClassLoader与现有类加载器的关系&#xff1a; ClassLoader是一个抽象类。如果我们给定了一个类的二进制名称&#xff0c;类加载器应尝试去定位或生成构成定义类的数据。一种典型的策略是将给定的二进制名称转换为文件名&#xff0c;然后去…

LeetCode Hot100 39.组合总数

题目&#xff1a; 给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target &#xff0c;找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 &#xff0c;并以列表形式返回。你可以按 任意顺序 返回这些组合。 candidates 中的 同一个 数字可以 无限…

[报错]记录IDEA远程开发报错:java: Cannot run program.....

报错内容 IDEA在进行远程开发的时候报错&#xff0c;内容如下&#xff1a; java: Cannot run program "/usr/lib/jvm/java-1.8.0-openjdk-amd64/bin/java" (in directory "/home/jim/.cache/JetBrains/RemoteDev-IU/_home_jim_DevCodes_Github_zfile/compile-…

空中消防员:无人机森林防火应用全面升级

森林是生态系统的重要组成部分&#xff0c;也是人类得以生存的关键。我国森林面积广大&#xff0c;存在火灾频发的困境。提升森林火灾防控能力是维护生态平衡、保护资源和保障人民生命安全的必要步骤。随着无人机技术的发展&#xff0c;其在无人机森林防火中的应用为传统巡查工…

【EI会议征稿中】第三届网络安全、人工智能与数字经济国际学术会议(CSAIDE 2024)

第三届网络安全、人工智能与数字经济国际学术会议&#xff08;CSAIDE 2024&#xff09; 2024 3rd International Conference on Cyber Security, Artificial Intelligence and Digital Economy 第二届网络安全、人工智能与数字经济国际学术会议&#xff08;CSAIDE 2023&…

SpringBoot中的YAML配置文件和日志

与其明天开始&#xff0c;不如现在行动&#xff01; 文章目录 YAML配置文件1 基本语法2 语法细节 日志1 简介2 格式3 级别4 日志保存 &#x1f48e;总结 YAML配置文件 SpringBoot集中化管理配置&#xff1a;application.properties 问题&#xff1a;配置多了以后难阅读和修i该&…

链表OJ—相交链表

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言 1、相交链表的题目&#xff1a; 方法讲解&#xff1a; 图文解析&#xff1a; 代码实现&#xff1a; 总结 前言 世上有两种耀眼的光芒&#xff0c;一种是正在升…