以下是在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;
}