首发于平等编程

readline简介

很多平台都不光有图形界面,还有自己的的命令行,俗称Command Line,如何实现命令行呢?其实很简单,readline是一个开源库,可以到网上下载下来用于实现我们自己的命令行,readline功能强大,有了巨人给我们造的轮子,做起事来那是非常爽的。

相信用过bash的小伙伴都对bash的强大赞不绝口,可以按"?"提示,可以按Tab键自动补全,可以按上,下箭头翻历史纪录,简直就是命令行产品的翘楚!其实啊,一切都离不开readline的支持,下面我就给大家介绍一下这个库的一些简单功能。

我们实现一个简单的命令集,支持ping一个目的主机,显示当前目录,打印星星,修改主机名,查询用户名,计算器等功能。每次通过键盘敲一个命令,就执行对应的功能。如图所示

可以看到,我以root身份进入了cmd目录,这个目录下只有cmdline, cmdline.c两个文件。我先是ping一个主机地址,可以ping通,然后打印两行星星,接着我查了一下我当前是谁,计算了从1加到10,显示了当前目录,再把主机名从默认的"Router"修改成"wang",最后退出。下面看看它的实现:

#include <stdio.h>
#include <stdlib.h>
#include <readline/readline.h>
#include <readline/history.h>

#define MAX_COMMAND_NUM 10
char gHost[64]={"Router>>"};   //默认主机名称是Router
char helpstr[]={
	"ping (IP-address) Ping destination address\n"\
	"quit Eixt the program.\n"\
	"print stars (1-10) (1-10) Print stars.\n"\	
	"calc (NUM1) (NUM2) Calculate the sum of the range you input.\n"\
	"hostname Fix the hostname of your computer.\n"\
	"ls Show the current files in current dir.\n"\
	"whoami Show the user of this computer.\n"
};                      //当输入'?' 时,可以看到命令集合
char*line_read=NULL;   //全局变量记录读入的行 
char *rl_gets()                     //行读取函数
{
	if(line_read)
	{
		free(line_read);
		line_read=NULL;
	}

rl:
    line_read=readline(gHost);
    if(line_read&&*line_read&&*line_read!='?')
    {
        add_history(line_read);  //支持上、下箭头翻阅历史命令
    }
    else if(*line_read=='?')
    {    
        printf("%s\n",helpstr);goto rl;    
    }
    else 
    	goto rl;
    return line_read;
}

int isValidIpAddress(char *ipStr) //IP地址合法校验
{
	char *p,*start;
	int i = 0;
	int prevhit = 0;
        char *array[4];
	
	p = ipStr;
	start = p;
	while(*p != '\0') 
       {
	if(*p == '.')
        {
            *p = '\0';
			if(prevhit)
				array[i]=NULL;
			else
				array[i]=start;
            
			i++;
			start = p+1;
			prevhit = 1;
	 }
        else
		{
			prevhit = 0;
		}
		p++;
	}
	if(*start != '\0')
		array[i++] = start;
    if(i!=4)
        return 0;

    if( (atoi(array[0])<255&&atoi(array[0])>0)&&
        (atoi(array[1])<255&&atoi(array[1])>0)&&
        (atoi(array[2])<255&&atoi(array[2])>0)&&
        (atoi(array[3])<255&&atoi(array[3])>0) )
        return 1;
    else 
        return 0;    
}
 
void* ping(int argc, char* argv[])     //ping命令的实现
{
    char buf[64]={0};
    char *ip;
    
    if(argc!=2){
	printf("Usage: ping (IP-address)\n");return NULL;}

    ip=strdup(argv[1]);
    if(!isValidIpAddress(ip))
    {
       printf("IP address format is invalid\n"); 
       free(ip);
       return NULL;
    }
    
    sprintf(buf,"ping -c 4 %s",argv[1]);
    system(buf);
    free(ip);
    
	return NULL; 
}

