「Linux」使用C语言制作简易Shell

news2024/11/15 23:29:40
在这里插入图片描述

💻文章目录

  • 📄前言
  • 简易shell实现
    • shell的概念
    • 系统环境变量
    • shell的结构定义
    • 内建命令
    • 完整代码
  • 📓总结


📄前言

对于很多学习后端的同学来讲,学习了C语言,发现除了能写出那个经典的“hello world”以外,其他什么都做不了,如果你在烦恼着这些事的话,不妨来学习下如何实现Linux中的shell吧,如此不仅能提高你C语言功力,也能增进你对系统的理解。

文章知识点要求

  • 系统环境变量
  • 多进程编程(fork函数)
  • 程序替换
  • 程序等待

如果你还没有学习过这些知识,可以我的以前写的文章中学习。

简易shell实现

shell的概念

shell是一种作为命令解释器的应用程序,用于用户与操作系统之间的交互。因为系统内核的指令过于复杂,而且如果让用户直接执行,会轻易使系统出现错误,而shell就是为了这种情况而诞生的。

在这里插入图片描述

系统环境变量

在写代码之前我们还得了解一下系统的环境变量该如何在C语言中获取。我们有两种方式获取系统中的环境变量,用getenv函数来获取某一个环境变量的值,或者使用全局变量environ,这是一个指向了环境变量表的指针。

如果需要需要修改或增加环境变量,则可以使用putenv。

  • getenv(const char* name ) -> char* :参数为环境变量的名字,返回名字对于的内容。
  • putenv(char* string) -> int :参数为需要新增的环境变量.
  • char** environ ; 包含在unistd.h,可以通过便利来遍历所有环境变量

使用实例

#include <stdlib.h> 
#include <stdio.h>
#include <string.h>

int main()
{
    // putenv 
    char ptr[] = "PATH=/usr/bin;/home;/tmp;";
    int n = putenv(ptr);
    for(int i = 0; environ[i]; ++i)
    {
        printf("%s\n", environ[i]);
    }
    printf("%s\n", getenv("PATH"));
    return 0;
}

shell的结构定义

总所周知,shell是无时无刻都在运行着的,并且如果我们在shell中运行一个程序错误了,一般也不会波及到shell。那么,我们可以通过让父进程来检查输入命令,子进程来实现用户指令的方式来实现。

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

#define NUM 1024	
#define SIZE 64
#define SEP " "		//需要切割的字符

const char* getCwd()	//获取当前路径
{
    const char* str = getenv("PWD");
    if(!str)    
        return "none";

    int n = strlen(str) - 1;
    while (n) 
    {
        if(str[n] == '/')
            break;
        --n;
    }

    return str+n+1;	//返回当前文件夹的名字
}

void commandSplit(char* usercommand, char** argv)	//切割字符串
{
    int n = 0;
    argv[n++] = strtok(usercommand, SEP);	
    while(argv[n++] = strtok(NULL, SEP));		//继续切割
}

int execute(char** argv)		//执行命令
{
    pid_t id = fork();
    if(id == -1)    
        return -1;
    else if(id == 0)
    {
        execvp(argv[0], argv);	//子进程执行命令。
        exit(1);
    }
    else 
    {
        int status = 0;
        pid_t rid = waitpid(id, &status, 0);    //阻塞等待
    }
    return 0;
}
    
int getUserCommand(char* usercommand, size_t n)
{
    printf("> %s:", getCwd());
    char* cmd = fgets(usercommand, n, stdin);
    if(!cmd)    
        return -1;

    n = strlen(cmd);
    usercommand[n-1] = '\0';

    return n;
}

int main()
{
    while (1) 
    {
        char usercommand[NUM];	
        char* argv[SIZE];
        int n = getUserCommand(usercommand, sizeof(usercommand));
        if(n <= 0) continue;	//输入错误
        commandSplit(usercommand, argv);	//切割
        execute(argv);		//执行
    }
}

在这里插入图片描述

我们可以看到程序可以正常的执行ls、pwd等命令,但如果使用cd echo 等命令则出现了异常,而cd、echo等命令就是内建命令。

内建命令

内建命令是类似于shell自己执行的命令,类似与内部函数的存在。如果要制作shell,必须得要完善一下内建命令。

char cwd[1024];	//存储当前的目录位置
int lastRet = 0;	//上一次子进程完成后的返回码

