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

简易shell

目录

一、整体功能概述

函数准备

1.env命令

2.getenv()函数

3.snprintf

4.strtok()函数

三、全局变量

四、核心功能函数解析

1. 信息获取函数

2. 命令行交互

3. 命令解析

4. 普通命令执行

5. 内置命令处理(核心功能)

五、主函数流程

六、总代码


一、整体功能概述

这是一个增强版的命令行解释器,具有以下功能:

  • 显示彩色命令行提示符
  • 支持内置命令(cd、export、echo)
  • 支持环境变量操作
  • 记录上一条命令的退出状态
  • 为 ls 命令自动添加颜色选项

函数准备

1.env命令

输出对应的环境变量

2.getenv()函数

这是一个获取操作系统环境变量的函数

所需参数:

#include<stdlib.h>
char *getenv(const char *name);//获取当前进程环境变量值

代码:

结果:

3.snprintf

snprintf 是一个安全版本的字符串格式化函数,用于将格式化的数据写入字符串缓冲区,同时防止缓冲区溢出。

函数参数:

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

参数说明:

str:目标字符串缓冲区

size:缓冲区大小(包括结尾的 null 字符)

format:格式化字符串

...:可变参数列表

返回值:

成功:返回想要写入的字符数(不包括结尾的 null)

失败:返回负值

例子:

char buffer[50];
int n = snprintf(buffer, sizeof(buffer), "Hello, %s!", "World");
// buffer = "Hello, World!"
// n = 13 (实际写入的字符数)

4.strtok()函数

strtok 是 C 标准库中的一个字符串分割函数,用于将字符串按指定的分隔符拆分为多个子串。该函数属于<string.h>头文件,常用于解析字符串数据

参数说明:

str:要分割的字符串(第一次调用时传入,后续传入 NULL)

delim:分隔符字符串(包含所有可能的分隔字符)

返回值:

成功:返回下一个令牌的指针

失败/结束:返回 NULL

例子:

char str[] = "apple,banana,cherry";
char *token = strtok(str, ",");while (token != NULL) {printf("Token: %s\n", token);token = strtok(NULL, ",");
}

结果:

Token: apple
Token: banana
Token: cherry

二、头文件和宏定义

#include <stdio.h>       // 标准输入输出
#include <stdlib.h>      // 标准库函数
#include <string.h>      // 字符串处理
#include <assert.h>      // 断言
#include <unistd.h>      // Unix标准函数
#include <sys/types.h>   // 系统类型
#include <sys/wait.h>    // 进程等待#define LEFT "["         // 提示符左括号
#define RIGHT "]"        // 提示符右括号
#define LABLE "#"        // 提示符标签
#define DELIM " \t"      // 命令分隔符(空格和制表符)
#define LINE_SIZE 1024   // 命令行缓冲区大小
#define ARGC_SIZE 32     // 参数数组大小
#define EXIT_CODE 44     // 子进程退出码

三、全局变量

int lastcode = 0;        // 上一条命令的退出码
int quit = 0;            // 退出标志
extern char **environ;   // 系统环境变量
char commandline[LINE_SIZE]; // 命令行输入缓冲区
char *argv[ARGC_SIZE];   // 参数数组
char pwd[LINE_SIZE];     // 当前工作目录缓冲区char myenv[LINE_SIZE];   // 自定义环境变量存储

四、核心功能函数解析

1. 信息获取函数

const char *getusername() // 获取用户名
{return getenv("USER"); // 从环境变量获取
}const char *gethostname() // 获取主机名
{return getenv("HOSTNAME"); // 从环境变量获取
}void getpwd() // 获取当前工作目录
{getcwd(pwd, sizeof(pwd)); // 系统调用获取当前目录
}

2. 命令行交互

void interact(char *cline, int size)
{getpwd(); // 更新当前目录// 显示提示符:[user@host directory]#printf(LEFT"%s@%s %s"RIGHT""LABLE" ", getusername(), gethostname(), pwd);char *s = fgets(cline, size, stdin); // 读取用户输入assert(s); // 确保读取成功cline[strlen(cline)-1] = '\0'; // 去掉换行符
}

3. 命令解析

