【Linux后端服务器开发】Shell外壳——命令行解释器

news2024/10/7 17:28:57

目录

一、Shell外壳概述

二、描述Shell外壳原理的生动例子

三、C语言模拟实现Shell外壳


一、Shell外壳概述

在狭义上 , 我们称Linux操作系统的内核为 Linux

在广义上 , Linux发行版 == Linux内核 + 外壳程序

就比如市面上现在的redhat, centos, ubuntu等等我们耳熟能详的Linux发行版,事实上这些Linux发行版都是基于Linux操作系统的内核,然后对之加装了不同的Shell外壳 ,最终做出不同种类的Linux发行版。

我们作为用户,是不能直接去操作Linux内核的,为什么呢?

  1. 直接去操作Linxu内核成本是极高的,学习成本、操作成本都不低
  2. 用户直接操作Linux内核存在风险,所以Linux内核对用户设置了权限限制

所以我们有了shell外壳程序来间接帮助我们操作Linux内核。

windows操作系统的Shell外壳是一个窗口图形界面,所以我们可以通过这个窗口图形,接收来自用户的点击、拖拽等操作,从而使用windows的各个功能。

Linux操作系统的Shell外壳的名字叫bash,所以我们也可以通过bash来传达我们的操作,即用户对bash输入指令,从而使用Linux操作系统的功能。

简单的说,Shell外壳程序是对Linux内核的一层封装 , 架起了用户和Linux沟通的桥梁。

命令行解释器

在大多数Linux发行版本中,是没有图形化界面的,那么,Shell是如何帮助用户和Linux内核进行沟通的呢?答案是命令行解释器,也就是小黑框。命令行解释器就是Shell外壳在Linux系统中的具体表现。

命令行解释器上每一行都有输入指令的提示,用户输入指令和选项之后,Shell外壳接收并解析该指令,然后发送给Linux内核去处理执行,Linux内核处理之后将结果反馈给Shell外壳,Shell外壳将结果解析返还给用户。

二、描述Shell外壳原理的生动例子

从前有座山,山里有座村,村里有个老村长,而你是村长的儿子——王二狗。同时你们村还有个远近闻名的媒婆——王婆,在你们当地有非常不错的口碑,曾撮合成功了无数对男女。

你作为村长的儿子,也老大不小了,也到了该找对象的年纪。你作为一个纯情的男人,心理自然还想着你们村的如花姑娘。

但是你还是一个害羞的小男生,不方便也不敢直接去和如花姑娘直说,所以你就只得找王婆来代为传递你的信息。

这天,你找到了王婆,说你想找如花姑娘相亲,王婆说可以帮你办这件事,然后她就把你想找如花姑娘约会这件事告诉了如花姑娘。如花姑娘说不行,她说她并不认识王二狗,不想和他相亲。于是王婆就很直白的告诉你说,如花姑娘压根不想和你相亲,你还是放弃吧。

所以王婆便是沟通你和如花姑娘的桥梁,类比用户和操作系统内核之间的媒介作用。

你心想,作为一个纯情的男人,我是不会放弃的!所以这天你有找到王婆,说再帮我问问看吧,我真的很喜欢如花姑娘[大哭]。

王婆说好吧,那我再给你传达最后一次,不过不出所料,结局再次上演,如花再次拒绝,王婆又把这个残忍的事实传达给你。

这时你还是放不下,有跟王婆说能不能再再帮我问一次。王婆此时直接拒绝了,说不要再这样子了,这样如花姑娘会不堪其扰内心厌烦且痛苦的(王婆顾及如花姑娘的感受,事实上也是在保护如花姑娘,防止你亲自去找她,做出极端的事情)。

所以王婆拥有拒绝传达信息的权利,类比Shell外壳可以拒绝一些非法的请求,从而保护os。

不过故事仍然没有结束,你可是村长的儿子啊,你的一再要求,王婆肯定会考虑到村长的面子。但是王婆也得考虑到自己的口碑,不能因为这件事把招牌给砸了,毕竟还忙着给其他男女说媒呢。

所以这时王婆想到一个绝妙的对策,招一个实习生来办这件事(王婆:让我的实习生来给你办这件事吧,我溜了哦~)。

实习生办这件事,对王婆来说有两个好处,一是王婆可以跟村长交代这件事她一直在办着,只不是是她的实习生在负责,二是就算实习生办砸了这件事也没关系,反正只要不是她王婆办的,这个招牌就不会砸。

王婆可以找实习生执行难度大的任务,类比Shell外壳可以创建子进程去执行有风险的任务,从而不影响Shell外壳。

总结

Shell外壳是对Linux内核的封装,连接沟通了用户(需求)与Linux内核(执行),这降低了用户的操作学习成本。

