C语言模拟实现Liunx操作系统与用户之间的桥梁shell(代码详解)

什么是shell?

Shell(壳)是指命令行界面(CLI)或脚本语言,它为用户提供了与操作系统交互的方式。它是一个程序,从用户那里接收命令,并通过与操作系统内核交互来执行这些命令。Shell充当用户和操作系统之间的中介,允许用户执行各种任务,运行程序,操作文件和目录,并通过脚本实现任务自动化。

在Xshell上使用一下shell

在使用Shell时,您在Shell提示符中键入命令,Shell执行这些命令并返回输出。
下图中红圈中的字符串都是linux系统通过shell来执行的一些命令
在这里插入图片描述

模拟实现shell

思路

主要的程序逻辑就是在main()函数中。通过一个无限循环,不断等待用户输入命令并执行。

  • getUserCommand()获取用户输入的命令字符串。
  • commandSplit()将命令字符串按照分隔符分割成参数列表。
  • doBuildin()检查是否为内建命令,如果是,则执行相应的操作。
    如果不是内建命令,则调用execute()函数执行命令。
  • execute()函数使用fork()创建子进程,在子进程中调用execvp()函数执行命令,父进程等待子进程执行完毕。

这样,代码就完成了一个简单的Shell程序,能够处理用户输入的命令,并执行相应的操作。

代码实现

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

#define NUM 1024
#define SIZE 64
#define SEP " "


char usercommand[NUM];
char cwd[1024];
char enval[100][100];//测试
int count_enval=0;
int lastcode;


const char *getUsername()
{
        const char *name = getenv("USER");
            if(name) return name;
                else return "none";

}
const char *getHostname()
{
        const char *hostname = getenv("HOSTNAME");
            if(hostname) return hostname;
                else return "none";

}
const char *getCwd()
{
        const char *cwd = getenv("PWD");
           if(cwd) return cwd;
                else return "none";

}

//打印命令行提示符,获取用户命令字符串
int getUserCommand(char *command, int num)
{
    printf("[%s@%s %s]# ", getUsername(), getHostname(), getCwd());
    char *r = fgets(command, num, stdin); // 最终你还是会输入\n
    if(r == NULL) return -1;
    // "abcd\n" "\n"
    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));

    //for (int i=0;out[i];i++)
    //{
    //    printf("%d:%s\n",i,out[i]);
    //}
}


int execute(char *argv[])
{

         pid_t id=fork();

         if(id<0)
         {
             return -1;
         }
         if(id==0)
         {
             execvp(argv[0],argv);
             exit(1);
         }
         else
         {
             int status=0;
             pid_t rid=waitpid(-1,&status,0);
             if(rid>0)
             {
                 lastcode=WEXITSTATUS(status);
             }
         }
         return 0;
}

void cd(const char* path )
{
    chdir(path);
    char tmp[1024];
    getcwd(tmp,sizeof(tmp));
    sprintf(cwd,"PWD=%s",tmp);
    putenv(cwd);
    
}


//内键情况
int doBuildin(char *argv[])
{
   if(strcmp(argv[0],"cd")==0)
   {
       char * path=NULL;
       if(argv[1]==NULL)
       {
           path=".";
       }
       else  path=argv[1];
       cd(path);
       return 1;
   }
   else if(strcmp(argv[0],"exprot")==0)
   {
       if(argv==NULL)return 1;
       strcpy(enval[count_enval],argv[1]);
       putenv(enval[count_enval]);
       count_enval++;
       return 1;
   }
   else if(strcmp(argv[0],"echo")==0)
   {
       char *val =argv[1]+1;
       if(strcmp(val,"?")==0)
       {
           printf("%d\n",lastcode);
           lastcode=0;
       }
       else
       {
           printf("%s\n",getenv(val));
       }
       return 1;
   }
   return 0;
}



