当前位置: 首页 > news >正文

Linux 12mybash的实现

该文章将讲解在linux终端下实现自己的终端命令解释器

目录

核心概念

fork 复制进程:

exec进程替换:

获取用户信息:

getuid() 

getpwuid(uid):

getcwd() 

gethostname();

颜色实现

字符串分割函数strtok()

mybash实现步骤

1.创建打印命令终端提示符

2.键盘输入信息分解函数

3.主函数实现

完整代码:


在实现之前我们需要先了解如下概念:

核心概念

fork 复制进程:

核心:

1.fork 函数会新生成一个进程,调用 fork 函数的进程为父进程,新生成的进程为子进程。在父进程中返回子进程的 pid,在子进程中返回 0,失败返回-1。

2.子进程会复制父进程的几乎所有资源(代码段、数据段、堆、栈、文件描述符表、信号处理方式等)。

3.若父进程未调用 wait() 或 waitpid() 回收子进程,子进程终止后会成为僵尸进程(保留 PID 和退出状态),需通过信号或进程管理机制避免。

具体想要学习fork可以参考L8-fotk进程复制

exec进程替换:

核心:

1.exec进程替换是指一个正在运行的进程,通过特定系统调用加载并执行新的程序代码,完全替换当前进程的代码段、数据段、堆和栈,仅保留进程 ID(PID)等核心属性。

2.进程替换调用函数

系统调用功能描述
execl以列表形式传递命令参数(参数个数固定)
execlp与 execl 类似,但会在 PATH 环境变量中搜索可执行文件
execle以列表形式传递参数,并显式指定环境变量
execv以数组形式传递命令参数(参数个数可变)
execvp与 execv 类似,会在 PATH 环境变量中搜索可执行文件
execvpe以数组形式传递参数,显式指定环境变量,且支持 PATH 搜索
execve最底层的系统调用,其他 exec 函数均为其封装,需指定程序路径、参数数组、环境数组
// "/usr/bin/ps",新程序的绝对路径或相对路径(必须明确指定路径)。
//"ps""-f"第一个参数通常是程序名(与 path 对应),后续是实际参数,最后以 (char*)0 结束。execl("/usr/bin/ps","ps","-f",(char*)0);
//同execl一样 path"ps"会自动从环境变量 PATH 中查找程序,无需指定完整路径。execlp("ps","ps","-f",(char*)0);
//允许自定义新程序的环境变量,而非使用当前进程的环境变量。程序的完整路径(同 execl)。
//最后一个参数 envp:自定义的环境变量数组(格式为 {"KEY=VALUE", ..., (char*)0})。char*myenvp[]={"KEY=VALUE",0};execle(path,"ps","-f",(char*)0,envp);
//path:程序的完整路径(同 execl)。
//argv:参数数组,格式为 {"程序名", "参数1", ..., (char*)0}。char*myargv[]={"ps","-f",0};execv(path,myargv);
//结合了 v(数组传参)和 p(自动查 PATH)的特性。
//file:程序名(从 PATH 中查找)。
//myargv:参数数组(同 execv)execvp(file,myargv);
//最底层的进程替换函数 
//"usr/bin/ps/"第一个参数为可执行文件的路径(绝对路径或相对路径)
//myargv命令参数数组(如 ["ls", "-l", NULL]),数组末尾必须以 NULL 结束。
//envp环境变量数组execve("/usr/bin/ps",myargv,envp);

 具体想要学习exec可以参考LIUNX 11进程替换

扩展:

获取用户信息:

getuid() 

getuid() 是 Unix/Linux 系统中用于获取当前进程实际用户 ID(UID) 的系统调用函数,定义在 <unistd.h> 头文件中。实际用户 ID 用于标识进程的所有者,是系统进行权限检查的重要依据。

UID 是系统中用于唯一标识用户的整数:

0:超级用户(root),拥有系统最高权限。

1~999:系统用户(如 daemon、ftp 等,用于运行系统服务)。

1000+:普通用户(由管理员创建的用户账号)。