void* quit(int argc, char* argv[])    //quit命令的实现
{
	printf("See you next time...\n");
	exit(0);
	return NULL; 
}
void* print_stars(int argc, char* argv[])  //打印星星命令的实现
{
	int i,j;
	int m,n;

    if(argc!=4){
	printf("Usage: print stars (1-10) (1-10)\n");return NULL;}
    
	m= atoi(argv[2]);
	n= atoi(argv[3]);
	for(i=0;i<m;i++)
	{
		for(j=0;j<n;j++)
		printf("*");
		printf("\n");
	}

	return NULL; 
}
void* calc_sum(int argc, char* argv[])    //计算累加和命令的实现
{
    int start,end;
    int i;
    int sum=0;
    if(argc!=3){
	printf("Usage: calc NUM1 NUM2\n");return NULL;}
    
    for(i=0;i<strlen(argv[1]);i++)
    {
        if(!isdigit(argv[1][i]))
        {
            printf("param 1 error\n");
            return NULL;
        }
    }
    for(i=0;i<strlen(argv[2]);i++)
    {
        if(!isdigit(argv[2][i]))
        {
            printf("param 2 error\n");
            return NULL;
        }
    }
    start = atoi(argv[1]);
    end = atoi(argv[2]);
    for(i=start;i<=end;i++)
        sum+=i;
	printf("Add from %d to %d is %d\n",start,end,sum);
	return NULL; 
}
void* hostname(int argc, char* argv[])    //修改主机名命令的实现
{
	char *s;

	if(argc!=2){
	printf("Usage: hostname string\n");return NULL;}
	
	printf("%s\n",(char*)(argv[1]));
	memset(gHost,0,sizeof(gHost));	
	strcpy(gHost,argv[1]);
	strcat(gHost,">>");
	return NULL; 
}
void* ls(int argc, char* argv[])         //ls命令的实现
{
	char stream[64]={0};
	FILE*fp =popen("ls","r");	
	while(fgets(stream, 64,fp))
    {   
		puts(stream);
    }
	if(fp)
	fclose(fp);

	return NULL; 
}

void* whoami(int argc, char* argv[])    //whoami命令的实现
{
	char stream[64]={0};
	FILE*fp =popen("whoami","r");	
	fgets(stream, 64,fp);
    stream[63]='\0';
    printf("You are: %s\n",stream);
    
	if(fp)
	fclose(fp);
    
	return NULL; 
}

char *commands[MAX_COMMAND_NUM]     //命令集合
={
	"ping",
	"quit",
	"print stars",
	"calc",
	"hostname",
	"ls",
	"whoami"
};
typedef void* (*FUNCPTR)(int argc, char* argv[]);
FUNCPTR func[MAX_COMMAND_NUM]=      //命令函数集合
{
	ping,
	quit,
	print_stars,
	calc_sum,
	hostname,
	ls,
	whoami
};
static char* command_generator(const char *text, int state)
{
    const char *name;
    static int list_index, len;

    if (!state)
    {
        list_index = 0;
        len = strlen (text);
    }

    while (name = commands[list_index])//根据text找到对应的命令
    {
        list_index++;

        if (strncmp (name, text, len) == 0)
        return strdup(name);
    }                         //如果没有,则返回空

    return ((char *)NULL);
}

char** command_completion (const char *text, int start, int end)
{
    char **matches = NULL;

    if (start == 0)
        matches = rl_completion_matches (text, command_generator);

    return (matches);
}

void init_readline()
{
	rl_attempted_completion_function=command_completion;
    
	return ;
}
void execute_command(char *cmd[])
{
	int index=0;
	char* name;
	int n;

	
	while(name=commands[index])
	{
		n=strlen(cmd[0]);
		if(strncmp(name,cmd[0],n)==0)
		{
			if(*(cmd[1])&&*(cmd[2])&&*(cmd[3]))
				func[index](4,cmd);
			else if(*(cmd[1])&&*(cmd[2]))
				func[index](3,cmd);
			else if(*(cmd[1]))
				func[index](2,cmd);
			else
				func[index](1,NULL);

			return;
		}
		
		index++;
	}	
	printf("no match\n");
	return ;
}
int main()
{
	int i=0;
	char* mline;
	char ss[4][16]={0};
	char*tok;
	char *s;
	char * cmd[4]={"\0","\0","\0","\0"};
	
	init_readline();
	while(1)
	{
		mline=rl_gets();     //读取用户的输入
		i=0;
		memset(*ss,0,4*16*sizeof(char));

		tok=strtok_r(mline," ",&s);
		strcpy(&ss[i++][0],tok);
		while(tok!=NULL)
		{	tok=strtok_r(NULL," ",&s);
			if(tok){
			strcpy(&ss[i++][0],tok);
			}
		}               //因为命令最多只含有4个单词
		cmd[0]=ss[0];          //把输入变成单词的集合
                cmd[1]=*ss[1]?ss[1]:cmd[1];
                cmd[2]=*ss[2]?ss[2]:cmd[2]; 
                cmd[3]=*ss[3]?ss[3]:cmd[3];

		execute_command(cmd);  //执行命令
	}

	return 0;
}

再在Ubuntu里面用这个命令,gcc -o cmdline cmdline.c -lreadline把它编译一下,运行,就看到上图的效果了。大家可以复制粘贴代码到自己的机器上,量身定制属于自己的命令行吧!

发布于 2022-03-10 15:16