int cd(const char* path)
{//更改当前的PWD环境变量
    chdir(path);	//更改当前的工作目录
    char temp[512];
    getcwd(temp, sizeof(temp));
    sprintf(cwd, "PWD=%s", temp);		
    return putenv(cwd);		//更新env中的变量
}

int doBuildin(char* argv[])
{
    if(strcmp(argv[0], "cd") == 0)	
    {
        char* path = argv[1];
        if(!path)
            path = getenv("HOME");
        
        return cd(path);
    }
    else if(strcmp(argv[0], "export") == 0)
    {
        if(argv[1] == NULL) 
            return 0;
        /*需要注意putenv函数在enval释放后,新增的变量也会同时消失。*/
        char* enval = (char*)malloc(strlen(argv[1])+1);
        strcpy(enval, argv[1]);

        return putenv(enval);
    }
    else if(strcmp(argv[0], "echo") == 0)
    {
        if(*argv[1] == '$' && strlen(argv[1]) > 1)	//参数为变量的情况
        {
            char* val = argv[1] + 1;
            if(strcmp(val, "?") == 0)	//获取上一次执行的返回码
            {
                printf("%d\n", lastRet);
                lastRet = 0;
            }
            else 
            {
                const char* enval = getenv(val);	//获取环境变量
                if(!enval)  
                    printf("\n");
                else 
                    printf("%s\n", enval);
            }
            return 0;
        }
        else  	//非变量
        {
            printf("%s\n", argv[1]);
            return 0;
        }
    }
    return 1;
}   

运行结果

在这里插入图片描述

完整代码

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


#define NUM 1024	
#define SIZE 64
#define SEP " "		//需要切割的字符

char cwd[1024];		//存储当前工作目录
int lastRet = 0;		//存储上一次指令的执行结果

const char* getCwd()	//获取当前路径	
{
    const char* str = getenv("PWD");
    if(strcmp(str, getenv("HOME")) == 0)
        return "~";		//家目录的情况返回"~";

    int n = strlen(str) - 1;		//获取长度
    while (n) 
    {
        if(str[n] == '/')
            break;
        --n;
    }
        
    return n == 0 ? str : str + n + 1;	//返回当前文件夹的名字
}

void commandSplit(char* usercommand, char** argv)	//切割字符串
{
    int n = 0;
    argv[n++] = strtok(usercommand, SEP);	
    while(argv[n++] = strtok(NULL, SEP));		//继续切割
}

int execute(char** argv)		//执行命令
{
    pid_t id = fork();
    if(id == -1)    
        return -1;
    else if(id == 0)
    {
        execvp(argv[0], argv);	//子进程执行命令。
        exit(127);
    }
    else 
    {
        int status = 0;
        pid_t rid = waitpid(id, &status, 0);    //阻塞等待
        lastRet = WEXITSTATUS(status);
    }
    return 0;
}


int cd(const char* path)
{
    chdir(path);	//改变工作目录
    char temp[512];
    getcwd(temp, sizeof(temp));	//获取当前目录
    sprintf(cwd, "PWD=%s", temp);	//打印当前目录到PWD
    return putenv(cwd);
}

int doBuildin(char* argv[])		//执行内建命令
{
    if(strcmp(argv[0], "cd") == 0)	//对比字符串,为0即相同
    {
        char* path = argv[1];
        if(!path)
            path = getenv("HOME");
        
        return cd(path);
    }
    else if(strcmp(argv[0], "export") == 0)
    {
        if(argv[1] == NULL) 
            return 0;
        /*注意:putenv在参数释放后,新增的变量内容也会随着消失,
        所以这里使用了动态内存开辟*/
        char* enval = (char*)malloc(strlen(argv[1])+1);
        strcpy(enval, argv[1]);

        return putenv(enval);
    }
    else if(strcmp(argv[0], "echo") == 0)	
    {
        if(!argv[1])	//空参数
        {
            return 0;
        }
        else if(*argv[1] == '$' && strlen(argv[1]) > 1)		//为参数是变量
        {
            char* val = argv[1] + 1;
            if(strcmp(val, "?") == 0)			//返回上一次指令的返回码
            {
                printf("%d\n", lastRet);
                lastRet = 0;
            }
            else 	//参数不是变量
            {
                const char* enval = getenv(val);
                if(!enval)  
                    printf("\n");
                else 
                    printf("%s\n", enval);
            }
            return 0;
        }
        else  
        {
            printf("%s\n", argv[1]);
            return 0;
        }
    }
    return 1;
}   
    