int main()
{
    while(1)
    {
		 char *argv[SIZE];
         //1.打印命令行提示符,获取用户命令字符串
         int n=getUserCommand(usercommand,sizeof(usercommand));
         if(n<0)continue;
         //2.分割字符串
         commandSplit(usercommand,argv);
        //3. 内键情况(cd,exprot...)
         n=doBuildin(argv);
         if(n) continue;
         //4.执行对应的命令
         execute(argv);
    }
    return 0;
}

运行结果:

在这里插入图片描述
在这里插入图片描述


代码讲解

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
这些头文件包含了在程序中使用的各种函数和类型的声明。

定义常量和全局变量:

#define NUM 1024
#define SIZE 64
#define SEP " "
char usercommand[NUM];
char cwd[1024];
char enval[100][100];//测试
int count_enval=0;
int lastcode;

stdio.h、stdlib.h、string.h、unistd.h、sys/types.h、sys/wait.h:包含了一些标准库函数和系统调用的声明。
NUM和SIZE:定义了一些常量,用于数组的大小。
SEP:定义了命令参数的分隔符。
usercommand[NUM]:存储用户输入的命令字符串。
cwd[1024]:存储当前工作目录的路径。
enval[100][100]:存储环境变量的字符串数组。
count_enval:环境变量计数器。
lastcode:上一个命令的退出状态码。


  • getUsername(), getHostname(), getCwd(): 这些函数用于获取当前用户名、主机名和工作目录。
  • getUserCommand(char *command, int num): 这个函数用于获取用户输入的命令,并返回命令的长度。
  • commandSplit(char *in, char *out[]): 这个函数将输入的命令字符串按照分隔符分割成一个个参数,存储在out数组中。
  • execute(char *argv[]): 这个函数使用fork()创建子进程,并在子进程中执行命令。
  • cd(const char *path): 这个函数用于实现cd命令,改变当前工作目录。
  • doBuildin(char *argv[]): 这个函数用于执行内建命令(如cd、export、echo)。

下面是对每一个函数的详细解释:

getUsername()
const char *getUsername()
{
    const char *name = getenv("USER");
    if (name)
        return name;
    else
        return "none";
}

该函数用于获取当前用户名。它通过调用getenv()函数获取环境变量"USER"的值,并返回该值作为用户名。如果环境变量不存在,则返回字符串"none"。


getHostname()
const char *getHostname()
{
    const char *hostname = getenv("HOSTNAME");
    if (hostname)
        return hostname;
    else
        return "none";
}

该函数用于获取当前主机名。它通过调用getenv()函数获取环境变量"HOSTNAME"的值,并返回该值作为主机名。如果环境变量不存在,则返回字符串"none"。


getCwd()
const char *getCwd()
{
    const char *cwd = getenv("PWD");
    if (cwd)
        return cwd;
    else
        return "none";
}

该函数用于获取当前工作目录。它通过调用getenv()函数获取环境变量"PWD"的值,并返回该值作为工作目录。如果环境变量不存在,则返回字符串"none"。


getUserCommand(char *command, int num)
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);
}

该函数用于获取用户输入的命令。它首先打印出类似"[username@hostname cwd]#"的提示符,然后使用fgets()函数从标准输入中读取用户输入的命令字符串。读取成功后,函数会去掉字符串末尾的换行符,并返回命令的长度。如果读取失败,函数返回-1。


commandSplit(char *in, char *out[])
void commandSplit(char *in, char *out[])
{
    int argc = 0;
    out[argc++] = strtok(in, SEP);
    while (out[argc++] = strtok(NULL, SEP));
}

该函数将输入的命令字符串按照分隔符进行分割,并将分割得到的参数存储在out数组中。分割过程使用strtok()函数,第一次调用时传入原始字符串和分隔符,后续调用传入NULL和分隔符。分割完成后,out数组中存储了每个参数的地址。函数没有返回值,通过参数传递分割得到的参数列表。


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

该函数用于执行命令。它首先调用fork()函数创建一个子进程,然后在子进程中调用execvp()函数执行指定的命令。如果fork()失败,函数返回-1。如果是子进程,调用execvp()执行命令,并在执行完毕后调用exit(1)退出子进程。如果是父进程,使用waitpid()函数等待子进程的结束,并获取子进程的退出状态码。最后,将子进程的退出码存储在lastcode变量中。