Shell外壳可以传达用户指令,交给操作系统内核去执行,最终把执行结果反馈给用户。

同时Shell外壳也可以直接拒绝用户,从而保护操作系统内核。

Shell外壳也可以通过创建Shell外壳程序的子进程的方式,来执行有风险的指令,从而来保护bash即Shell外壳本身。

三、C语言模拟实现Shell外壳

命令行解释器

  • 输出提示
  • 获取用户输入
  • 执行命令

核心算法:字符串切割、进程替换、环境变量调用、重定向

源代码:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <assert.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>

#define NUM 1024
#define OPT_NUM 64

#define NONE_REDIR 0
#define INPUT_REDIR 1
#define OUTPUT_REDIR 2
#define APPEND_REDIR 3

char commandLine[NUM];
char* myArgv[OPT_NUM];
int lastCode = 0;
int lastSig = 0;

int redirType = NONE_REDIR;
char* redirFile = NULL;

// 跳过空格
void skip_space(char* start)
{
    while (isspace(*start))
    {
        ++start;
    }
}

// 检查命令行是否是输入输出重定向
void command_check(char* commands)
{
    assert(commands);
    char* start = commands;
    char* end = commands + strlen(commands);

    while (start < end)
    {
        if (*start == '>')
        {
            *start = 0;
            ++start;
            if (*start == '>')
            {
                // 追加输出重定向
                // "ls -a -l >> test.txt"
                redirType = APPEND_REDIR;
                ++start;
            }
            else
            {
                // 覆盖输出重定向
                // "ls -a -l > test.txt"
                redirType = OUTPUT_REDIR;
            }
            skip_space(start);
            redirFile = start;
            break;
        }
        else if (*start == '<')
        {
            // 输入重定向
            // "cat < test.txt"
            *start = 0;
            ++start;
            skip_space(start);
            redirType = INPUT_REDIR;
            redirFile = start;
            break;
        }
        else 
        {
            ++start;
        }
    }
}

int main()
{
    while (1)
    {
        redirType = NONE_REDIR;
        redirFile = NULL;

        // 输入提示符
        char* pwd = getenv("PWD");
        char* token = strtok(pwd, "/");
        char* dir = token;
        while (token != NULL)
        {
            dir = token;
            token = strtok(NULL, "/");
        }

        char* user = getenv("USER");
        if (strcmp(user, "root") == 0)
        {
            printf("[%s@%s %s]# ", user, getenv("HOSTNAME"), dir);
        }
        else
        {
            printf("[%s@%s %s]$ ", user, getenv("HOSTNAME"), dir);
        }
        fflush(stdout);

        // 获取用户输入
        fgets(commandLine, sizeof(commandLine) - 1, stdin);
        commandLine[strlen(commandLine) - 1] = 0;
        command_check(commandLine);

        // 字符串切割
        myArgv[0] = strtok(commandLine, " ");
        int i = 1;

        // 添加、更改参数
        if (myArgv[0] != NULL && strcmp(myArgv[0], "ls") == 0)
        {
            myArgv[i++] = (char*)"--color=auto";
        }
        if (myArgv[0] != NULL && strcmp(myArgv[0], "ll") == 0)
        {
            myArgv[0] = (char*)"ls";
            myArgv[i++] = (char*)"-l";
            myArgv[i++] = (char*)"--color=auto";
        }

        // 存储参数
        while (myArgv[i++] = strtok(NULL, " "))
        {}

        // 如果是 cd 指令,无需创建子进程
        // 让 shell 执行对应的命令,本质是是执行系统接口    
        // 这种不需要子进程执行,而是让 shell 自己执行的命令,叫做内置/内建命令
        if (myArgv[0] != NULL && strcmp(myArgv[0], "cd") == 0)
        {
            if (myArgv[1] != NULL)
            {
                chdir(myArgv[1]);   // 跳转目录
                char tmp_path[1024] = {0};
                getcwd(tmp_path, sizeof(tmp_path) - 1);     // 将当前工作目录存入tmp_path中
                setenv("PWD", tmp_path, 1);     // 修改环境变量
            }
            else
            {
                chdir(getenv("HOME"));
                setenv("PWD", getenv("HOME"), 1);
            }
            continue;
        }
        if (myArgv[0] != NULL && myArgv[1] != NULL && strcmp(myArgv[0], "echo") == 0)
        {
            if (strcmp(myArgv[1], "$?") == 0)
            {
                // 查看上一条指令的退出码、退出信号
                printf("last exit code: %d, last exit sig: %d\n", lastCode, lastSig);
            }
            else
            {
                printf("%s\n", myArgv[1]);
            }
            continue;
        }

        // 执行命令
        pid_t id = fork();
        if (id == 0)
        {
            // 命令由子进程执行,重定向的工作由子进程完成
            // 父进程给子进程提供信息重定向
            int fd = 0;
            switch (redirType)
            {
            case NONE_REDIR:
                break;
            case INPUT_REDIR:
                fd = open(redirFile, O_RDONLY);
                if (fd < 0)
                {
                    perror("open");
                    exit(errno);
                }
                dup2(fd, 0);
                break;
            case OUTPUT_REDIR:
            case APPEND_REDIR:
                umask(0);
                int flags = O_WRONLY | O_CREAT;
                if (redirType == APPEND_REDIR)
                {
                    flags |= O_APPEND;
                }
                else
                {
                    flags |= O_TRUNC;
                }
                fd = open(redirFile, flags, 0666);
                if (fd < 0)
                {
                    perror("open");
                    exit(errno);
                }
                dup2(fd, 1);
                break;
            default:
                printf("bug\n");
                break;
            }
            execvp(myArgv[0], myArgv);
            exit(1);
        }

        // 父进程
        int status = 0;
        pid_t ret = waitpid(id, &status, 0);
        assert(ret > 0);
        lastCode = (status >> 8) & 0xFF;
        lastSig = status & 0x7F;
    }

    return 0;
}

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

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

