今天进行了linux系统高级编程io阶段学习的结尾,完成了一个minshell的小项目。
一、项目介绍
利用Linux中IO接口实现MiniShell,实现常用的shell指令的实现。
项目想要实现需要思考的地方有:
1.如何打印终端命令
2.如何接受终端命令
3.实现对应的命令
要求:采用多文件编程,并实现按时间将输入的指令记录到日志文件中。
二、项目实现
初步思考整个项目的大致流程图如下:
接下来介绍项目实现中的一些关键部分:
2.1 打印终端提示符
要实现如上图的终端命令提示符,我们可以发现该提示符主要分为两部分:
1.前面的linux@ubuntu是固定不变的,直接printf输出即可,后面的路径则是一直改变的,这时我们可以通过getcwd函数来实现获取当前的绝对路径:
int Terminal(void)
{char buf[1024] = {0};char *p = NULL;getcwd(buf,sizeof(buf));p = buf;while(*p != '\0') //主要打印当前的一级路径即可,则可以通过指针分割出我们想要的路径{p++;}while(*p != '/'){p--;}p++;printf("\033[1;31m"); //这里主要采用vt100打印出想要的字符颜色printf("linux@Ubuntu:");printf("\033[0m");printf("\033[1;35m");printf("%s",p);printf("\033[0m");printf("$ ");return 0;
}
实现效果如下:
2.2用户指令的获取
这里采用fgets获取用户键盘输入的指令
int Gets(char *tmbuff,int maxlen)
{fgets(tmbuff,maxlen,stdin);tmbuff[strlen(tmbuff)-1] = '\0'; //将fgets获取到的字符串末尾的换行符删掉return 0;
}
2.3用户指令的分割
我们在输入shell指令时有些指令并不是一个单独的指令,另外还包括了参数以及操作对象等,用上面的方法获取的字符串往往是一整串,每个指令之间还包括了空格,这时我们就需要去分割出来每个部分。
有两种实现方法:
第一种直接采用strtok函数对字符串进行分割。
以下是strtok函数的功能介绍:
char *strtok(char *str, const char *delim);
将字符串分解为一个由零个或多个非空记号组成的序列。在第一次调用strtok()
时,要解析的字符串应该在str中指定。在每个应该解析相同字符串的后续调用中,
str必须为NULL。
以下是实现代码:
#include <string.h>
#include <stdio.h>int main(void)
{char cmdbuf[1024] = {0};char *pret = NULL;char *parg[10] = {NULL};int cnt = 0;//ln -s file.txt a.txtgets(cmdbuf);parg[cnt] = strtok(cmdbuf, " ");//以空格进行分割cnt++;while (1){parg[cnt] = strtok(NULL, " "); //后续分割第一个参数传入NULLif (NULL == parg[cnt]){break;}cnt++;}int i = 0;for (i = 0; i < cnt; i++){printf("parg[%d] = %s\n", i, parg[i]);}return 0;
}
第二种是编写函数,采用一个可移动指针去移动到空格出,并置‘\0’,从而实现对字符串的分割:
int SplitCommand(char *pcmdbuf, char **parg, int maxlen)
{char *ptmp = NULL;int cnt = 0;ptmp = pcmdbuf;while (1){parg[cnt] = ptmp;cnt++;while (*ptmp != '\0' && *ptmp != ' '){ptmp++;}if ('\0' == *ptmp){break;}*ptmp = '\0';ptmp++;while (*ptmp == ' '){ptmp++;}}return cnt;
}
2.4 日志文件的创建
这里直接用标准io的相关接口函数进行即可
#include "record.h"
#include <stdio.h>
#include <time.h>FILE *fp = NULL;/*********************************************************函数名:InitRecord*参 数:* 缺省 void *返回值:* 成功返回0 * 失败返回-1 *******************************************************/
int InitRecord(void)
{fp = fopen(RECORD_PATH, "a");if (NULL == fp){return -1;}return 0;
}/*********************************************************函数名:RecordCommand*参 数:* pcmdbuf 命令字符串首地址 *返回值:* 成功返回0 * 失败返回-1 *******************************************************/
int RecordCommand(char *pcmdbuf)
{time_t t;struct tm *ptm = NULL;time(&t);ptm = localtime(&t);fprintf(fp, "[%04d-%02d-%02d %02d:%02d:%02d]%s\n", ptm->tm_year + 1900, ptm->tm_mon+1, ptm->tm_mday, ptm->tm_hour, ptm->tm_min, ptm->tm_sec, pcmdbuf);fflush(fp);return 0;
}/*********************************************************函数名:DeInitRecord*参 数:* 缺省 void *返回值:* 成功返回0 * 失败返回-1 *******************************************************/
int DeInitRecord(void)
{if (fp != NULL){fclose(fp);fp = NULL;}return 0;
}
项目主函数代码展示:
#include <stdio.h>
#include <string.h>
#include "terminal.h"
#include "record.h"int main(void)
{char command[1024] = {0};char *parg[10] = {NULL};int curcmdlen = 0;InitRecord();while (1){ShowTerminal();GetCommand(command, 1024);if (!strcmp(command, "exit")){DeInitRecord();break;}RecordCommand(command);curcmdlen = SplitCommand(command, parg, 10);ExecCommand(parg, curcmdlen);}return 0;
}
三、项目总结
通过本次项目进一步加深了对文件io以及标准io相关函数的了解,进一步加强了在遇到问题时,解决问题的能力,项目过程中令我影响最深刻的就是在获取用户输入的指令时,一开始在另外一个文件中定义了一个数组用来存放获取的指令,导致之后运行时,指令数据莫名消失,通过排查才发现,对于这种程序一直都会到处使用的数据应该注意其的存在时间,因为如果将指令存放在一个函数中,就会导致这样一个局部变量在函数运行结束后这个数据就会消失,所以应该将其定义在主函数中或者定义为全局变量。