本学期的UNIX大作业是编写一个简单的命令解释器。做的比较简单
需求文档
//***************************************//
//Author: xxxxxxxx
//Date: 2014-6-15
//******Shell 命令编辑器***************//
1、大作业要求的基本功能分析
1)命令提示符的改变。通过自定义的环境变量pmpc,对其进行赋值进行提示符的变化。本次设计主要提供了3种命令提示符:'$'、'>'、'~';
2)通过某个特殊的命令或者按键组合可以正常关闭本程序。本设计通过了自定义指令:exit,来完成程序的退出,退出时有一提示:
'**************BYE!*************';
3)提供后台运行机制。本设计通过在指令前加上'bg'指令,完成指令后台的运行。主进程不需要等待当前指令完成,让其再后台运行,而可以直 接输出下一条指令输入的提示符;
4)提供输出重定向和输入重定向。本设计通过重定向符号'>','<'完成输入输出重定向的功能。
2、另外实现的功能分析
1)提供了Shell解释器版本的输出指令:myname;
2)重定向输出追加写到文件中:'>>';
3)历史命令的存储、显示和执行:存储addCmd()、printHis、! i(其中i表示第几条历史命令)。
详细设计文档
//***************************************//
//Author: xxxxxxxx
//Date: 2014-6-15
//******Shell 命令编辑器***************//
1、功能描述
1) cmd为命令名+参数形式的命令,<为输入重定向,后跟输入重定向文件名infileName;>或>>后跟输出重定向文件名,输出内容将写入outfileName,
>>表示以追加方式写入文件,>则表示以覆盖方式写入;
2) bg命令实现了后台运行机制;
3) 实现了cd命令;
4) exit为退出命令;
5) 自定义指令printHis,可以显示最近执行的命令,最多为10条;
6) ! i为执行历史命令,意为执行第i条历史命令。
7)自定义命令myname,将输出此Shell的版本信息
8)本解释器命令格式中的简单命令可为所有外部命令和一部分内部命令和自定义命令
2、主要数据结构
#define MAXSAVE 10 //历史cmd的数量
#define MAXARG 20 //命令参数的个数
#define MAXLEN 50 //参数的最大长度为49
#define STD_IN_OUT 0 //标准输入输入
#define TRUE 1
#define FALSE 0
#define RD_IN 1 //标准输入重定向
#define RD_OUT 2 //标准输出重定向
#define MYNAME "Program Name: Linux Shell\nAuthor :Wangxiang 2013110400\ndate :2014/6/15"//输出版本信息
int bg_flag = FALSE; //后台运行标记:FALSE,没有
int append = FALSE; //文件输出追加标记
int his_flag = FALSE; //历史命令输出标记
char* cmd[MAXARG]; //分析后的指令
char cmd_buffer[MAXLEN];//标准输入读入的指令
char historyCmd[MAXSAVE][MAXLEN];//历史指令
char* cmd_in = NULL;
char* infile = NULL; //重定向的输入文件
char* outfile = NULL; //重定向的输出文件
char* pmpc = "PS"; //改变提示符的环境变量
char* PSdollar = "dollar";
char* PSbracket = "bracket";
int io_type = STD_IN_OUT;//初始化重定向
char* out_signal = ">"; //输出重定向
char* out_signal_app = ">>";//输出追加
char* in_signal = "<"; //输入重定向
3、自定义函数功能及接口说明
void begin(); //欢迎界面
void prompt(); //提示符
如 [WX_Richard@ %s:] %c 其中%s部分是当前目录,%c是可以改变的命令提示符
void get_line(); //读取输入的指令
void parsecmd(char* cmd_in);//分析读入的指令
void addCmd(); //添加历史命令
void printHis(); //打印历史命令
4、功能的实现方法说明
1)命令提示符改变功能
提供了3种命令提示符(prompt_char),分别是:'$','>','~'。初始默认值是'~',其转变是通过定义了一个环境变量pmpc,当其为dollar时,prompt_char是'$',当其为bracket时,prompt_char是'>'。
2)重定向功能
本解释器使用复制文件描述符系统调用dup2来实现重定向,在分析读入的指令时,判断是否是重定向指令,改变重定向标示io_type。并将重定向的文件存入infile,outfile。其中io_type实现的原理如下:其是一个2位的2进制数,00,第一位为'1'时,表示要进行输出重定向,第二位为'1'时,表示要进行输入重定向。另外还增加了'>>',表示重定向以追加的方式输入文件,'>'以覆盖的方式写入文件
3)命令的后台运行
bg+指令,就可以实现指令的后台运行。设计了一个bg_flag,当分析读入的指令后,发现是bg运行指令,就打开其标示符,从而实现后台运行。
4)历史指令的存放以及调用
Linux自带的history命令是将历史命令写入文件中,维护了一个历史命令表。这里我做的比较简单,只是引入了一个全局变量,历史信息并不能持久化。分配了一个historyCmd[MAXSAVE][MAXLEN]的空间,最多存放10条指令,如果多于10条指令,则覆盖最早的那条。
若遇到自定义的指令'printHis'则调用printHis()输出所有记录的历史命令。同样的,遇到指令“!”命令的方法也比较简单了,只需根据!后面的序号在数组里查找相应的命令即可,然后使用execvp或自定义的处理过程执行相应命令。
5)其他简单的指令
其他简单命令全部使用系统调用execvp()实现。将输入的字符串处理成名字加参数形式后,将命令名作为第一个参数,将命令名和参数一起作为第二个参数调用execvp即可执行所有简单的外部命令和一部分内部命令。
6)其他自定义的简单指令
1>、版本信息的显示:myname
2>、退出指令:exit
附上源代码
//***************************************//
//Author: xxxxxxx
//Date: 2014-6-15
//******Shell 命令编辑器***************//
#include <stdio.h>
#include <malloc.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/wait.h>
#include <sys/fcntl.h>
#include <math.h>
#include <stdlib.h>
#define MAXSAVE 10 //历史cmd的数量
#define MAXARG 20 //命令参数的个数
#define MAXLEN 50 //参数的最大长度为49
#define STD_IN_OUT 0 //标准输入输入
#define TRUE 1
#define FALSE 0
#define RD_IN 1 //标准输入重定向
#define RD_OUT 2 //标准输出重定向
#define MYNAME "Program Name: Linux Shell\nAuthor :xxxxxx\ndate :2014/6/15\nVersion :v1.0.1\n"
int bg_flag = FALSE; //后台运行标记:FALSE,没有
int append = FALSE; //文件输出追加标记
int his_flag = FALSE; //历史命令输出标记
char* cmd[MAXARG]; //分析后的指令
char cmd_buffer[MAXLEN];//标准输入读入的指令
char historyCmd[MAXSAVE][MAXLEN];//历史指令
char* cmd_in = NULL;
char* infile = NULL; //重定向的输入文件
char* outfile = NULL; //重定向的输出文件
char* pmpc = "PS"; //改变提示符的环境变量
char* PSdollar = "dollar";
char* PSbracket = "bracket";
int io_type = STD_IN_OUT;//初始化重定向
char* out_signal = ">"; //输出重定向
char* out_signal_app = ">>";//输出追加
char* in_signal = "<"; //输入重定向
int hisNum = 0; //历史指令个数
void begin(); //欢迎界面
void prompt(); //提示符
void get_line(); //读取输入的指令
void parsecmd(char* cmd_in);//分析读入的指令
void addCmd(); //添加历史命令
void printHis(); //打印历史命令
int main(int argc,char* argv[])
{
begin();
while(1)
{
prompt(); //命令提示符
get_line(); //读取命令输入
addCmd();
parsecmd(cmd_buffer); //分析命令
bg_flag = FALSE;
if(strcmp(cmd[0], "exit") == 0)
{
printf("**************BYE!*************\n");
return 0;
}
else if(strcmp(cmd[0], "cd") == 0)
{
if(chdir(cmd[1]))
{
printf("This directory doesn't exist!\n");
}
continue;
}
else if(strcmp(cmd[0],"bg") == 0)
{
bg_flag = TRUE;
}
else if(strcmp(cmd[0],"printHis")==0)
{
if(hisNum>0) printHis();
else
printf("There is no history cmd");
continue;
}
else if(strcmp(cmd[0],"myname")==0)
{
printf("%s\n",MYNAME);
continue;
}
if(fork()==0)
{
if((io_type & RD_OUT)!=0)
{
int fd_out;
if(append == TRUE)
fd_out = open(outfile,O_WRONLY|O_CREAT|O_APPEND,0666);
else
fd_out = open(outfile,O_WRONLY|O_CREAT|O_TRUNC,0666);
dup2(fd_out,1);
close(fd_out);
}
if((io_type & RD_IN)!=0)
{
int fd_in;
fd_in = open(infile, O_RDONLY);
dup2(fd_in,0);
close(fd_in);
}
execvp(cmd[0+bg_flag],cmd + bg_flag);
return 1;
}
else
{
int cpid;
if(bg_flag != 1)
while((cpid = wait(NULL))>0);
}
}
return 0;
}
void begin()
{
char instruction[] = "*******Thanks for use*******\n";
printf("%s", instruction);
}
void prompt(){
char cwd[MAXLEN]; //当前的工作目录
char prompt_char = '~'; //命令提示符
char *envstr;
if((envstr =(char *) getenv(pmpc)) != NULL){
if(strcmp(envstr, PSdollar) == 0){
prompt_char = '$';
}else if(strcmp(envstr, PSbracket) == 0){
prompt_char = '>';
}
else
prompt_char = '~';
}
if(getcwd(cwd, sizeof(cwd)) != NULL){
printf("[WX_Richard@ %s:] %c ", cwd, prompt_char);
} else{
printf("Oops!Current working directory wrong\n");
}
}
void get_line()
{
fgets(cmd_buffer, MAXLEN, stdin);
cmd_buffer[strlen(cmd_buffer) - 1] = '\0';
}
void parsecmd(char* cmd_in)
{
char* delim = " ";//分割命令的标志
char* temp = NULL;
int i = 0;
int rd_in_flag = FALSE;
int rd_out_flag = FALSE;
io_type = STD_IN_OUT;
infile = NULL;
outfile = NULL;
while((temp = strsep(&cmd_in, delim)) != NULL) //以“ ”分割命令
{
if(strcmp(temp, "!") == 0)
{
his_flag = TRUE;
continue;
}
if(strcmp(temp, out_signal) == 0)
{
io_type = io_type | RD_OUT;
rd_out_flag = TRUE;
append = FALSE;
continue;
}
else if(strcmp(temp, in_signal) == 0)
{
io_type = io_type | RD_IN;
rd_in_flag = TRUE;
continue;
}
else if(strcmp(temp, out_signal_app) == 0)
{
io_type = io_type | RD_OUT;
rd_out_flag = TRUE;
append = TRUE;
continue;
}
if(rd_out_flag == TRUE)
{
outfile = temp;
rd_out_flag = FALSE;
continue;
}
else if(rd_in_flag == TRUE)
{
infile = temp;
rd_in_flag = FALSE;
continue;
}
cmd[i++] = temp;
if(his_flag)
{
cmd_in = historyCmd[atoi(cmd[0])-1];
strcpy(cmd_buffer , historyCmd[atoi(cmd[0])-1]);
i = 0;
his_flag = FALSE;
continue;
}
}
cmd[i] = NULL;
}
void addCmd()
{
if(hisNum < 10)
{
strcpy(historyCmd[hisNum],cmd_buffer);
hisNum++;
}
else
{
int i;
for(i = 0; i<9; i++)
{
strcpy(historyCmd[i],historyCmd[i+1]);
}
strcpy(historyCmd[i],cmd_buffer);
hisNum++;
}
}
void printHis()
{
int i;
for(i = 0;i < (hisNum > 10?10:hisNum);i++)
{
printf("%d. %s", i+1,historyCmd[i]);
printf("\n");
}
}
待改进的地方:
1、引进readline库函数的使用
2、后台运行这一功能实现的不是特别好,待后期对SIGNAL学习加深之后再进一步改进
另外就是,重定向用的是文件描述符进行的,后来想想如果用fopen,流的重定向,可能会更加方便一些~