相关文章

一文教会你风格迁移CycleGAN从入门到高阶再到最终成功魔改(附成功魔改代码)

专栏导读 &#x1f525;&#x1f525;本文已收录于专栏&#xff1a;《风格迁移之从入门到成功魔改》&#xff0c;欢迎免费订阅 ​此专栏用于带你从零基础学会什么是风格迁移&#xff0c;风格迁移有什么作用&#xff0c;传统做法和Cyclegan的原理&#xff0c;及其优缺点&#x…

Docker NGINX 加载Geoip模板

前提环境&#xff1a; Docker 环境 涉及参考文档&#xff1a; ngx_http_geoip_module 模块Loki NGINX Service MeshGeoIP IP库 一、下载GeoIP IP库 二、配置Nginx主配置文件 vim /data/nginx/MangoMoh/dos/nginx.confuser nginx; worker_processes auto;error_log /var…

ncnn源码阅读(三)----数据结构Mat

文章目录 数据结构Mat成员变量成员方法构造函数1、普通构造函数2、外部数据指针构造函数3、拷贝构造函数和opertor 深拷贝函数类型转换引用计数的实现其他数据操作函数 数据结构Mat 个人认为一个框架中的比较核心的两个点&#xff0c;一个是数据结构&#xff0c;一个任务调度…

STM32F407ZGT6正点原子F4探索者开发板 -- 跑马灯例程

1. USB 转USART1 烧录工具 FlyMcu 2. FlyMcu 配置 3. 查看开发板原理图&#xff0c;LED0、LED1 硬件连接 LED0 - PF9 LED1 - PF10 PF9 0&#xff0c; LED0 亮&#xff0c;PF9 1&#xff0c;LED0 灭 PF10 0&#xff0c; LED1 亮&#xff0c;PF10 1&#xff0c;LED1 灭 4.…

cesium 实现多颗卫星与多颗地面站雷达通信效果

最主要的部分是计算空间内两点之间的距离以及卫星对地点是否在雷达扫描范围内 先看效果 1.计算空间内两点之间的距离 //计算距离 function distance(point1, point2) {var point1cartographic = Cesium.Cartographic.fromCartesian(point1);var</

医师访问学者申请需要注意什么?

医师访问学者申请是医学领域中一项重要的学术交流和合作方式。在准备申请时&#xff0c;有几个关键点需要注意&#xff0c;下面就随知识人网小编一起来看一看。 1. 目标和动机&#xff1a;明确访问学者的目标和动机&#xff0c;例如学术研究、专业发展、文化交流等。清楚表达你…

产品经理学习画原型-登录界面(交互样式设置)

经过第一阶段学习&#xff0c;目前效果如下&#xff1a; 下面我们来加入交互样式效果&#xff1a; 效果&#xff1a;

[SSM]MyBatis的缓存与逆向工程

目录 十三、MyBatis的缓存 13.1一级缓存 13.2二级缓存 13.3MyBatis集成EhCache 十四、MyBatis的逆向工程 14.1逆向工程配置与生成 14.2测试 十三、MyBatis的缓存 缓存&#xff1a;cache 缓存的作用&#xff1a;通过减少IO的方式&#xff0c;来提高程序的执行效率。 myb…

【分布式】 ELK 企业级日志分析系统 二

目录 一、FilebeatELK 部署1.1 环境部署 二、grok 正则捕获插件mutate 数据修改插件multiline 多行合并插件date 时间处理插件 一、FilebeatELK 部署 1.1 环境部署 Node1节点&#xff08;2C/4G&#xff09;&#xff1a;node1/192.168.137.101 Elasticsearch Node2节点&…