cd(const char* path )
void cd(const char* path )
{
    chdir(path);
    char tmp[1024];
    getcwd(tmp,sizeof(tmp));
    sprintf(cwd,"PWD=%s",tmp);
    putenv(cwd);
}

调用chdir()函数改变当前工作目录为传入的路径。
使用getcwd()函数获取当前工作目录的路径,并存储在临时数组tmp中。
使用sprintf()函数将路径格式化为"PWD=路径"的形式,并存储在cwd字符串中。
使用putenv()函数将cwd设置为环境变量。


doBuildin(char *argv[])
//内键情况
int doBuildin(char *argv[])
{
   if(strcmp(argv[0],"cd")==0)
   {
       char * path=NULL;
       if(argv[1]==NULL)
       {
           path=".";
       }
       else  path=argv[1];
       cd(path);
       return 1;
   }
   else if(strcmp(argv[0],"exprot")==0)
   {
       if(argv==NULL)return 1;
       strcpy(enval[count_enval],argv[1]);
       putenv(enval[count_enval]);
       count_enval++;
       return 1;
   }
   else if(strcmp(argv[0],"echo")==0)
   {
       char *val =argv[1]+1;
       if(strcmp(val,"?")==0)
       {
           printf("%d\n",lastcode);
           lastcode=0;
       }
       else
       {
           printf("%s\n",getenv(val));
       }
       return 1;
   }
   return 0;
}

根据传入的参数数组argv[]判断是否是内键命令。
如果是cd命令,判断是否提供了路径参数,如果没有,则将路径设置为当前目录;否则,使用cd()函数切换到指定路径。
如果是export命令,判断是否提供了参数,如果没有,则返回;否则,将参数复制到enval数组中,并使用putenv()函数将其设置为环境变量,同时递增count_enval计数器。
如果是echo命令,从参数中提取变量名,并判断变量名是否为"?",如果是,则打印上一个命令的退出状态码;否则,使用getenv()函数获取指定变量的值,并打印出来。
如果是以上内键命令之一,返回1;否则,返回0。



int main()
{
    while(1)
    {
		 char *argv[SIZE];
         //1.打印命令行提示符,获取用户命令字符串
         int n=getUserCommand(usercommand,sizeof(usercommand));
         if(n<0)continue;
         //2.分割字符串
         commandSplit(usercommand,argv);
        //3. 内键情况(cd,exprot...)
         n=doBuildin(argv);
         if(n) continue;
         //4.执行对应的命令
         execute(argv);
    }
    return 0;
}

main()函数:
进入一个无限循环,用于不断接收用户的命令并执行。
定义参数数组argv[SIZE]。
调用getUserCommand()函数获取用户命令字符串。
如果获取失败,则继续下一次循环。
调用commandSplit()函数将命令字符串分割为参数数组argv[]。
调用doBuildin()函数判断是否是内建命令并执行。
如果是内键命令,则继续下一次循环。
调用execute()函数执行命令。
返回0,结束程序运行。

总结

代码实现了一个简单的交互式shell,能够解析用户输入的命令并执行相应的操作。内键命令包括cd切换目录,export设置环境变量,echo打印变量值。其他命令会通过fork()创建子进程并调用execvp()执行外部命令。

(本章完)

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

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

相关文章

px、em、rem、百分比的区别

文章目录 1. 单位类型与相对基准2. 相对长度1.em2.rem3.%百分比4.vw 和 vh5.vmin 和 vmax6.vm7.ch8. ex 3. 总结 1. 单位类型与相对基准 单位名称 单位类型&#xff08;相对/绝对&#xff09; 相对基准 px 相对 屏幕像素缩放后的尺寸 百分比 相对 font-size相对于继承&#xf…

一步一步教你如何在Windows 10上使用Java,包括下载、安装和配置等

