今天進行了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相關函數的了解,進一步加強了在遇到問題時,解決問題的能力,項目過程中令我影響最深刻的就是在獲取用戶輸入的指令時,一開始在另外一個文件中定義了一個數組用來存放獲取的指令,導致之后運行時,指令數據莫名消失,通過排查才發現,對于這種程序一直都會到處使用的數據應該注意其的存在時間,因為如果將指令存放在一個函數中,就會導致這樣一個局部變量在函數運行結束后這個數據就會消失,所以應該將其定義在主函數中或者定義為全局變量。