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);
}
相关推荐
WnHj3 小时前
带密码加密机制的自动 FTP 拉取脚本
linux·运维·服务器·网络
脑子缺根弦3 小时前
辉视融合服务器:强劲驱动电视信息发布,直播点播流畅运行,赋能高效传播
linux·运维·服务器
。TAT。3 小时前
Linux - 命令行参数与环境变量
linux·学习
zl_dfq3 小时前
Linux 之 【基本指令 与 shell命令以及运行原理】
linux
@卞3 小时前
从0到1:Linux 系统编程 --- 冯·诺依曼体系结构
linux·运维·服务器
杜子不疼.3 小时前
【Linux】操作系统上的进程状态及其转换
linux·ai
艾莉丝努力练剑4 小时前
【C++STL :list类 (二) 】list vs vector:终极对决与迭代器深度解析 && 揭秘list迭代器的陷阱与精髓
linux·开发语言·数据结构·c++·list
---学无止境---5 小时前
Linux中页面分配alloc_pages相关函数
linux
m0_380921806 小时前
容器虚拟化基础之LXC
linux