int getUserCommand(char* usercommand, size_t n)
{
    printf("> %s:", getCwd());
    char* cmd = fgets(usercommand, n, stdin);
    if(!cmd)    
        return -1;

    n = strlen(cmd);
    usercommand[n-1] = '\0';

    return n;
}

int main()
{
    while (1) 
    {
        char usercommand[NUM];	
        char* argv[SIZE];
        int n = getUserCommand(usercommand, sizeof(usercommand));	
        if(n <= 0) continue;	//输入错误
        commandSplit(usercommand, argv);	//切割

        n = doBuildin(argv);		//执行内建指令
        if(!n)  continue;			//执行内建执行后

        execute(argv);		//执行指令
    }
}

📓总结

📜博客主页:主页
📫我的专栏:C++
📱我的github:github

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

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

相关文章

XUbuntu22.04之OBS30.0设置录制音频降噪(一百九十六)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

LabVIEWL实现鸟巢等大型结构健康监测

LabVIEWL实现鸟巢等大型结构健康监测 管理国家地震防备和减灾的政府机构中国地震局(CEA)选择了七座新建的巨型结构作为结构健康监测(SHM)技术的测试台。这些标志性建筑包括北京2008年夏季奥运会场馆&#xff08;包括北京国家体育场和北京国家游泳中心&#xff09;、上海104层的…

QML学习一、GridView的使用和增加添加动画、删除动画

