類SHELL UI界面C語言實現

一個在終端實現類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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章