int splitstring(char cline[], char *_argv[])
{int i = 0;argv[i++] = strtok(cline, DELIM); // 分割第一个参数while(_argv[i++] = strtok(NULL, DELIM)); // 继续分割剩余参数return i - 1; // 返回参数个数
}

4. 普通命令执行

void NormalExcute(char *_argv[])
{pid_t id = fork(); // 创建子进程if(id < 0){perror("fork");return;}else if(id == 0){// 子进程执行命令execvp(_argv[0], _argv); // 自动搜索PATHexit(EXIT_CODE); // 执行失败退出}else{// 父进程等待子进程int status = 0;pid_t rid = waitpid(id, &status, 0);if(rid == id) {lastcode = WEXITSTATUS(status); // 记录退出码}}
}

5. 内置命令处理(核心功能)

int buildCommand(char *_argv[], int _argc)
{// cd 命令:切换目录if(_argc == 2 && strcmp(_argv[0], "cd") == 0){chdir(argv[1]); // 切换目录getpwd(); // 更新当前目录sprintf(getenv("PWD"), "%s", pwd); // 更新PWD环境变量return 1; // 表示是内置命令,不需要执行NormalExcute}// export 命令:设置环境变量else if(_argc == 2 && strcmp(_argv[0], "export") == 0){strcpy(myenv, _argv[1]); // 复制到全局变量putenv(myenv); // 设置环境变量return 1;}// echo 命令:输出内容else if(_argc == 2 && strcmp(_argv[0], "echo") == 0){if(strcmp(_argv[1], "$?") == 0) {// 输出上一条命令的退出码printf("%d\n", lastcode);lastcode = 0;}else if(*_argv[1] == '$'){// 输出环境变量值char *val = getenv(_argv[1]+1);if(val) printf("%s\n", val);}else{// 直接输出字符串printf("%s\n", _argv[1]);}return 1;}// 为 ls 命令自动添加颜色选项if(strcmp(_argv[0], "ls") == 0) {_argv[_argc++] = "--color"; // 添加颜色参数_argv[_argc] = NULL; // 保持数组以NULL结束}return 0; // 不是内置命令,需要执行NormalExcute
}

五、主函数流程