【C++顺序容器】forward_list的成员函数和非成员函数

目录 forward_list 1. forward_list的成员函数 1.1 构造、析构和赋值运算符重载 1.1.1 构造函数 1.1.2 析构函数 1.1.3 赋值运算符重载 1.2 迭代器 1.3 容量 1.4 元素访问 1.4.1 遍历方法 1.5 修改器 1.6 操作 1.7 观察者 2. forward_list的非成员函数 forward_l…

npm5中本地间模块引用的最好方式(附带引用方法总结)

引用其他的包 正常情况下在项目 package.json 所在的目录&#xff08;一般也是项目根目录&#xff09;运行npm install xxxx 命令之后&#xff0c;会从远程或者代理地址下载xxxx包到node_modules&#xff0c;然后在package.json生成对应的包名和版本 如果想要依赖本地自己开发…

TypeScript 学习笔记(一):类型

文章目录 一、常见类型1. 数组2. 布尔3. 数值4. 字符串5. object6. null 和 undefined7. symbol7.1 作为属性名7.2 属性名遍历7.3 静态方法&#xff1a;Symbol.for()和 Symbol.keyFor()7.4 内置 symbol 值7.4.1 Symbol.hasInstance7.4.2 Symbol.isConcatSpreadable7.4.3 Symbol…

第四十五章Java 接口

Java 接口 接口&#xff08;英文&#xff1a;Interface&#xff09;&#xff0c;在JAVA编程语言中是一个抽象类型&#xff0c;是抽象方法的集合&#xff0c;接口通常以interface来声明。一个类通过继承接口的方式&#xff0c;从而来继承接口的抽象方法。 接口并不是类&#x…

DevOps(二)

CD 1. 平台选择2. 技术选型3. 阶段性目标4. 搭建示例4.1 环境准备(节点机)1. java版本升级2. 编译安装git3. docker安装4. docker-compose安装5. sonarqube安装6. harbor安装7. gitlab私服 4.2 示例一&#xff08;手动&#xff09;1. 创建项目2. 编码3. Dockerfile4. 拷贝pytho…

【linux】线程详解

线程 线程的概念 在官方书籍对于线程的概念&#xff1a; 1.在进程内部的执行流 2.线程比进程粒度更细&#xff0c;调度成本更低。 3.线程是CPU调度的最小单位。 线程&#xff08;tcb&#xff09;&#xff1a;进程&#xff08;pcb&#xff09; n&#xff1a;1 进程和线程在执…

Java 动态规划 面试题 17.16. 按摩师

代码展示&#xff1a; class Solution {public int massage(int[] nums) {int nnums.length;if(n0){return 0;}//创建数组int f[]new int[n]; //f[i]表示接i位置的最长时间int g[]new int[n]; //g[i]表示不接i位置的最长时间//初始化f[0]nums[0];g[0]0;//填充数组for(int i1;i…

VectorCAST单元测试手动配置测试用例

一、单元测试 等待环境创建完成后&#xff0c;就可以开始单元测试。 二、生成测试用例 在 VectorCAST 中&#xff0c;一共有两种方法来生成测试用例&#xff0c;一种是手动生成测试用例&#xff0c;另外一种是自动 生成测试用例。 三、手动生成测试用例 在 VectorCAST 中&a…

《面试1v1》Redis分布式锁

&#x1f345; 作者简介&#xff1a;王哥&#xff0c;CSDN2022博客总榜Top100&#x1f3c6;、博客专家&#x1f4aa; &#x1f345; 技术交流&#xff1a;定期更新Java硬核干货&#xff0c;不定期送书活动 &#x1f345; 王哥多年工作总结&#xff1a;Java学习路线总结&#xf…

泛微E-Cology SQL注入漏洞复现(QVD-2023-15672)

0x01 产品简介 泛微协同管理应用平台e-cology是一套兼具企业信息门户、知识文档管理、工作流程管理、人力资源管理、客户关系管理、项目管理、财务管理、资产管理、供应链管理、数据中心功能的企业大型协同管理平台。 0x02 漏洞概述 由于泛微e-cology未对用户的输入进行有效的…

matlab学习指南(2):安装工具箱Toolbox的方法(详细图解)

&#x1f305;*&#x1f539;** φ(゜▽゜*)♪ **&#x1f539;*&#x1f305; 欢迎来到馒头侠的博客&#xff0c;该类目主要讲数学建模的知识&#xff0c;大家一起学习&#xff0c;联系最后的横幅&#xff01; 喜欢的朋友可以关注下&#xff0c;私信下次更新不迷路&#xff0…