【Linux】模拟Shell命令行解释器

一、知识补充

1.1 snprintf

snprintf() 是 C语言的一个标准库函数,定义在<stdio.h>头文件中。

snprintf() 函数的功能是格式化字符串,并将结果存储在指定的字符数组中。该函数的原型如下:

int snprintf(char *str, size_t size, const char *format[,argument...]);

参数

  • str:指向一个字符数组,用于存储格式化后的字符串,该数组的大小至少为 size。
  • size:指定写入 str 数组中字符的最大个数(包括最后的空字符 '\0')。
  • format:包含格式说明符的字符串,它定义了后续参数的输出格式。
  • [,argument...]:可变参数列表,与格式字符串中的格式说明符相匹配

return 

  • 如果参数 size 的值足够大,则函数返回写入到 str 数组中的字符个数(不包括结尾的空字符),它的值位于[0, size-1]之间。
  • 如果出现编码错误,则返回一个负数。
  • 请注意,只有当这个返回值是非负且小于n时,字符串才被完整地写入了。

1.2 fflush 

fflush()函数:更新缓存区。头文件:#include<stdio.h> 

调用fflush()会将缓冲区中的内容写到stream所指的文件中去.若stream为NULL,则会将所有打开的文件进行数据更新。 

int fflush(FILE *stream);

fflush(stdin):刷新缓冲区,将缓冲区内的数据清空并丢弃。
fflush(stdout):刷新缓冲区,将缓冲区内的数据输出到设备。

  1 #include<stdio.h>2 #include<unistd.h>3 int main()4 {5     printf("hello");                                                                                                            6 7     sleep(5);8 9     printf(" world!\n");10 11     return 0;12 }

5秒后打印hello world!

  1 #include<stdio.h>2 #include<unistd.h>3 int main()4 {5     printf("hello\n");                                                                                                            6 7     sleep(5);8 9     printf(" world!\n");10 11     return 0;12 }

 先打印hello,5秒后打印 world!\n有刷新功能

  1 #include<stdio.h>2 #include<unistd.h>3 int main()4 {                                                                                                                               5     printf("hello");6 7     fflush(stdout);//将缓冲区的内容输出到设备中8 9     sleep(5);10 11     printf(" world!\n");12 13     return 0;14 }

先打印hello5秒后打印 world!

fflush()的作用是用来刷新缓冲区,fflush(stdin)刷新标准输入缓冲区,把输入缓冲区里的东西丢弃; fflush(stdout)刷新标准输出缓冲区,把输出缓冲区里的东西强制打印到标准输出设备上。

 1.3 fgets

fgets是C标准库中用于从文件或标准输入流中读取一行字符的函数,常用于处理字符串输入。它的主要作用是读取文件或标准输入中的一行,直到遇到换行符\n或达到指定的字符数为止。

char * fgets ( char * str, int num, FILE * stream );

参数

  • str:这是一个指向字符数组的指针,fgets将把读取的字符存储到这个数组中。
  • num:这是一个整数,表示最多读取的字符数(包括\0终止符)。即使没有读取到换行符,fgets也会在读取的字符数达到num-1时停止
  • stream:这是输入流,可以是文件流(如stdin、stdout)或者其他文件指针。

返回值:

  • 如果读取成功,fgets返回str,即指向读取数据的字符数组。
  • 如果发生错误或达到文件末尾,fgets返回NULL。

1.4 strtok

C语言字符函数和字符串函数-CSDN博客

可参考第11节中的strtok

1.5 getcwd

 getcwd是属于系统接口

#include <unistd.h>
char *getcwd(char *buf, size_t size);

getcwd()会将当前工作目录的绝对路径复制到参数buf所指的内存空间中,参数size为buf的空间大小。

如果getcwd函数执行失败,它将返回NULL

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main() {char cwd[1024];if (getcwd(cwd, sizeof(cwd))!= NULL) {printf("当前工作目录是: %s\n", cwd);} else {perror("获取当前工作目录出错");return 1;}return 0;
}

像pwd就是调用getcwd这个系统接口 

[zxw@hcss-ecs-cc58 myshell]$ pwd
/home/zxw/linux/112/lesson16/myshell

1.6 chdir 

chdir属于是系统接口

