
目录
[1. 信息获取函数](#1. 信息获取函数)
[2. 命令行交互](#2. 命令行交互)
[3. 命令解析](#3. 命令解析)
[4. 普通命令执行](#4. 普通命令执行)
[5. 内置命令处理(核心功能)](#5. 内置命令处理(核心功能))
一、整体功能概述
这是一个增强版的命令行解释器,具有以下功能:
- 显示彩色命令行提示符
- 支持内置命令(cd、export、echo)
- 支持环境变量操作
- 记录上一条命令的退出状态
- 为 ls 命令自动添加颜色选项
函数准备
1.env命令
输出对应的环境变量

2.getenv()函数
这是一个获取操作系统环境变量的函数
所需参数:
cpp
#include<stdlib.h>
char *getenv(const char *name);//获取当前进程环境变量值
代码:

结果:

3.snprintf
snprintf 是一个安全版本的字符串格式化函数 ,用于将格式化的数据写入字符串缓冲区,同时防止缓冲区溢出。
函数参数:
cpp
int snprintf(char *str, size_t size, const char *format, ...);
参数说明:
str
:目标字符串缓冲区
size
:缓冲区大小(包括结尾的 null 字符)
format
:格式化字符串
...
:可变参数列表
返回值:
成功:返回想要写入的字符数(不包括结尾的 null)
失败:返回负值
例子:
cpp
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
例子:
cpp
char str[] = "apple,banana,cherry";
char *token = strtok(str, ",");
while (token != NULL) {
printf("Token: %s\n", token);
token = strtok(NULL, ",");
}
结果:
cpp
Token: apple
Token: banana
Token: cherry
二、头文件和宏定义
cpp
#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 // 子进程退出码
三、全局变量
cpp
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. 信息获取函数
cpp
const char *getusername() // 获取用户名
{
return getenv("USER"); // 从环境变量获取
}
const char *gethostname() // 获取主机名
{
return getenv("HOSTNAME"); // 从环境变量获取
}
void getpwd() // 获取当前工作目录
{
getcwd(pwd, sizeof(pwd)); // 系统调用获取当前目录
}
2. 命令行交互
cpp
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. 命令解析
cpp
int splitstring(char cline[], char *_argv[])
{
int i = 0;
argv[i++] = strtok(cline, DELIM); // 分割第一个参数
while(_argv[i++] = strtok(NULL, DELIM)); // 继续分割剩余参数
return i - 1; // 返回参数个数
}
4. 普通命令执行
cpp
void NormalExcute(char *_argv[])
{
pid_t id = fork(); // 创建子进程
if(id < 0){
perror("fork");
return;
}
else if(id == 0){
// 子进程执行命令
execvp(_argv[0], _argv); // 自动搜索PATH
exit(EXIT_CODE); // 执行失败退出
}
else{
// 父进程等待子进程
int status = 0;
pid_t rid = waitpid(id, &status, 0);
if(rid == id) {
lastcode = WEXITSTATUS(status); // 记录退出码
}
}
}
5. 内置命令处理(核心功能)
cpp
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
}
五、主函数流程
cpp
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;
}
六、总代码
cpp
#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 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];
// 自定义本地变量表
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;
}
// 特殊处理一下ls
if(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.txt
interact(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;
}
