目錄
一、整體功能概述
函數準備
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;
}