getpwuid(uid):
#include<sys/types.h>
#include<pwd.h>
struct passwd *getpwuid(uid_t uid);
成功:返回指向 struct passwd 结构体的指针(该结构体由系统静态分配,后续调用可能覆盖内容)。
失败:返回 NULL,并设置 errno(如 UID 不存在时,errno 为 ESRCH)。

getcwd() 

getcwd()是 C 语言中用于获取当前工作目录(Current Working Directory)绝对路径的标准库函数。

#include <unistd.h>char *getcwd(char *buf, size_t size);
  • 参数说明

    • buf:用于存储当前目录路径的缓冲区。若为 NULL,函数会自动分配足够大小的内存。
    • size:缓冲区的大小(字节数)。若 buf 为 NULLsize 需设为 0
  • 返回值

    • 成功:返回指向存储路径的缓冲区指针(与 buf 相同,或函数分配的内存)。
    • 失败:返回 NULL,并设置 errno(如路径过长、权限不足)
gethostname();

gethostname()获取当前主机信息

颜色实现

// 定义颜色和样式常量
#define RESET "\033[0m"
#define RED "\033[31m"
#define GREEN "\033[32m"
#define BOLD "\033[1m"
#define YELLOW_BACKGROUND "\033[43m"
#define BLUE "\033[34m"

字符串分割函数strtok()

在 C 语言中,strtok(string tokenize,字符串分割)是用于按指定分隔符拆分字符串的标准库函数,定义在 <string.h> 头文件中,广泛用于解析命令行参数、日志文件、CSV 数据等场景。其核心特点是通过 “替换分隔符为字符串结束符 \0” 实现拆分,并通过静态变量记录分割位置,支持多轮分割。

char *strtok(char *str, const char *delim);

str:待分割的字符串。

首次调用时:传入完整的待分割字符串(如 "a,b;c d")。

后续调用时:传入 NULLstrtok 会通过内部静态变量读取上一次分割的 “中断位置”,继续分割剩余字符串。

delim:分隔符集合(字符串形式),例如 ",; " 表示 “逗号、分号、空格” 均为分隔符。

 返回值:

成功:返回当前拆分出的 “子串(token)” 的指针。

失败 / 分割结束:返回 NULL(表示已无更多子串可拆分)。

  • 首次调用时,strtok 从 str 开头跳过连续分隔符,找到第一个非分隔符字符(子串的起始位置)。
  • 继续向后扫描,遇到第一个分隔符时,将该分隔符替换为 \0(使当前子串终止),并记录下一个字符的位置(供后续调用使用)。
  • 后续调用传入 NULL 时,strtok 从上次记录的位置继续重复上述过程,直到字符串末尾。

mybash实现步骤

1.创建打印命令终端提示符

实现结果:

我们封装一个Print函数用来实现终端打印函数

first:我们使用getuid函数获取当前实际用户UID并用char*变量a识别是普通用户还是超级管理员

second:用struct passwd* ptr接受当前用户UID的各种信息,用getpwuid函数实现

third:使用getcwd函数实现获取当前目录路径

forth:使用gethostname获取当前主机信息

fifth:在printf函数中添加颜色。

void Print(){
int uid=getuid();char*a="$";if(uid==0){a="#";}struct passwd*ptr=getpwuid(uid);if(ptr==NULL){printf("$");fflush(stdout);return;}char fixed_buf[1024];if (getcwd(fixed_buf, sizeof(fixed_buf)) == NULL) {perror("固定缓冲区获取目录失败(可能路径过长)");return 1;}char hostname[128];gethostname(hostname,127);printf("%s%s%s%s:~%s$%s%s%s ",BOLD,GREEN,ptr->pw_name,hostname,BLUE,fixed_buf,RESET,a);fflush(stdout);
}

2.键盘输入信息分解函数

first:buff为键盘输入函数,s为需要存入的参数数组

second:使用strtok进行分割

char* get_str(char*buff,char**s){if(buff==NULL||s==NULL){return NULL;}int i=0;char*a=strtok(buff," ");while(a!=NULL){s[i++]=a;a=strtok(NULL," ");}return s[0];
}

3.主函数实现

first:我们从键盘中输入需要执行的命令并将其保存在buff中,并将输入的\n换成\0

second:我们利用进程替换函数execvp只需要路径名和参数数组即可

