以下是在Linux操作系统 centos7版本下实现的shell ,该shell具备bash的基础功能,无上下键输入历史命令功能,删除字符或命令时按住Ctrl + Back
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<fcntl.h>
#include<errno.h>
#define COMMAND_SIZE 512
#define ARGV_SIZE 32
#define SPACE " "
#define ZERO '\0'
#define NOMODE 0
#define WRITE_TRUNC 1
#define WRITE_APPEND 2
#define READ 3
#define ENDPATH(p) do{ p += strlen(p)-1; while(*p != '/')p--; }while(0)char cwd[COMMAND_SIZE * 2];
char oldcwd[COMMAND_SIZE * 2];
char* argv[ARGV_SIZE];
int errorcode = 0;
const char* filename;
const char* GetUser() //获取相应的环境变量内容,下同
{const char* ret = getenv("USER");if(ret == NULL)return "None";return ret;
}const char* GetHostName()
{const char* ret = getenv("HOSTNAME");if(ret == NULL)return "None";return ret;
}const char* GetHome()
{const char* ret = getenv("HOME");if(ret == NULL)return "/";return ret;
}const char* GetPwd()
{const char* ret = getenv("PWD");if(ret == NULL)return "None";return ret;
}const char* GetOldPwd()
{const char* ret = getenv("OLDPWD");if(ret == NULL)return "None";return ret;
}
void ShowUserCommand()
{char commandline[COMMAND_SIZE]; //定义一个缓冲区,用来存放命令行提示符const char* lineuser = GetUser();//获取用户名const char* linehostname = GetHostName(); //获取家目录名称const char* linepwd = GetPwd(); //获取当前工作路径ENDPATH(linepwd); //得到当前的工作路径后,取出最后一个目录,用宏函数实现linepwd++;linepwd = strlen(linepwd) == 0 ? "/" : (strcmp(linepwd, lineuser) == 0 ? "~" : linepwd);snprintf( commandline, COMMAND_SIZE, "[%s@%s:%s]>> ", lineuser, linehostname, line);// 如果长度为0表示当前工作路径为根目录,打印"/",如果当前工作路径是家目录,打印"~"printf("%s", commandline); //将缓冲区的内容打印到显示器fflush(stdout);
}int SplitCommand( char* tmp)
{//将tmp中的指令切分,读入到命令行参数表argv里int ret = NOMODE;argv[0] = strtok(tmp,SPACE);int i = 1;while((argv[i] = strtok(NULL, SPACE))){//如果指令中含有 ">>", ">","<"等与重定向相关的字符,则设置返回值ret,//对应后续相应的打开文件方式if(ret != NOMODE){i++;continue;}if(strcmp(argv[i], ">>") == 0){ret = WRITE_APPEND;filename = strtok(NULL, SPACE); //若有重定向则更新filename,为后面//的打开文件做准备,下同}else if(argv[i][0] == '>'){if(strlen(argv[i]) == 1){ret = WRITE_TRUNC;filename = strtok(NULL, SPACE);}else if(argv[i][1] == '>'){ret = WRITE_APPEND;filename = argv[i] + 2;}else {ret = WRITE_TRUNC;filename = argv[i] + 1;}}else if(argv[i][0] == '<'){ret = READ;if(strlen(argv[i]) == 1){filename = strtok(NULL, SPACE);}else {filename = argv[i] + 1;}}else {i++;}}argv[i] = NULL;return ret;
}int GetUserCommand(char* usercommand, size_t size)
{//将一整行输入读取到我们定义的缓冲区 usercommand中,进行后续的解析if(NULL == fgets(usercommand, size, stdin)){return 0;}usercommand[strlen(usercommand) - 1] = ZERO;return strlen(usercommand);//返回缓冲区长度,是0表示没有读到指令
}void Die()
{exit(1);
}
void ChangeFile(int mod)
{int fd = 0;if(mod == 0) return ;if(mod == 1 ){fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666); //打开重定向的文件,使用相应的模式打开dup2(fd,1); //将文件描述符为1(标准输出文件)的文件重定向为fd,即我们打开的文件,下同}else if(mod == 2){fd = open(filename, O_WRONLY | O_CREAT | O_APPEND, 0666);dup2(fd,1);}else {fd = open(filename, O_RDONLY);dup2(fd,0);}
}
void RunUserCommand(int mod)
{pid_t id = fork(); //执行指令,为子进程运行指令的可执行程序,//父进程为shell程序,不能被影响if(id == 0) //返回的id为0,为子进程{ChangeFile(mod); //以mod方式打开文件execvp(argv[0], argv); //进程替换,替换为要执行的指令exit(errno);}else if(id > 0) //父进程{int status;if(id == waitpid(id, &status, 0) ) //父进程等待子进程,子进程结束后回收,{ //并且记录退出信息statuserrorcode = WEXITSTATUS(status); //获取退出码if(errorcode != 0) //错误码不为0,则子进程执行异常,打印异常信息printf("No success:%s:%d\n", strerror(errorcode), errorcode);}}else {Die(); //创建进程失败,退出shell}
}void Cd()
{const char* path = argv[1]; //用path记录要进入的目录int falg = 0;if(path == NULL ||strcmp(path, "~") == 0 ) path = GetHome(); //如果用户没有指定目录,或者目录为"~"就进入家目录if(strcmp(path,"-") == 0 ){falg = 1;path = GetOldPwd(); //如果用户要进入的目录为 - ,则为最近一次进入的目录,//环境变量oldpwd会记录最近一次进入的目录}char tmp[COMMAND_SIZE * 2];getcwd(tmp,sizeof(tmp)); //将当前工作路径保存,更改路径后用于更新oldpwdif( -1 == chdir(path)) //更改工作路径为path{errorcode = errno;printf("No success:%s:%d\n", strerror(errorcode), errorcode);return;}snprintf(oldcwd, sizeof(oldcwd), "OLDPWD=%s", tmp); //在缓冲区更新环境变量OLDPWDgetcwd(tmp,sizeof(tmp)); //获取更改后的工作路径,//用于更新环境变量PWDsnprintf(cwd, sizeof(cwd), "PWD=%s", tmp); //在缓冲区更新环境变量PWDputenv(cwd); //更新环境变量PWDputenv(oldcwd); //更新环境变量OLDPWDif(falg) printf("%s\n", tmp);
}int CheckBuildin(int mod)
{//是内建命令就执行,并且mod不为0时,设置相应的重定向内容int ret = 0;if(strcmp(argv[0],"cd") == 0 ){ret = 1;Cd();}else if(strcmp(argv[0],"exit") == 0 ) //用户输入exit就退出shell程序{ret = 1;exit(0);}else if(strcmp(argv[0], "echo") == 0 && strcmp(argv[1], "$?") == 0 ) //echo $?打印退出码,若有重定向则更换file的指向{ret = 1;FILE* file = stdout;if(mod == 1)file = fopen(filename, "w");else if(mod == 2){file = fopen(filename, "a");}fprintf(file, "%d\n", errorcode);errorcode = 0; //更新退出码if(mod != 0)fclose(file);}return ret;
}int main()
{int quit = 1;while(quit){//打印命令行提示符ShowUserCommand();//读取命令行参数char usercommand[ARGV_SIZE * 2];int n = GetUserCommand(usercommand,sizeof(usercommand));if(n < 0) return 1;if(n == 0) continue;//切分命令行参数int mod = SplitCommand(usercommand);//判断是否是内建命令n = CheckBuildin(mod);//执行命令if(n == 0)RunUserCommand(mod);}return 0;
}