一.项目简介
本项目旨在实现一个简易的linux shell命令行。我们将通过逐步分析和实现来构建这个shell,使其能够执行基本的命令行操作。
二.分析项目实现
实现一个shell需要循环以下过程:
- 获取命令行
- 解析命令行
- 建立一个子进程(fork)
- 替换子进程(execvp)
- 父进程等待子进程退出(wait)
三.逐步实现项目功能
1.获取命令行
我们将获取命令行设计为一个循环,除非用户主动退出,否则一直保持命令行接收指令的状态。具体实现逻辑如下:
int main(){ while(!quit){ // 2.交互问题,获取命令行内容 interact(commandline, sizeof(commandline)); <pre class="brush:php;toolbar:false"> // 3.分割命令字符串strtok(),解析命令行 int argc = splitstring(commandline, argv); if(argc == 0) continue; // 4.指令的判断 int n = buildCommand(argv, argc); // 5.普通命令的执行 if(!n) NormalExcute(argv); } return 0;
}
具体的获取命令行逻辑如下函数:
const char* getusername(){ // 通过getenv()获取环境变量中的用户名 return getenv("USER"); }</p><p>void getpwd(){ // 通过getcwd系统接口获取并更新pwd getcwd(pwd, sizeof(pwd)); }</p><p>void interact(char *cline, int size){ // 需要环境变量相关的系统调用函数来获取命令行提示信息 // 获取主机名 char hostname[64]; gethostname(hostname, sizeof(hostname));</p><pre class="brush:php;toolbar:false">// 1.打印bash命令行前面的提示信息 getpwd(); printf(LEFT"%s@%s %s"RIGHT""LABLE" ", getusername(), hostname, pwd); // 2.接收用户输入信息 fgets(cline, size, stdin); assert(cline != NULL); (void)cline; // 防止编译器报错定义而未使用的变量(假装用一下) cline[strlen(cline)-1] = ' ';
}
2.解析命令行
解析命令行主要是将获取到的字符串按空格切分开来放入一个新数组中。我们使用strtok()来完成这个工作,具体实现代码如下:
int splitstring(char cline[], char <em>_argv[]){ int i = 0; _argv[i++] = strtok(cline, DELIM); while(_argv[i++] = strtok(NULL, DELIM)); return i-1; }
3.指令的判断
虽然我们可以借助fork()创建子进程来实现诸多普通命令,但对于很多内建命令来说,创建子进程执行命令的结果并不会影响父进程,这会导致父进程命令无效。因此对于内建命令我们要先判断,再让父进程自主完成这些内建命令,代码如下:
int buildCommand(char </em>_argv[], int _argc){ // 4.指令的判断 // cd命令 if(_argc == 2 && strcmp(_argv[0], "cd") == 0){ // 更改目录 chdir(_argv[1]); getpwd(); // 更改环境变量 sprintf(getenv("PWD"), "%s", pwd); return 1; }</p><pre class="brush:php;toolbar:false">// export命令 else if(_argc == 2 && strcmp(_argv[0], "export") == 0){ // 因为_argv一直被我们用来存新的指令,环境变量会因此被覆盖 // 所以需要一个固定的存环境变量的地方来保存环境变量 strcpy(myenv, _argv[1]); putenv(myenv); return 1; } // echo命令 else if(_argc == 2 && strcmp(_argv[0], "echo") == 0){ if(strcmp(_argv[1], "$?") == 0){ printf("%dn", lastcode); lastcode = 0; } else if(*_argv[1] == '$'){ char *val = getenv(_argv[1]+1); if(val) printf("%sn", val); } else { printf("%sn", _argv[1]); } return 1; } // ls命令 if(strcmp(_argv[0], "ls") == 0){ _argv[_argc++] = "--color"; _argv[_argc] = NULL; } return 0;
}
4.普通命令的执行
普通命令的执行不会影响父进程,因此我们可以使用fork()创建子进程,然后使用exec系列进程替换函数来完成相关操作,代码如下:
void NormalExcute(char </em>_argv[]){ // 5.普通命令的执行 pid_t id = fork(); if(id < 0){ perror("fork"); exit(1); } else if(id == 0){ // 子进程 execvp(_argv[0], _argv); perror("execvp"); exit(1); } else{ // 父进程 int status; waitpid(id, &status, 0); if(WIFEXITED(status)){ lastcode = WEXITSTATUS(status); } } }
四.完整项目代码
完整项目代码如下:
#include<stdio.h></p><h1>include<stdlib.h></h1><h1>include<unistd.h></h1><h1>include<string.h></h1><h1>include<assert.h></h1><h1>include<unistd.h></h1><h1>include<sys/wait.h></h1><h1>include<sys/types.h></h1><h1>define LEFT "["</h1><h1>define RIGHT "]"</h1><h1>define LABLE "$"</h1><h1>define DELIM " t"</h1><h1>define LINE_SIZE 1024</h1><h1>define ARGC_SIZE 32</h1><h1>define EXIT_CODE 55</h1><p>int lastcode = 0; int quit = 0; char commandline[LINE_SIZE]; char *argv[ARGC_SIZE]; char pwd[LINE_SIZE]; // 自定义环境变量表,做成二维数组就需要维护了 char myenv[LINE_SIZE]; // 自定义本地变量表</p><p>const char* getusername(){ // 通过getenv()获取环境变量中的用户名 return getenv("USER"); }</p><p>void getpwd(){ // 通过getcwd系统接口获取并更新pwd getcwd(pwd, sizeof(pwd)); }</p><p>void interact(char *cline, int size){ // 1.打印bash命令行前面的提示信息 // 需要环境变量相关的系统调用函数来获取命令行提示信息 char hostname[64]; gethostname(hostname, sizeof(hostname)); getpwd(); printf(LEFT"%s@%s %s"RIGHT""LABLE" ", getusername(), hostname, pwd);</p><pre class="brush:php;toolbar:false">// 2.接收用户输入信息 fgets(cline, size, stdin); assert(cline != NULL); (void)cline; // 防止编译器报错定义而未使用的变量(假装用一下) cline[strlen(cline)-1] = ' ';
}
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[]){ // 5.普通命令的执行 pid_t id = fork(); if(id
结语
希望这篇关于在linux中实现一个简易的shell命令行的博客能对大家有所帮助,欢迎大佬们留言或私信与我交流。
学海漫浩浩,我亦苦作舟!关注我,大家一起学习,一起进步!