third:我们利用自己实现的字符串分割函数,用get_str函数获取路径名,参数列表保存在myargv中

forth:使用fork创建新进程,pid==0execvp替换

fifth:使用wait()函数等待子进程结束

int main(){while(1){Print();char buff[128]={0};fgets(buff,127,stdin);buff[strlen(buff)-1]=0;if(buff[0]=='\0'){printf("\n");continue;}char*myargv[128]={0};char*cwd=get_str(buff,myargv);pid_t pid=fork();if(pid==-1){perror("pid err");exit(1);
}if(pid==0){execvp(cwd,myargv);perror("exec err");exit(0);}wait(NULL);
}exit(0);
}

完整代码:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<sys/types.h>
#include<pwd.h>
#include<sys/wait.h>
// 定义颜色和样式常量
#define RESET "\033[0m"
#define RED "\033[31m"
#define GREEN "\033[32m"
#define BOLD "\033[1m"
#define YELLOW_BACKGROUND "\033[43m"
#define BLUE "\033[34m"
char* get_str(char*buff,char**s){if(buff==NULL||s==NULL){return NULL;}int i=0;char*a=strtok(buff," ");while(a!=NULL){s[i++]=a;a=strtok(NULL," ");}return s[0];
}
void Print(){int uid=getuid();char*a="$";if(uid==0){a="#";}struct passwd*ptr=getpwuid(uid);if(ptr==NULL){printf("$");fflush(stdout);return;}char fixed_buf[1024];if (getcwd(fixed_buf, sizeof(fixed_buf)) == NULL) {perror("固定缓冲区获取目录失败(可能路径过长)");return 1;}char hostname[128];gethostname(hostname,127);printf("%s%s%s%s:~%s$%s%s%s ",BOLD,GREEN,ptr->pw_name,hostname,BLUE,fixed_buf,RESET,a);fflush(stdout);
}
int main(){while(1){Print();char buff[128]={0};fgets(buff,127,stdin);buff[strlen(buff)-1]=0;if(buff[0]=='\0'){printf("\n");continue;}char*myargv[128]={0};char*cwd=get_str(buff,myargv);pid_t pid=fork();if(pid==-1){perror("pid err");exit(1);
}if(pid==0){execvp(cwd,myargv);perror("exec err");exit(0);}wait(NULL);
}exit(0);
}

http://www.dtcms.com/a/473927.html

相关文章:

  • K8s YAML 文件详解:从语法到实战编写指南
  • 社区版Idea怎么创建Spring Boot项目?Selected Java version 17 is not supported. 问题解决
  • 益阳市 网站建设电子商务网站建设的主要风险
  • SpringBootRemotePowershellAdmin:开箱即用的 Windows远程运维开源工具
  • 插槽vue/react
  • 对vue生命周期的理解
  • 2017民非单位年检那个网站做黄山旅游攻略景点必去
  • [笔记 自用]CAN总线通信配置
  • HTML 教程
  • 用自己服务器做网站用备案怎样在亚马逊网上开店
  • PHP操作elasticsearch7.8
  • 学校网站建设需求分析哪个小说网站可以做封面
  • 网站制作类软件推荐莆田网站格在哪里做
  • TypeScript 面试题及详细答案 100题 (21-30)-- 接口(Interface)
  • 承德网站新手怎么做网络推广
  • 6. 从0到上线:.NET 8 + ML.NET LTR 智能类目匹配实战--渐进式学习闭环:从反馈到再训练
  • 2.c++面向对象(五)
  • python中的一些运算符
  • 【嵌入式面试题】boss收集的11道,持续更新中
  • 保证样式稿高度还原
  • 网站建设 源码怎么注册公司名
  • [xboard] 34 buildroot 的overlay机制
  • 某公司站点的挖掘实战分享
  • 第三方和审核场景回调还是主动查询
  • Git基本命令的使用(超详细)
  • NC40 链表相加(二)
  • 网安面试题收集(3)
  • JetLinks设备接入的认识与理解
  • 从HashMap到ConcurrentHashMap深入剖析Java并发容器的演进与实战
  • 做一组静态页面网站多少钱网站源码上传到哪个文件夹