「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.mzph.cn/news/195765.shtml

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

相关文章

142873-41-4脂质过氧化抑制剂1-星戈瑞

142873-41-4脂质过氧化抑制剂1 英文名称&#xff1a;Lipid peroxidation inhibitor 1 中文名称&#xff1a;脂质过氧化抑制剂 化学名称&#xff1a;2,4,6,7-四甲基-2-[(4-苯基哌啶-1-基)甲基]-3H-1-苯并呋喃-5-胺 CAS&#xff1a;142873-41-4 外观&#xff1a;固体粉末 分…

D2822ML 用于便携式录音机和收音机作音频功率放大器。采用 DIP8 SOP8 封装形式

D2822ML 用于便携式录音机和收音机作音频功率放大器。采用 DIP8 SOP8 封装形式 特点: 电源电压降到 1.8V 时仍能正常工作交越失真小 静态电流小可作桥式或立体声式功放应用外围元件少通道分离度高 开机和关机无冲击噪声软限幅

RT-Thread 内存管理

在计算机系统中&#xff0c;通常存储空间可以分为两种&#xff1a;内部存储空间和外部存储空间。 内部存储空间通常访问速度比较快&#xff0c;能够按照变量地址随机访问&#xff0c;也就是我们通常所说的RAM&#xff08;随机存储器&#xff09;&#xff0c;可以把它理解为电脑…

docker安装达梦数据库并挂在数据卷

离线包下载地址&#xff1a;请点击 1.在线下载 wget https://download.dameng.com/eco/dm8/dm8_20230808_rev197096_x86_rh6_64_single.tar2. 导入镜像 docker load -i dm8_20230808_rev197096_x86_rh6_64_single.tar3. 运行容器 docker run -d -p 5236:5236 --restartalwa…

微信公众号端在线客服系统源码 聊天记录云端实时保存 附带完整的搭建教程

随着社交媒体的普及&#xff0c;越来越多的用户通过微信公众号与企业进行沟通。因此&#xff0c;开发一款基于微信公众号的在线客服系统&#xff0c;可以帮助企业更好地服务用户&#xff0c;提高客户满意度。同时&#xff0c;为了解决聊天记录的存储和管理问题&#xff0c;我们…

【Python 训练营】N_17 冒泡排序

题目 列表L [3,2,5,6,1,3,8,1,9]&#xff0c;冒泡排序实现从小到大排列。 分析 冒泡排序的基本思想是从序列的第一个元素开始&#xff0c;依次比较相邻的两个元素&#xff0c;如果它们的顺序错误就交换它们的位置&#xff0c;直到整个序列有序为止。具体步骤如下&#xff1…

如何看待华为宣称“纯鸿蒙”OS将不再兼容安卓应用 APK彻底再见?

如何看待华为宣称“纯鸿蒙”OS将不再兼容安卓应用 APK彻底再见&#xff1f; 在开始前我有一些资料&#xff0c;是我根据自己从业十年经验&#xff0c;熬夜搞了几个通宵&#xff0c;精心整理了一份「安卓开发资料从专业入门到高级教程工具包」&#xff0c;点个关注&#xff0c;…

2023.12.4 GIT的概念和组成

目录 1.git的介绍 2.git的历史 开发者&#xff1a;Linus Torvalds Linux的创始人 3.git和svn的对比 svn:集中式管理 git:分布式管理 4.git管理的组成结构 1.git的介绍 git是项目版本管理工具,能自动的将多个版本进行管理存储,类似于快照,多个人共享版本 git的诞生:分布式…

VIM中替换光标所在行文字内容

:s/old/new/g将光标所在行的所有的old字符替换为new字符 :n,$s/old/new/g 从第n行开始到文件尾&#xff0c;将每一行中的old字符替换为new字符

PyQt pdf格式保存

参考文章 pyqt5:利用QFileDialog从本地选择图片\文本文档显示到label、保存图片\label文本到本地&#xff08;附代码&#xff09;_pyqt5中qfiledialog.getopenfileurl-CSDN博客 txt文件的打开与保存 def openTextFile(self): # 选择文本文件上传fd,fp QFileDialog.getOpen…

springboot统一异常处理

在 Spring Boot 中&#xff0c;可以通过自定义异常处理器来实现统一的异常处理。 创建自定义异常类 首先&#xff0c;创建一个自定义的异常类&#xff0c;继承自 RuntimeException 或其子类。这个异常类可以用来表示应用程序中的特定异常情况。 public class CustomExceptio…

行为型剩余的模式

1.中介者模式 package com.jmj.pattern.mediator;public abstract class Mediator {public abstract void constact(String message,Person person); }package com.jmj.pattern.mediator;public class MediatorStructure extends Mediator{private HouseOwner houseOwner;priva…

华为云云绘本第一期:童话奇迹原来是你

点此进入官网&#xff0c;专家1对1&#xff1a;应用身份管理服务OneAccess_华为云IDaaS-华为云

赛捷CRM集成无需API开发:连接营销系统,优化电商用户运营和广告策略

赛捷CRM集成无需API开发&#xff1a;连接营销系统&#xff0c;优化电商用户运营和广告策略 在当前的电商热潮下&#xff0c;企业如何在竞争中脱颖而出&#xff0c;提高用户运营效率和广告策略的精准度&#xff0c;成为了关键性的挑战。赛捷CRM以其无需API开发的集成解决方案&a…

如何看待 Android 面试却是 Java 面试官?

如何看待 Android 面试却是 Java 面试官&#xff1f; 在开始前我有一些资料&#xff0c;是我根据自己从业十年经验&#xff0c;熬夜搞了几个通宵&#xff0c;精心整理了一份「Android资料从专业入门到高级教程工具包」&#xff0c;点个关注&#xff0c;全部无偿共享给大家&…

变配电智能监控系统

变配电智能监控系统是一种能够实时监测电力变压器和配电柜、配电箱运行状态的智能设备。这种系统利用先进的传感器和数据通信技术&#xff0c;能够实时监测电力设备的运行状态&#xff0c;包括电压、电流、温度、湿度等参数&#xff0c;并且能够对这些数据进行处理和分析&#…

CTF特训日记day3

复现一下RWCTF5th shellfind题目 题目描述如下&#xff1a; Hello Hacker. You dont know me, but I know you. I want to play a game. Heres what happens if you lose. The device you are watching is hooked into your Saturday and Sunday. When the timer in the back …

安卓8预装可卸载应用

环境 系统&#xff1a;Android 8 CPU:MTK 理论上改法适用于其他平台&#xff0c;比如展讯。 不适用于安卓11。安卓11请参照android 11预装APP到data/app目录 实现 假设要内置test这个应用。 第一步把test添加到系统编译配置中&#xff0c;不同平台或cpu文件不一样。 比如&am…

ES6与ES5的区别?

目录 一、模块化区别 1.ES6模块化 2.Commonjs模块化 区别&#xff1f; 二、声明变量方式区别? var特点&#xff1a; let特点: const特点: 一、模块化区别 1.ES6模块化 导出&#xff1a; 1.列表导出 export {first,last} 2.重命名导出 export {first as fi,last…

沿着马可·波罗的足迹,看数字云南

刚入行的时候&#xff0c;有位前辈跟我说过一句话&#xff1a;去现场“要像外国人一样去看”&#xff0c;重新审视那些自己可能早已“熟视无睹”的事物。 前不久&#xff0c;我跟随“看见数字云南——云南数字经济媒体探营活动”&#xff0c;奔赴昆明、大理、西双版纳等地&…