Java开发工具包(JDK)是用于Java编程的软件,与Java虚拟机(JVM)和Java运行时环境(JRE)一起使用。JDK包括编译器和类库,允许开发人员创建可由JVM和JRE执行的Java程序。 在本教程中,你将学习在Windows上安装Java开发工具包。 检查是否安装了Java 在安装Java开发工具包之…

Vatee万腾外汇市场新力量:vatee科技决策力

在当今数字化时代&#xff0c;Vatee万腾崭露头角&#xff0c;以其强大的科技决策力进军外汇市场&#xff0c;成为该领域的新力量。这一新动向将不仅塑造外汇市场的未来&#xff0c;也展现Vatee科技决策力在金融领域的引领作用。 Vatee万腾带着先进的科技决策力进入外汇市场&…

Modbus转Profinet网关在金银精炼控制系统中应用案例

金银精炼控制系统中采用Modbus转Profinet网关&#xff08;XD-MDPN100&#xff09;连接1200plc与PID控制阀门进行通讯&#xff0c;通过控制PID阀门的大小来实现温度的恒温控制。这一系统的好处在于它能够提高金银精炼过程的效率和精确度。PID控制阀门可以根据温度的变化实时调整…

设计模式(二)-创建者模式(3)-抽象工厂模式

一、为什么需要抽象工厂模式&#xff1f; 在工厂模式中&#xff0c;我们需要定义多个继承于共同工厂抽象基类的工厂子类&#xff0c;这些子类负责创建一个对应的对象。工厂模式存在一个缺点就是&#xff1a;每次扩展新的工厂子类&#xff0c;就会增加系统的复杂度。 如果我们…

【shell】 1、bash语法超详细介绍

