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:缓冲区的大小(字节数)。若 bufNULLsize 需设为 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")。

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

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

返回值:

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

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

  • 首次调用时,strtokstr 开头跳过连续分隔符,找到第一个非分隔符字符(子串的起始位置)。
  • 继续向后扫描,遇到第一个分隔符时,将该分隔符替换为 \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);
}
相关推荐
一叶知秋yyds8 小时前
Centos 安装 Docker教程
linux·docker·centos
fie88898 小时前
在CentOS 7上集成cJSON库的方法
linux·运维·centos
带土18 小时前
5. Unix/Linux 系统常用类型别名清单
linux·unix
爱吃橘的橘猫8 小时前
如何解决VMware虚拟机中Linux系统终端不显示ens33 inet IP地址的问题
linux·运维·服务器·虚拟机
梁正雄9 小时前
linux服务-Bonding网卡绑定工具
linux·运维·linux bonding·网卡绑定
云边有个稻草人9 小时前
Windows 里用 Linux 不卡顿?WSL + cpolar让跨系统开发变简单
linux·运维·服务器·cpolar
打不了嗝 ᥬ᭄9 小时前
【Linux】网络层协议
linux·网络·c++·网络协议·http
LXY_BUAA10 小时前
将linux操作系统装入U盘20251107
linux·运维·服务器
kaoa00011 小时前
Linux入门攻坚——53、drbd - Distribute Replicated Block Device,分布式复制块设备-2
linux·运维·服务器
落羽的落羽11 小时前
【C++】现代C++的新特性constexpr,及其在C++14、C++17、C++20中的进化
linux·c++·人工智能·学习·机器学习·c++20·c++40周年