「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;可以把它理解为电脑…

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

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

如何看待华为宣称“纯鸿蒙”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的诞生:分布式…

行为型剩余的模式

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 …

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

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

多路径传输(MPTCP MPQUIC)数据包调度研究总结

近些年来&#xff0c;以5G和Wifi6为代表的无线通信技术发展迅速&#xff0c;并已经在全世界实现了大规模部署。此外&#xff0c;智能手机等移动设备不断迭代更新&#xff0c;其网络通信能力也持续演进&#xff0c;使得应用同时利用多个不同网卡在多条不同物理链路上&#xff08…

性价比开放式蓝牙耳机推荐哪款、性价比最高的开放式耳机

传统的耳机设计虽然便携&#xff0c;但却可能给一些需要长时间佩戴的用户带来不适。长时间封闭在耳机内可能导致耳朵不透气&#xff0c;甚至引起疼痛。这就是为什么近年来开放式耳机越来越受欢迎的原因。这种耳机设计无需直接插入耳道&#xff0c;采用挂耳的佩戴方式&#xff0…

Python3+selenium自动化测试框架详解

背景 为了更好的发展自身的测试技能&#xff0c;应对测试行业以及互联网行业的迭代变化。自学python以及自动化测试。 虽然在2017年已经开始接触了selenium&#xff0c;期间是断断续续执行自动化测试&#xff0c;因为还有其他测试任务&#xff0c;培训任务要执行… 前期建议大…

W11+Ipv6+可道云+PHPstudy实现私人云盘搭建

W11Ipv6可道云PHPstudy实现私人云盘搭建 一、搭建原因二、搭建过程软件选择服务器环境管理软件私人云盘 可道云搭建小皮面板搭建 三、相关配置程序开机自启远程关机远程开机 四、相关参考 一、搭建原因 工位电脑上一些文件想备份到家里电脑&#xff0c;购买NAS又有点多余&…

solidity实现ERC1155多代币标准

文章目录 1、NFT - 维基百科2、IERC1155MetadataURI3、IERC1155Receiver4、IERC11555、ERC11556、NFT11557、开源地址 1、NFT - 维基百科 ERC-1155 标准于2018年6月由Witek Radomski、Andrew Cooke、Philippe Castonguay、James Therien、Eric Binet及Ronan Sandford提出。此标…

从微软官网下载系统镜像重装的方法

一、制作系统镜像介质U盘 1、在一台能够正常进入系统的电脑中登录以下网址&#xff1a;https://www.microsoft.com/zh-cn/software-download/windows10&#xff0c; 点击立即下载工具。 2、在下载完成后&#xff0c;双击打开&#xff0c;选择为另一台电脑创建安装介质&#xf…

controller能接收到数据有数据但是前端无法显示数据

又是制作系统时遇到的问题。只是想做个查询商品的页面&#xff0c;结果弄了一天&#xff0c;在网上各种查问题&#xff0c;各种解决办法用在我的代码上&#xff0c;换了无数种关键词搜索终于找到了一条成功解决了问题。 问题描述&#xff1a; 事情是这样的&#xff1a;我要写一…