#include <unistd.h>
int chdir(const char *path//路径);

用于改变当前工作目录,其参数为Path 目标目录,可以是绝对目录或相对目录。

成功返回0,错误返回-1。

1.7 putenv

#include <stdlib.h>
int putenv(char *string);

函数说明:putenv()用来改变或增加环境变量的内容.

参数string 的格式为name=value, 如果该环境变量原先存在, 则变量内容会依 value 改变, 否则此参数内容会成为新的环境变量。

返回值:执行成功则返回0, 有错误发生则返回-1. 

 二、模拟Shell命令行解释器

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>using namespace std;const int basesize = 1024;
const int argvnum = 64;
const int envnum = 64;
// 全局的命令行参数表
char *gargv[argvnum];
int gargc = 0;using namespace std;
// 我系统的环境变量表
char *genv[envnum];// 全局的当前shell工作路径
char pwd[basesize];
char pwdenv[basesize];// 全局变量
int lastcode = 0;string GetUserName()
{string name = getenv("USER");return name.empty() ? "None" : name;
}string GetHostName()
{string hostname = getenv("HOSTNAME");return hostname.empty() ? "None" : hostname;
}string GetPwd()
{string pwd = getenv("PWD");return pwd.empty() ? "None" : pwd;// string pwd = getenv("PWD");// 获取当前pwdif(nullptr == getcwd(pwd,sizeof(pwd))) return "None";// 修改环境变量snprintf(pwdenv,sizeof(pwdenv),"PWD=%s",pwd);putenv(pwdenv);return pwd;
}string LastDir()
{string curr =GetPwd();if(curr == "/" || curr == "None") return curr;size_t pos = curr.rfind("/");if(pos == string::npos) return curr;return curr.substr(pos+1);
}// 1.命令行提示符
string MakeCommandLine()
{char command_line[basesize];snprintf(command_line,basesize,"[%s@%s %s]# ",GetUserName().c_str(),GetHostName().c_str(),GetPwd().c_str());GetUserName().c_str(),GetHostName().c_str(),LastDir().c_str());return command_line;
}void PrintCommandLine()
{printf("%s",MakeCommandLine().c_str());fflush(stdout);
}void debug()
{printf("argc:%d\n",gargc);for(int i = 0; gargv[i]; i++){printf("argv[%d]:%s\n",i,gargv[i]);}
}bool GetCommandLine(char command_buffer[],int size)// 2.获取用户命令
// 2.获取用户命令
bool GetCommandLine(char command_buffer[],int size)
{// 我们认为,我们要将用户输入的命令行,当成一个完整的字符串// "ls -a -l -n"char *result = fgets(command_buffer,size,stdin);if(!result){return false;}command_buffer[strlen(command_buffer)-1] = 0;if(strlen(command_buffer) == 0) return false;return true;
}//3.分析命令
void ParseCommandLine(char command_buffer[],int len)
{(void)len;memset(gargv, 0, sizeof(gargv));gargc = 0;// "ls -a -l -n"const char *sep = " ";gargv[gargc++] = strtok(command_buffer,sep);// =是刻意写的 ---》最后返回NULLwhile(gargv[gargc++] = strtok(nullptr,sep)); gargc--;
}void AddEnv(const char *item)
{int index = 0;while(genv[index]){index++;}genv[index] = (char*)malloc(strlen(item)+1);strncpy(genv[index],item,strlen(item)+1);genv[++index] = nullptr;}// shell自己执行命令,本质是shell调用自己的函数
bool CheckAndExecBuiltCommand()
{if(strcmp(gargv[0],"cd") == 0){// 内建命令没有创建子进程,自己执行if(gargc == 2){chdir(gargv[1]);lastcode = 0;}else{lastcode = 1;}return true;}else if(strcmp(gargv[0],"export") == 0){// export也是内建命令if(gargc == 2){AddEnv(gargv[1]);lastcode = 0;}else{lastcode = 2;}return true;}else if(strcmp(gargv[0],"env") == 0){for(int i = 0; genv[i]; i++){printf("%s\n",genv[i]);}lastcode = 0;return true;}else if(strcmp(gargv[0],"echo") == 0){if(gargc == 2){// echo $?// echo helloif(gargv[1][0] == '$'){if(gargv[1][1] == '?'){printf("%d\n",lastcode);lastcode = 0;}}else{printf("%s\n",gargv[1]);}}else{lastcode = 3;}return true;}return false;
}// 4.执行命令
// 在shell中
// 有些命令,必须由子进程来执行
// 有些命令,不能由子进程执行,要由shell自己执行bool ExecuteCommand()
{// 让子进程进行执行pid_t id = fork();if(id < 0){return false;}if(id == 0){//child//1.执行命令execvp(gargv[0],gargv);execvpe(gargv[0],gargv,genv);//2.退出exit(1);}int status = 0;pid_t rid = waitpid(id,&status,0);if(rid < 0)if(rid > 0){//DO Nothingif(WIFEXITED(status)){lastcode = WEXITSTATUS(status);}else{lastcode = 100;}return true;}return false;
}// shell自己执行命令,本质是shell调用自己的函数
bool CheckAndExecBuiltCommand()
// 作为一个shell,获取环境变量应该从系统的配置
// 我们今天就直接从父进程获取环境变量
void InitEnv()
{if(strcmp(gargv[0],"cd") == 0)extern char **environ;int index = 0;while(environ[index]){// 内建命令if(gargc == 2){chdir(gargv[1]);}return true;genv[index] = (char*)malloc(strlen(environ[index])+1);strncpy(genv[index],environ[index],strlen(environ[index]+1));index++;}return false;genv[index] = nullptr;
}int main()
{InitEnv();char command_buffer[basesize];while(true){PrintCommandLine();// 1.命令行提示符// command_line -> outputif(!GetCommandLine(command_buffer, basesize))// 2.获取用户命令{continue;}// printf("%s\n",command_buffer); //"ls -a -b" ---> "ls" "-a" "-b"ParseCommandLine(command_buffer,strlen(command_buffer));// 3.分析命令//debug();//debug();//检查 if(CheckAndExecBuiltCommand()){continue;}ExecuteCommand(); // 4.执行命令}return 0;
}

通过模拟实现了解环境变量也是单独申请了一块地址的,另外我们之前所学习的本地变量也是通过一个数组来维护的。也清楚的了解一些为什么要内建命令,不能单独fork子进程。

 

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

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

相关文章

云计算基础,虚拟化原理

文章目录 一、虚拟化1.1 什么是虚拟化1.2 虚拟化类型 二 、存储虚拟化2.1 存储指标2.2 存储类型2.3 存储协议2.4 RAID 三、内存 i/O虚拟化3.1 内存虚拟化基本概念地址空间转换原理内存共享与隔离原理 3.2 I/O 虚拟化基本概念模拟&#xff08;Emulation&#xff09;方式半虚拟化…

Vue3 + Vite + Electron + Ts 项目快速创建

一、创建 Vue 项目 1. 创建项目 pnpm create vite 2. 安装依赖 cd excel-electron pnpm install 3. 运行项目 pnpm dev 二、添加 Electron 1. 安装 electron pnpm add electron -D 2. 修改 package.json 添加入口 js 和执行命令。 {"main": "dist-ele…

pytest+allure 入门

使用allure如何生成自动化测试报​​​​​​告 &#xff1f;一文详解allure的使用 。_allure测试报告-CSDN博客 例子&#xff1a; import allure import pytest import osallure.epic("闹钟") allure.feature("闹钟增删") class TestSchedule():def setu…

新活动平台建设历程与架构演进

01 前言 历时近两年的重新设计和迭代重构&#xff0c;用户技术中心的新活动平台建设bilibili活动中台终于落地完成&#xff01;并迎来了里程碑时刻 —— 接过新老迭代的历史交接棒&#xff0c;从内到外、从开发到搭建实现全面升级&#xff0c;开启了活动生产工业化新时代&#…

文生图模型的技术原理、训练方案与微调方案

文生图模型的技术原理、训练方案与微调方案 引言 文生图(Text-to-Image)模型是一类能够根据文本描述生成对应图像的深度学习模型。近年来,随着生成对抗网络(GANs)和扩散模型(Diffusion Models)等技术的进步,文生图模型在图像生成领域取得了显著的进展。本文将详细介绍…

从CentOS到龙蜥:企业级Linux迁移实践记录(系统安装)

引言&#xff1a; 随着CentOS项目宣布停止维护CentOS 8并转向CentOS Stream&#xff0c;许多企业和组织面临着寻找可靠替代方案的挑战。在这个背景下&#xff0c;龙蜥操作系统&#xff08;OpenAnolis&#xff09;作为一个稳定、高性能且完全兼容的企业级Linux发行版&#xff0…

MR实战:IP地址去重

文章目录 1. 实战概述2. 提出任务2.1 原始问题2.2 简单化处理 3. 准备数据3.1 在云主机上创建文本文件3.2 上传文件到HDFS指定目录 4. 实现步骤4.1 创建Maven项目4.2 添加相关依赖4.3 创建日志属性文件4.4 创建网址去重映射器类4.5 创建网址去重归并器类4.6 创建网址去重驱动器…

STM32的存储结构

STM32F103 芯片是基于 ARM Cortex-M3 内核的微控制器&#xff0c;它集成了多种类型的存储器&#xff0c;每种存储器都有其特定的作用和存储对象。以下是关于 STM32F103 中 Flash、ROM 和 SRAM 的详细介绍&#xff1a; 1. Flash Memory (闪存) 作用&#xff1a;Flash 是非易失性…

AnaConda下载PyTorch慢的解决办法

使用Conda下载比较慢&#xff0c;改为pip下载 复制下载链接到迅雷下载 激活虚拟环境&#xff0c;安装whl&#xff0c;即可安装成功 pip install D:\openai.wiki\ChatGLM2-6B\torch-2.4.1cu121-cp38-cp38-win_amd64.whl

C++语言的文件操作

C语言的文件操作 在现代计算机程序设计中&#xff0c;文件操作是必不可少的一部分。无论是处理用户输入&#xff0c;数据存储&#xff0c;还是实现持久化&#xff0c;掌握文件操作都至关重要。本文将深入探讨C语言中的文件操作&#xff0c;包括文件的打开、关闭、读写、追加、…

3D目标检测数据集——kitti数据集

KITTI官网网址:The KITTI Vision Benchmark Suite 下载数据集:The KITTI Vision Benchmark Suite KITTI数据集论文:CMSY9 github可视化代码:GitHub - kuixu/kitti_object_vis: KITTI Object Visualization (Birdview, Volumetric LiDar point cloud )

Photoshop PS批处理操作教程(批量修改图片尺寸、参数等)

前言 ‌Photoshop批处理的主要作用‌是通过自动化处理一系列相似的操作来同时应用于多张图片&#xff0c;从而节省时间和精力&#xff0c;提高工作效率。批处理功能特别适用于需要批量处理的任务&#xff0c;如图像尺寸调整、颜色校正、水印添加等‌。 操作步骤 1.创建动作 …

Web渗透测试之XSS跨站脚本 防御[WAF]绕过手法

目录 XSS防御绕过汇总 参考这篇文章绕过 XSS payload XSS防御绕过汇总 服务端知道有网络攻击或者xss攻 Html 通过js代码 标签属性等手段进行一个过滤 不允许出现css的payload 前端过滤 我可以在抓包工具里面修改 抓包工具是不受前端的防御 也 就是浏览器 服务端过滤…

git提交

基本流程&#xff1a;新建分支 → 分支上开发(写代码) → 提交 → 合并到主分支 拉取最新代码因为当前在 master 分支下&#xff0c;你必须拉取最新代码&#xff0c;保证当前代码与线上同步&#xff08;最新&#xff09;&#xff0c;执行以下命令&#xff1a;bashgit pull orig…

adb端口转发

adb server 运行在 PC 端&#xff0c;监听 localhost:5037 端口的实现原理涉及 Socket 编程、进程管理、消息处理机制 以及 客户端-服务器架构&#xff08;Client-Server Architecture&#xff09;。其核心机制如下&#xff1a; 1. ADB 的三大核心组件 adb 采用 C/S 架构&…

Spring Boot 框架下的过滤器

1. 过滤器的基础概念 1.1 什么是过滤器&#xff1f; Servlet 规范的一部分&#xff0c;定义在 javax.servlet.Filter 接口中。在 HTTP 请求到达目标资源&#xff08;如 Controller&#xff09;之前或响应返回客户端之前&#xff0c;拦截并对其进行预处理或后处理。 1.2 过滤…

多云架构,JuiceFS 如何实现一致性与低延迟的数据分发

随着大模型的普及&#xff0c;GPU 算力成为稀缺资源&#xff0c;单一数据中心或云区域的 GPU 资源常常难以满足用户的全面需求。同时&#xff0c;跨地域团队的协作需求也推动了企业在不同云平台之间调度数据和计算任务。多云架构正逐渐成为一种趋势&#xff0c;然而该架构下的数…

【Git原理和使用】Git 分支管理(创建、切换、合并、删除、bug分支)

一、理解分支 我们可以把分支理解为一个分身&#xff0c;这个分身是与我们的主身是相互独立的&#xff0c;比如我们的主身在这个月学C&#xff0c;而分身在这个月学java&#xff0c;在一个月以后我们让分身与主身融合&#xff0c;这样主身在一个月内既学会了C&#xff0c;也学…

静态路由配置与调试——计算机网络实训day1

文章目录 操作前准备一、实验目的二、实验要求三、实验过程1、在R1和R2上配置设备名称。基本配置设备命名 2、在R1和R2上配置接口IP地址&#xff0c;并查看IP地址的配置情况。3、在R1和R2上配置静态路由&#xff0c;并查看路由表。静态路由缺省路由&#xff08;默认路由&#x…

农产品直播带货方案拆解

作为一名经验丰富的营销策划人道叔&#xff0c;今天我来拆解一下咱们4A营销广告圈的这份《直播天府川农好物带货方案》&#xff0c;让你能学到很多实用的策略和技巧&#xff0c;直接应用到你的策划工作中去。 首先&#xff0c;咱们看看背景分析。 助农直播现在可是个大热门&a…