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

在这里插入图片描述

💻文章目录

  • 📄前言
  • 简易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);	//打印当前目录到PWDreturn 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.hqwc.cn/news/234763.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

代码随想录算法训练营第一天 | 704. 二分查找 27. 移除元素

class Solution { public:int search(vector<int>& nums, int target) {int l0;int rnums.size()-1;while(l<r){int mid(lr)>>1;if(targetnums[mid]) return mid;if(target>nums[mid]){lmid1;}else{rmid-1;}}return -1;} }; 之前就已经熟悉二分法了&am…

倒计时(JS计时器)

<script>function countDown() {document.body.innerHTML ;//清空页面内容var nowTimer new Date(); //现在时间的毫秒数var valueTimer new Date("2024-1-1 12:00"); //用户输入年份倒计时时间毫秒数var timer (valueTimer - nowTimer) / 1000; //倒计时秒…

量化误差的测量

因为转换的精度有限&#xff0c;所以将模拟值数字化时会不可避免地出现量化误差。量化误差由转换器及其误差、噪声和非线性度决定。当输入信号和计数器时基有区别时就会产生量化误差。根据输入信号的相位和计数器时基的匹配程度&#xff0c;计数器有下列三种可能性&#xff1a;…

JDK 动态代理从入门到掌握

快速入门 本文介绍 JDK 实现的动态代理及其原理&#xff0c;通过 ProxyGenerator 生成的动态代理类字节码文件 环境要求 要求原因JDK 8 及以下在 JDK 9 之后无法使用直接调用 ProxyGenerator 中的方法&#xff0c;不便于将动态代理类对应的字节码文件输出lombok为了使用 Sne…

性能测试:系统架构性能优化

今天谈下业务系统性能问题分析诊断和性能优化方面的内容。这篇文章重点还是谈已经上线的业务系统后续出现性能问题后的问题诊断和优化重点。 系统性能问题分析流程 我们首先来分析下如果一个业务系统上线前没有性能问题&#xff0c;而在上线后出现了比较严重的性能问题&#x…

Android Studio Giraffe版本遇到的问题

背景 上周固态硬盘挂了&#xff0c;恢复数据之后&#xff0c;重新换了新的固态安装了Win11系统&#xff0c;之前安装的是Android Studio 4.x的版本&#xff0c;这次也是趁着新的系统安装新的Android开发工具。 版本如下&#xff1a; 但是打开以前的Android旧项目时&#xff…

vue3 keep-alive页面切换报错:parentComponent.ctx.deactivate is not a function

问题&#xff1a; <router-view v-slot"{ Component }"><keep-alive ><component :is"Component" v-if"$route.meta.keepAlive" /></keep-alive><component :is"Component" v-if"!$route.meta.keepA…

PLC:200smart(13-16章)

PLC&#xff1a;200smart 第十三章2、带参子程序3、将子程序设置成库文件 第十三章 项目ValueValue主程序MAIN一个项目只能有一个&#xff0c;循环扫描子程序SBR_0项目中最多有128个&#xff0c;只有在调用时 才执行&#xff08;子程序可以嵌套其他子程序&#xff0c;最多八层…

STM32 启动文件分析

STM32 启动文件分析 基于STM32F103VET6芯片的 startup_stm32f10x_hd.s 启动文件分析 设置栈&#xff0c;将栈的大小Stack_Size设置为0x00004900&#xff08;18688/102418KB&#xff09;&#xff0c;即局部变量不能大于18KB。&#xff08;EQU等值指令&#xff0c;将0x0000490…

fiddler弱网测试实践

准备工作 1、fiddler安装包 2、一部安卓手机 一、fiddler安装 安装fiddler到电脑上&#xff0c;傻瓜式安装即可 二、fiddler环境配置 三、手机端环境配置 1、获取电脑的IP地址&#xff1a;WindowsR&#xff0c;输入cmd弹出命令窗口&#xff0c;输入命令ipconfig 或者鼠标…

无mac电脑生成uniapp云打包私钥证书的攻略

uniapp顾名思义是一个跨平台的开发工具&#xff0c;大部分uniapp的开发者&#xff0c;其实并没有mac电脑来开发&#xff0c;但是生成ios的证书&#xff0c;官网的教程却是需要mac电脑的&#xff0c;那么有没有办法无需mac电脑即可生成uniapp云打包的私钥证书呢&#xff1f; 下…

虚幻学习笔记1—给UI添加动画

一、前言 本文所使用的虚幻版本为5.3.2&#xff0c;之前工作都是用unity&#xff0c;做这类效果用的最多的是一个DoTween的插件&#xff0c;在虚幻中都内置集成了这这种效果制作。 图1.1 UI动画 二、过程 1、首先&#xff0c;在诸如按钮、图像等可交互控件中选中&#xff0c;如…