Linux 12mybash的实现

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

目录

核心概念

[fork 复制进程:](#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 函数均为其封装,需指定程序路径、参数数组、环境数组
bash 复制代码
// "/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):
cpp 复制代码
#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)绝对路径的标准库函数。

bash 复制代码
#include <unistd.h>

char *getcwd(char *buf, size_t size);
  • 参数说明:

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

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

gethostname()获取当前主机信息

颜色实现

cpp 复制代码
// 定义颜色和样式常量
#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")。

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

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函数中添加颜色。

cpp 复制代码
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进行分割

cpp 复制代码
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()函数等待子进程结束

cpp 复制代码
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);
}

完整代码:

cpp 复制代码
#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);
}
相关推荐
Android系统攻城狮3 小时前
Linux Gstreamer深度解析之gst_audio_encoder_set_frame_max调用流程与实战(六十一)
android·linux·运维·音视频·gstreamer音视频·音视频进阶·gstreamer音视频进阶
H.莓飛3 小时前
【C++】命名空间、缺省参数、函数重载与引用
linux·开发语言·c++·visual studio
91刘仁德3 小时前
IP协议详解:从IP协议头到网段划分、路由与NAT
linux·服务器·网络·网络协议·tcp/ip
硅基手札4 小时前
【Linux内核专栏 14】网络协议栈
linux·运维·网络协议
沫璃染墨6 小时前
《Linux工程实践篇(一):认识设计模式——从日志系统看策略模式》
linux·c++·安全·设计模式·策略模式
工作10年+,存储芯片行业6 小时前
Linux NVMe 中断排查与性能优化:CPU 亲和性
linux·运维·服务器·windows·性能优化·ssd·pcie
wuminyu6 小时前
ForkJoinPool内部WorkQueue的Lock-Free数组操作以及并发任务窃取原理剖析
java·linux·c语言·jvm·c++
她说彩礼65万7 小时前
C语言 堆区和栈区
java·linux·c语言
一号弯7 小时前
装完LINUX,请先新建日常用户
linux·运维·服务器
驭渊的小故事7 小时前
linux 基础命令 + git 仓库创建和配置命令
linux·git