一、效果预览 二、源码分享 import QtQuick import QtQuick.ControlsApplicationWindow {visible: truewidth: 640height: 480title: "Test"property int cnt:cnt model.countListModel{id:modelListElement{index:0}ListElement{index:1}ListElement{index:2}List…

csapp-linklab之第3阶段“输出学号”实验报告(强弱符号)

题目 新建一个phase3_patch.o&#xff0c;使其与main.o和phase3.o链接后&#xff0c;运行输出自己的学号&#xff1a; $ gcc -o linkbomb main.o phase3.o phase3_patch.o $ ./linkbomb $学号 提示 利用符号解析中的强弱符号规则。&#xff08;COOKIE字符串未初始化&#xff…

单片机AVR单片机病房控制系统设计+源程序

一、系统方案 设计一个可容8张床位的病房呼叫系统。要求每个床位都有一个按钮&#xff0c;当患者需要呼叫护士时&#xff0c;按下按钮&#xff0c;此时护士值班室内的呼叫系统板上显示该患者的床位号&#xff0c;并蜂鸣器报警。当护士按下“响应”键时&#xff0c;结束当前呼叫…

【无标题】读transformer

这里写目录标题 transformerabstractconclusionintroductionbackground注意力机制mlptransformer和RNN传递序列信息embedding之后维度越大的向量归一化后其单个值就越小&#xff0c;乘个根号512position encoding加入时序信息 transformer abstract 编码器和解码器的架构 处理…

强化学习中的Q学习

Q学习&#xff08;Q-Learning&#xff09;是强化学习中的一种基于值的学习方法&#xff0c;用于在有限马尔可夫决策过程&#xff08;MDP&#xff09;中学习最优的动作策略。Q学习主要用于离散状态和离散动作的问题。 以下是Q学习的基本概念和步骤&#xff1a; Q-Value&#xf…

程序员也需要养生——程序员睡不好,重视一下你的情绪吧

程序员也需要养生——程序员睡不好&#xff0c;重视一下你的情绪吧 睡眠是一个复杂的系统工程&#xff0c;可以促进生长发育&#xff0c;修复受损的组织。促进大脑细胞的修复等等。在情绪的失调会影响到我们的睡眠状况。 一、心情差&#xff0c;压力大&#xff0c;睡不好跟这…

XXL-Job详解(一):组件架构

目录 XXL-Job特性系统组成架构图调度模块剖析任务 “运行模式” 剖析执行器 XXL-Job XXL-JOB是一个分布式任务调度平台&#xff0c;其核心设计目标是开发迅速、学习简单、轻量级、易扩展。现已开放源代码并接入多家公司线上产品线&#xff0c;开箱即用。 特性 1、简单&#…

【驱动】串口驱动分析(二)-tty core

前言 tty这个名称源于电传打字节的简称&#xff0c;在linux表示各种终端&#xff0c;终端通常都跟硬件相对应。比如对应于输入设备键盘鼠标&#xff0c;输出设备显示器的控制终端和串口终端。也有对应于不存在设备的pty驱动。在如此众多的终端模型之中&#xff0c;linux是怎么…

信贷专员简历模板

这份简历内容&#xff0c;以信贷专员招聘需求为背景&#xff0c;我们制作了1份全面、专业且具有参考价值的简历案例&#xff0c;大家可以灵活借鉴。 信贷专员简历在线编辑下载&#xff1a;百度幻主简历 求职意向 求职类型&#xff1a;全职 意向岗位&#xff1a;信贷专员 …

Linux:docker的数据管理(6)

数据管理操作*方便查看容器内产生的数据 *多容器间实现数据共享 两种管理方式数据卷 数据卷容器 1.数据卷 数据卷是一个供容器使用的特殊目录&#xff0c;位于容器中&#xff0c;可将宿主机的目录挂载到数据卷上&#xff0c;对数据卷的修改操作立刻可见&#xff0c;并且更新数…

基于SpringBoot的在线视频教育平台的设计与实现

摘 要 随着科学技术的飞速发展&#xff0c;各行各业都在努力与现代先进技术接轨&#xff0c;通过科技手段提高自身的优势&#xff1b;对于在线视频教育平台当然也不能排除在外&#xff0c;随着网络技术的不断成熟&#xff0c;带动了在线视频教育平台&#xff0c;它彻底改变了过…

Django二转Day03 04

0 cbv执行流程&#xff0c;self问题 path(index/, Myview.as_view()),Myview.as_view() 实例化后返回 变成return Myview.dispatch(request, *args, **kwargs)但是视图函数Myview中没有 dispatch 方法 所以去 父类View中寻找return View.dispatch(request, *args, **kwargs)调用…

动手学深度学习(六)---权重衰退

文章目录 一、理论知识二、代码实现【相关总结】 主要解决过拟合 一、理论知识 1、使用均方范数作为硬性限制&#xff08;不常用&#xff09; 通过限制参数值的选择范围来控制模型容量 通常不限制偏移b 小的意味着更强的正则项 使用均方范数作为柔性限制 对于每个都可以找到使…

Zabbix“专家坐诊”第213期问答汇总

问题一 Q&#xff1a;Zabbix报错&#xff1a;Zabbix server is not running :the information displayed may not be current&#xff0c;是什么问题呢&#xff1f; A&#xff1a; 1、数据库软件问题导致导入的zabbix数据库不完整2、zabbix Server配置问题3、zabbix-server没…

【C++】类和对象——const修饰成员函数和取地址操作符重载

在上篇博客中&#xff0c;我们已经对于日期类有了较为全面的实现&#xff0c;但是&#xff0c;还有一个问题&#xff0c;比如说&#xff0c;我给一个const修饰的日期类的对象 这个对象是不能调用我们上篇博客写的函数的&#xff0c;因为&d1是const Date*类型的&#xff…

【合集】MQ消息队列——Message Queue消息队列的合集文章 RabbitMQ入门到使用

前言 RabbitMQ作为一款常用的消息中间件&#xff0c;在微服务项目中得到大量应用&#xff0c;其本身是微服务中的重点和难点。本篇博客是Message Queue相关的学习博客文章的合集篇&#xff0c;目前主要是RabbitMQ入门到使用文章&#xff0c;后续会扩展其他MQ。 目录 前言一、R…

C++ ini配置文件的简单读取使用

ini文件就是简单的section 下面有对应的键值对 std::map<std::string, std::map<std::string, std::string>>MyIni::readIniFile() {std::ifstream file(filename);if (!file.is_open()) {std::cerr << "Error: Unable to open file " << …

Java多线程核心技术一-基础篇synchronzied同步方法

1 概述 关键字synchronzied保障了原子性、可见性和有序性。 非线程安全问题会在多个线程对同一个对象中的同一个实例变量进行并发访问时发生&#xff0c;产生的后果就是“脏读”&#xff0c;也就是读取到的数据其实是被更改过的。而线程安全是指获取的实例变量的值是经过同步处…