一個在終端實現類Linux shell(cd ls命令)UI界面的功能(C語言實現)
這2天做了一個類似Linux shell的UI界面,目前已初步完成cd, ls, help, pwd, quit等命令,在Linux下實現,效果圖見下圖:
ls命令:
開始及help命令:
對於完成此UI界面來說,最主要是根據輸入的命令找到相關的命令處理函數。通過參考部分bash的實現,找到了如下方法:
通過一個回調函數指針,實現調用相關命令處理函數的功能,代碼如下:
//*****************************************************************************
//
// Command line function callback type.
//
//*****************************************************************************
typedef int (*pfnCmdLine)(int argc, char *argv[]);
//*****************************************************************************
//
//! Structure for an entry in the command list table.
//
//*****************************************************************************
typedef struct
{
//
//! A pointer to a string containing the name of the command.
//
const char *pcCmd;
//
//! A function pointer to the implementation of the command.
//
pfnCmdLine pfnCmd;
//
//! A pointer to a string of brief help text for the command.
//
const char *pcHelp;
}
tCmdLineEntry;
而終端輸入的命令最終會在CmdLineProcess()這個核心函數裏面進行處理,代碼如下:
int CmdLineProcess(char *pcCmdLine)
{
static char *argv[CMDLINE_MAX_ARGS + 1];
char *pcChar;
int argc;
int bFindArg = 1;
tCmdLineEntry *pCmdEntry;
//
// Initialize the argument counter, and point to the beginning of the
// command line string.
//
argc = 0;
pcChar = pcCmdLine;
//printf("CmdLineProcess: %s\n", pcCmdLine);
//
// Advance through the command line until a zero character is found.
//
while (*pcChar)
{
//
// If there is a space, then replace it with a zero, and set the flag
// to search for the next argument.
//
if(*pcChar == ' ')
{
*pcChar = 0;
bFindArg = 1;
}
//
// Otherwise it is not a space, so it must be a character that is part
// of an argument.
//
else
{
//
// If bFindArg is set, then that means we are looking for the start
// of the next argument.
//
if(bFindArg)
{
//
// As long as the maximum number of arguments has not been
// reached, then save the pointer to the start of this new arg
// in the argv array, and increment the count of args, argc.
//
if(argc < CMDLINE_MAX_ARGS)
{
//printf("\nargc=%d, argv=%s ", argc, argv);
//printf(" pcChar=%c ", *pcChar);
argv[argc] = pcChar;
//printf("\nargc=%d, argv=%s ", argc, argv);
argc++;
bFindArg = 0;
}
//
// The maximum number of arguments has been reached so return
// the error.
//
else
{
return(CMDLINE_TOO_MANY_ARGS);
}
}
}
//
// Advance to the next character in the command line.
//
pcChar++;
}
//printf("argc=%d, argv=%s ", argc, argv);
//
// If one or more arguments was found, then process the command.
//
if(argc)
{
//
// Start at the beginning of the command table, to look for a matching
// command.
//
pCmdEntry = &g_sCmdTable[0];
//printf("pCmdEntry->pcCmd=%s \n", pCmdEntry->pcCmd);
//
// Search through the command table until a null command string is
// found, which marks the end of the table.
//
while(pCmdEntry->pcCmd)
{
//printf("while: pCmdEntry->pcCmd=%s, argv[0]=%s \n", pCmdEntry->pcCmd, argv[0]);
//printf("while: pCmdEntry->pfnCmd=0x%x \n", pCmdEntry->pfnCmd);
//
// If this command entry command string matches argv[0], then call
// the function for this command, passing the command line
// arguments.
//
if(!strcmp(argv[0], pCmdEntry->pcCmd))
{
//printf("Define: pCmdEntry->pcCmd=%s, argv[0]=%s \n", pCmdEntry->pcCmd, argv[0]);
//printf("***pCmdEntry->pfnCmd=0x%x \n", pCmdEntry->pfnCmd);
return(pCmdEntry->pfnCmd(argc, argv));
}
//
// Not found, so advance to the next entry.
//
pCmdEntry++;
}
}
//
// Fall through to here means that no matching command was found, so return
// an error.
//
return(CMDLINE_BAD_CMD);
}
參考了部分網上的資料,實現Linux "ls" 命令的源碼如下:
/************************************************************************************
** File: - Z:\work\code\c\longluo\cmd_ls.c
** VENDOR_EDIT
** Copyright (C), tcpipstack.
**
** Description:
** cmd_ls.c - The implement of the Linux Like "ls" command.
**
** Version: 1.0
** Date created: 10:20:56,14/11/2012
** Author: long.luo
**
** --------------------------- Revision History: --------------------------------
** <author> <data> <desc>
**
************************************************************************************/
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <pwd.h>
#include <grp.h>
#include <time.h>
#include <dirent.h>
int do_ls(char *dir,char *filename,int lflag)
{
int n;
struct stat buf;
char out[100];
struct passwd *pw;
struct group *gr;
struct tm *t;
//如果不帶l參數,直接顯示文件/目錄名
if (lflag == 0)
{
printf("%s\t", filename);
return 0;
}
if(lstat(dir,&buf)<0)
{
fprintf(stderr,"stat error:%s\n",strerror(errno));
return -1;
}
//獲取字符串的屬性:普通文件-、目錄d、字符設備c、塊設備b、
//管道文件p、連接文件l、套接字文件s
switch(buf.st_mode & S_IFMT)
{
case S_IFREG:
printf("-");
break;
case S_IFDIR:
printf("d");
break;
case S_IFCHR:
printf("c");
break;
case S_IFBLK:
printf("b");
break;
case S_IFIFO:
printf("p");
break;
case S_IFLNK:
printf("l");
break;
case S_IFSOCK:
printf("s");
break;
}
//打印文件的讀寫屬性:讀r、寫w、執行x、無權限-
for(n=8;n>=0;n--)
{
if(buf.st_mode&(1<<n))
{
switch(n%3)
{
case 2:
printf("r");
break;
case 1:
printf("w");
break;
case 0:
printf("x");
break;
default:
break;
}
}
else
{
printf("-");
}
}
//硬鏈接數,此鏈接非彼鏈接,指(包含)目錄的個數,
//文件爲1,目錄起始爲2,再加上目錄裏包含的目錄個數(不遞歸,只一層)
printf(" %d",buf.st_nlink);
pw = getpwuid(buf.st_uid); //所屬用戶名
printf(" %s",pw->pw_name);
gr = getgrgid(buf.st_gid); //所屬組名
printf(" %s",gr->gr_name);
printf(" %ld",buf.st_size); //字節計總大小
t = localtime(&buf.st_atime); //最後一次訪問時間
printf(" %d-%d-%d %d:%d"
,t->tm_year+1900
,t->tm_mon+1
,t->tm_mday
,t->tm_hour
,t->tm_min);
printf(" %s ",filename);
//判斷是否爲鏈接,是返回真
if(S_ISLNK(buf.st_mode))
{
printf(" -> ");
if(readlink(filename,out,100)==-1)
{
//printf("readlink error\n");
}
printf("%s",out);
}
printf("\n");
return 0;
}
// ls的準備工作
int ls_prepare(char *w,int aflag,int lflag)
{
struct stat buf; //man lstat可以看到此結構
char name[100];
DIR *dir; //類似打開文件的fd描述符
struct dirent *pdr; //man readdir可以看到此結構
//獲取文件/目錄屬性並賦值給buf,該函數和lstat一樣,
//只是當w爲鏈接時,指代他本身,並不存在文件
if(lstat(w,&buf)<0)
{
fprintf(stderr,"stat error:%s\n",strerror(errno));
return -1;
}
if(S_ISDIR(buf.st_mode)) //判斷是否爲目錄,是返回真
{
dir = opendir(w); //打開目錄
while ((pdr = readdir(dir))!=NULL) //讀/遍歷目錄
{
if(aflag==0) //如果不帶a參數,越過以.開頭的所有文件/目錄
{
if(pdr->d_name[0]=='.')
continue;
memset(name,0,100);
strcpy(name,w); //拷貝
strcat(name,"/"); //追加
strcat(name,pdr->d_name);
do_ls(name,pdr->d_name,lflag);
}
//有a參數顯示所有
else
{
memset(name,0,100);
strcpy(name,w);
strcat(name,"/");
strcat(name,pdr->d_name);
do_ls(name,pdr->d_name,lflag);
}
}
closedir(dir);
}
//爲文件則直接顯示
else
{
do_ls(w,w,lflag);
}
return 0;
}
項目的main函數代碼如下,主要就是一個while(1)循環讀取終端的輸入,然後送入命令處理函數,值得注意的是因爲輸入了換行符,因此需要對輸入的字符進行處理,否則會有bug產生:
int main(int argc, char *argv[])
{
int nStatus, nStrLen;
fprintf(stderr, "Hello, CMD UI Program...");
// Enter an infinite loop for reading and processing commands from the user.
while (1) {
// Print a prompt to the console. Show the CWD.
printf("\n%s> ", g_cCwdBuf);
// Get a line of text from the user.
fgets(g_cCmdBuf, CMD_BUF_SIZE, stdin);
// Remove the char '\n'.
nStrLen = strlen(g_cCmdBuf);
if ('\n' == g_cCmdBuf[nStrLen - 1]) {
g_cCmdBuf[nStrLen - 1] = '\0';
}
// Pass the line from the user to the command processor.
// It will be parsed and valid commands executed.
nStatus = CmdLineProcess(g_cCmdBuf);
// Handle the case of bad command.
if(nStatus == CMDLINE_BAD_CMD) {
printf("Bad command!\n");
}
// Handle the case of too many arguments.
else if(nStatus == CMDLINE_TOO_MANY_ARGS) {
printf("Too many arguments for command processor!\n");
}
// Otherwise the command was executed. Print the error
// code if one was returned.
else if(nStatus != 0) {
printf("Command returned error code.\n");
}
}
return 0;
}
在編寫這個小項目的同時,也順便把Makefile的編寫也複習了下,這個項目的Makefile文件如下:
#
# Makefile - Rules for building the program.
#
# History:
# long.luo, 12/11/2012, created it.
# long.luo, 13/11/2012, modified it.
#
# The compiler to be used.
ifndef COMPILER
COMPILER=gcc
endif
# Definitions for using GCC.
ifeq (${COMPILER}, gcc)
# The command for calling the compiler.
CC=gcc
# The flags passed to the compiler.
CFLAGS=-Wall -Os -c
# The command for calling the linker.
LD=ld
# The flags passed to the linker.
LDFLAGS=--gc-sections
# The PHONY targets.
.PHONY: all clean
# The default rule, which causes the Application to be built.
all: cmd_ui
# The rule to create the target directory.
${COMPILER}:
mkdir ${COMPILER}
# The rule for building the object file from each C source file.
%.o : %.c
${CC} ${CFLAGS} -D${COMPILER} -o $@ $<
# The objects.
OBJS=cmdline.o cmd_ui_shell.o cmd_ls.o
# The final executable file.
exec=cmd_ui
# The rule for building the application.
cmd_ui: ${OBJS}
$(CC) -o $@ $?
# The rule to clean out all the build products.
clean:
rm -f ${wildcard *.o} ${exec}
endif
以上,這個項目可以應用在其他終端上,比如串口上實現簡單的UI界面:-)
本文鏈接:https://blog.csdn.net/tcpipstack/article/details/8181921