文章目录 修改前缀路径dirname set常用函数参数变量local 返回值正则打印第 n 行获取行号核对数据库各表数量jq查询检查日志 sshpassexpect数组xargs bash manual 修改前缀 参考 export PS1"bash> "路径 dirname strip last component from file name dir$(…

AWS云服务器EC2实例实现ByConity快速部署

1. 前言 亚马逊是全球最大的在线零售商和云计算服务提供商。AWS云服务器在全球范围内都备受推崇&#xff0c;被众多业内人士誉为“云计算服务的行业标准”。在国内&#xff0c;亚马逊AWS也以其卓越的性能和服务满足了众多用户的需求&#xff0c;拥有着较高的市场份额和竞争力。…

小小发票拦住出海“巨头”,合合信息智能文档处理技术助力企业重塑财务管理流程

作为连接企业采购、生产、运营、销售等经营行为的枢纽&#xff0c;财务系统的数字化、智能化升级近年来变得愈发重要。发票的录入、存储关乎企业应收账款管理和税务规范&#xff0c;通过技术手段提升发票管理的精准度与效率&#xff0c;也成为了财务数字化的关键环节。 近日&a…

MTK Pump Express 快速充电原理分析

1 MTK PE 1.1 原理 在讲正文之前&#xff0c;我们先看一个例子。 对于一块电池&#xff0c;我们假设它的容量是6000mAh&#xff0c;并且标称电压是3.7V&#xff0c;换算成Wh(瓦时)为单位的值是22.3Wh(6000mAh*3.7V)&#xff1b;普通的充电器输出电压电流是5V2A(10W)&#xff0c…

ModStartCMS v7.6.0 CMS备份恢复优化,主题开发文档更新

ModStart 是一个基于 Laravel 模块化极速开发框架。模块市场拥有丰富的功能应用&#xff0c;支持后台一键快速安装&#xff0c;让开发者能快的实现业务功能开发。 系统完全开源&#xff0c;基于 Apache 2.0 开源协议&#xff0c;免费且不限制商业使用。 功能特性 丰富的模块市…

『亚马逊云科技产品测评』活动征文|构建生态农场家禽系统

『亚马逊云科技产品测评』活动征文&#xff5c;构建生态农场家禽系统 授权声明&#xff1a;本篇文章授权活动官方亚马逊云科技文章转发、改写权&#xff0c;包括不限于在 Developer Centre, 知乎&#xff0c;自媒体平台&#xff0c;第三方开发者媒体等亚马逊云科技官方渠道 前…

.NET 6 在已知拓扑路径的情况下使用 Dijkstra,A*算法搜索最短路径

📢欢迎点赞 :👍 收藏 ⭐留言 📝 如有错误敬请指正,赐人玫瑰,手留余香!📢本文作者:由webmote 原创📢作者格言:新的征程,我们面对的不仅仅是技术还有人心,人心不可测,海水不可量,唯有技术,才是深沉黑夜中的一座闪烁的灯塔 !背景介绍 突然闯到路径搜索算法里…

智能货柜:无人零售行业的新宠

智能货柜&#xff1a;无人零售行业的新宠 与早期的传统自动售货机相比&#xff0c;智能货柜的投放场景正呈现多样化的趋势。除了人流密集的公共场所&#xff0c;一些办公室、健身房等室内场景也成为商家争相争夺的热门位置。 其次&#xff0c;在无人零售行业中&#xff0c;许多…

2023-简单点-树莓派picamera2介绍和要点

picamera2 requirements简介preview windows预览窗口GtGL preview【有 x windwows情况下 硬件加速】Qt preview【软件加速】DRM/KMS preview【没有 x windwows情况下】NULL previewpreview的一些其他特征 配置camera配置的细节 捕捉图像capture images切换模式 requirements简…

【MySQL】细谈SQL高级查询

文章目录 一、前言 & 准备工作二、简易高级查询⚙ 准备工作1、查询每一门课程及其先修课程2、查询和“刘涛”在一个班级的学生的信息3、查询选修了‘计算机基础’课的学生的学号、姓名③ 连接方式④ 子查询嵌套方式 4、查询没有选修课程的学生的信息① 子查询in方式② 子查…

typora整理markdown笔记

效果 符号 快捷键 斜体 * * ctrlB(代表同时按) 加粗 ** ** ctrlI 竖线 > 超链接 清除样式 ctrl\ 图片 ![图片描述][图片绝对路径/相对路径] 如何在Typora中插入图像&#xff1f; ➊ 使用Markdown语法 &#xff08;不推荐&#xff0c;太慢&#xff09; ➋ 直接拷贝图…

nvm 安装后出现的各种问题解决方法

1、nvm安装后无法安装node版本 首先需要确定删除了电脑上所有的node版本&#xff0c;如果不会卸载那么请移步到 查看 &#xff0c;我们是要通过nvm来下载node环境&#xff0c;所以之前下载的node有冲突&#xff0c;所以都要清除。 2、下载后的nvm环境&#xff0c;无法使用node、…

Linux入门攻坚——6、磁盘管理——分区及文件系统管理

磁盘管理主要涉及分区的管理&#xff0c;以及分区后的文件系统管理。 磁盘的使用大体要分两步&#xff1a; 文件系统也是一个软件&#xff0c;根是自引用的。 文件系统的全局结构&#xff1a;物理格式&#xff1a; 一个磁盘刚被生产出来的时候&#xff0c;它里边没有划分扇区…

[OpenAirInterface-01]什么是OAI?OAI在github中源代码的存放结构

文章目录 前言&#xff1a;什么是软件无线电SDR第1章 什么是OAI&#xff08;OpenAirInterface&#xff09;1.1 来自官网的概述1.2 来自github的概述1.3 来自我的解读 第2章 什么是OSA2.1 OSA概述&#xff1a;2.2 OSA联盟职责2.3 OpenAirInterface github成员的角色 第3章 常见的…

服务号转为订阅号可以吗

服务号和订阅号有什么区别&#xff1f;服务号转为订阅号有哪些作用&#xff1f;很多小伙伴想把服务号改为订阅号&#xff0c;但是不知道改了之后具体有什么作用&#xff0c;今天跟大家具体讲解一下。首先我们知道服务号一个月只能发四次文章&#xff0c;但是订阅号每天都可以发…