int main()
{while(!quit){ // 主循环// 1. 显示提示符并获取命令interact(commandline, sizeof(commandline));// 2. 解析命令int argc = splitstring(commandline, argv);if(argc == 0) continue; // 空命令跳过// 3. 调试输出(注释状态)// for(int i = 0; argv[i]; i++) printf("[%d]: %s\n", i, argv[i]);// 4. 处理内置命令int n = buildCommand(argv, argc);// 5. 注释中描述了管道功能的实现思路// 这里预留了管道功能的框架// 6. 执行普通命令if(!n) NormalExcute(argv);}return 0;
}

六、总代码


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>#define LEFT "["
#define RIGHT "]"
#define LABLE "#"
#define DELIM " \t"
#define LINE_SIZE 1024
#define ARGC_SIZE 32
#define EXIT_CODE 44int lastcode = 0;
int quit = 0;
extern char **environ;
char commandline[LINE_SIZE];
char *argv[ARGC_SIZE];
char pwd[LINE_SIZE];// 自定义环境变量表
char myenv[LINE_SIZE];
// 自定义本地变量表const char *getusername()
{return getenv("USER");
}const char *gethostname()
{return getenv("HOSTNAME");
}void getpwd()
{getcwd(pwd, sizeof(pwd));
}void interact(char *cline, int size)
{getpwd();printf(LEFT"%s@%s %s"RIGHT""LABLE" ", getusername(), gethostname(), pwd);char *s = fgets(cline, size, stdin);assert(s);(void)s;// "abcd\n\0"cline[strlen(cline)-1] = '\0';
}// ls -a -l | wc -l | head 
int splitstring(char cline[], char *_argv[])
{int i = 0;argv[i++] = strtok(cline, DELIM);while(_argv[i++] = strtok(NULL, DELIM)); // 故意写的=return i - 1;
}void NormalExcute(char *_argv[])
{pid_t id = fork();if(id < 0){perror("fork");return;}else if(id == 0){//让子进程执行命令//execvpe(_argv[0], _argv, environ);execvp(_argv[0], _argv);exit(EXIT_CODE);}else{int status = 0;pid_t rid = waitpid(id, &status, 0);if(rid == id) {lastcode = WEXITSTATUS(status);}}
}int buildCommand(char *_argv[], int _argc)
{if(_argc == 2 && strcmp(_argv[0], "cd") == 0){chdir(argv[1]);getpwd();sprintf(getenv("PWD"), "%s", pwd);return 1;}else if(_argc == 2 && strcmp(_argv[0], "export") == 0){strcpy(myenv, _argv[1]);putenv(myenv);return 1;}else if(_argc == 2 && strcmp(_argv[0], "echo") == 0){if(strcmp(_argv[1], "$?") == 0){printf("%d\n", lastcode);lastcode=0;}else if(*_argv[1] == '$'){char *val = getenv(_argv[1]+1);if(val) printf("%s\n", val);}else{printf("%s\n", _argv[1]);}return 1;}// 特殊处理一下lsif(strcmp(_argv[0], "ls") == 0){_argv[_argc++] = "--color";_argv[_argc] = NULL;}return 0;
}int main()
{while(!quit){// 1.// 2. 交互问题,获取命令行, ls -a -l > myfile / ls -a -l >> myfile / cat < file.txtinteract(commandline, sizeof(commandline));// commandline -> "ls -a -l -n\0" -> "ls" "-a" "-l" "-n"// 3. 子串分割的问题,解析命令行int argc = splitstring(commandline, argv);if(argc == 0) continue;// 4. 指令的判断 // debug//for(int i = 0; argv[i]; i++) printf("[%d]: %s\n", i, argv[i]);//内键命令,本质就是一个shell内部的一个函数int n = buildCommand(argv, argc);// ls -a -l | wc -l// 4.0 分析输入的命令行字符串,获取有多少个|, 命令打散多个子命令字符串// 4.1 malloc申请空间,pipe先申请多个管道// 4.2 循环创建多个子进程,每一个子进程的重定向情况。最开始. 输出重定向, 1->指定的一个管道的写端 // 中间:输入输出重定向, 0标准输入重定向到上一个管道的读端 1标准输出重定向到下一个管道的写端// 最后一个:输入重定向,将标准输入重定向到最后一个管道的读端// 4.3 分别让不同的子进程执行不同的命令--- exec* --- exec*不会影响该进程曾经打开的文件,不会影响预先设置好的管道重定向// 5. 普通命令的执行if(!n) NormalExcute(argv);}return 0;
}

http://www.xdnf.cn/news/1372015.html

相关文章:

  • 【ElasticSearch】客户端选择
  • 力扣100+补充大完结
  • Linux命令详解+示例(炫彩超全)
  • 在Godot中为您的游戏添加并控制游戏角色的完整技术指南
  • IUV5G专网排障(上)
  • Markdown 编辑器 语法
  • 使用【阿里云百炼】搭建自己的大模型
  • 微服务-26.网关登录校验-OpenFeign传递用户信息
  • 半小时打造七夕传统文化网站:Qoder AI编程实战记录
  • 【HarmonyOS NEXT】打包鸿蒙应用并发布到应用市场
  • dapo:开源大规模llm强化学习系统的突破与实现
  • Spring Boot -Mybatis的使用和基础
  • 【图像处理 - 基础知识】ISP(Image Signal Processor)处理
  • 基于SpringBoot的社团管理系统【2026最新】
  • JVM线上调优参数配置指南
  • Powercat PowerShell工具:原理详解+使用方法+渗透实战
  • C语音初阶————指针2
  • 小范围疫情防控元胞自动机模拟matlab
  • 用 Allure 生成 pytest 测试报告:从安装到使用全流程
  • 【项目】深房数据通——深圳房价可视化系统
  • 数字时代下的智能信息传播引擎
  • Python大型数组计算完全指南:从基础到分布式系统实践
  • 简明 | ResNet特点、残差模块、残差映射理解摘要
  • Cherry-pick冲突与Git回滚
  • Nginx Ubuntu vs CentOS 常用命令对照表---详解笔记
  • 手机移动代理IP:使用、配置、维护的10问10答
  • 数据集数量与神经网络参数关系分析
  • 如果 我退休了
  • 身份管理与安全 (Protect identities)
  • Firefox Relay 体验