当前位置:首页 期刊杂志

个性化的Linux命令解析器的设计与实现

时间:2024-07-06

刘 雍

(海南热带海洋学院 计算机科学与技术学院,海南 三亚 572022)

0 引言

在全球超级计算机操作系统TOP500强排行榜中,Linux操作系统的占比在最近几十年一直呈现快速上升趋势,且保持在85%以上[1]。又随着移动互联网的快速发展,Linux内核亦越来越频繁地被移植和使用到嵌入式、物联网、树莓派及Android等智能设备中[2-3], 不断扩展的Linux Socket网络编程实现信息实时交互、在线存储、实时监控等,自然成了主要开发的方向[4],因此,对Linux的各种命令的整合和解析也随之成为热门的课题。

以Linux操作系统原理为导向,从内核编程的角度,重新构建Shell命令从读取到执行的各环节,是命令解析器开发研究的核心,而解析和模拟的本质则是一个对多进程控制的过程[5-6]。针对Linux固有命令较多,并且相当多的命令反而极少被使用的现状[7],本研究给出了一种设计Shell解析命令器通用模型的思路与方法:即把使用频率较高的命令整合在一个帮助界面,并提供记忆历史命令等辅助功能, 以实现更安全、更友好、更个性化的定制服务。随着程序版权意识越来越强[8],个性化设计亦是本研究的重点之一。

1 个性化命令解析器的功能与工作流程

个性化命令解析器用C语言编程设计,遵照Linux默认的Bash Shell结构特点,处理终端命令从输入、到解析直至执行全过程。在确保能正常调用Linux固有的命令集的前提下[9],个性化命令解析器实现了一个自定义的常用命令的集合,提供“help”帮助界面,解析命令的功能及用法,且增设了命令记忆功能,其较之传统的单一的字符界面,用户操作方便,交互性较好。

设计个性化命令解析器的思路和步骤主要包括以下三个环节。

第一步,初始化解析器,提供个性化命令行提示格式,获取当前用户信息、主机信息及目录信息等;第二步,切割用户输入命令行,获取命令及参数,第一个字符串为命令字,其余依次是参数序列;第三步,对上一个步骤获取的命令进行解析,包括判断、调用和执行等环节。首先判断命令与当前有限的自定义的命令是否匹配,若匹配则执行“help”分支,创建进程调用自定义函数[10];否则,判断命令是否为系统内部命令,对肯定的情况进行“内部命令”分支,调用系统默认的命令解析,以保证Linux内部命令正常使用;若对以上两种情况都无法识别的命令,将执行“无法识别的命令”分支,返回系统出错信息。程序设计重点在于自定义命令的函数框架及API调用[11],个性化命令解析器工作流程如图1所示。

图1 个性化命令解析器工作流程

2 主要算法与分析

2.1 初始化解析器

Linux 中Bash Shell的组成部分从左到右依次是:登陆时的用户名、主机信息和当前目录[12]。其中,使用getpwuid函数获取密码数据库结构指针数据struct passwd*pw,后输出passwd成员变量char*pw_name即为用户登录名;通过函数原型int uname(struct utsname*buf) ,获取主机信息,utsname成员变量nodename即为主机名;获取当前路径使用函数的原型 char*getcwd(char*buf,size_t size),但是该函数获取的是当前的绝对路径,因而还需要把绝对路径转化为相对路径;提示部分区分#与$,如果函数getuid()的返回值为0,代表是根用户root输出#,否则输出$。以上四个部分用以下语句在一行内输出:

printf("[ly′%s@%s %s]%c",pw->pw_name,ptr,p,flag);

假如默认终端的原格式为“[root@hntou user]#(绝对路径为/home/user)”,命令解析器初始化后可显示为“[ly′root@hntou user]#”,意为当前root用户ly,登录主机hntou,所在的相对路径是user的目录,还可以改变终端显示的字体颜色和背景颜色,实现个性化友好显示。

2.2 分离命令与参数

在自定义的终端下,如何识别用户输入执行的命令及其参数是关键步骤之一,这里使用切割的方法。方法是使用strtok函数按空格分离用户输入的字符串数据(回车结束),分割后的各部分分别赋值给指针数组,第一个字符数组argv[0]即为命令,后面argv[1]、argv[2]等为命令的参数,可用语句printf("listargv[%d]:%s/n",cnt,p)测试用户输入后的分割情形,其结果如下所示。

Your cmd is:copy ./a /home/user/bak_a

listargv[0]:copy

listargv[1]:./a

listargv[2]:/home/user/bak_a

2.3 解析并执行命令

把上一步结果中的命令(argv[0])及参数列表(argv[1],argv[2]等)传递给内核,解析并调用对应功能的函数。使用strncmp函数去匹配命令集,首先调用自定义函数,即定制的个性化可执行程序;其次调用Linux固有的内部命令,本质上使用fork()函数创建新的进程,在子进程中调用exec系列函数执行新进程[13],如使用以下语句:

execl("/bin/sh","sh","-c",argv[0],NULL);

最后,如果以上子进程执行不成功,意为命令argv[0]无法识别,或者参数出错,系统则输出出错信息,让父进程回收子进程资源。

3 个性化命令集

3.1 实现框架

根据图1可知,当前命令解析器提供了至少6个常用命令解析,命令字及相关功能,如表1所示。

表1 自定义命令解析表

如果以上自定义的命令函数全部写在main()函数所在的.c的文件里,代码量将会相当庞大,而且不利于调度和功能扩展,为了程序书写方便,且优化编译,一般做法是书写各命令解析为独立的.c源文件,如mycd.c、list.c、copy.c等,而后把这些.c的文件包含到一个自定义的myshell.h的头文件中,当然.h文件中还包括相当多函数调用的系统头文件[14],而后编译时,只编译包含main()函数的主函数的一个.c文件。

3.2 实现实例

以更改用户路径这一子功能mycd的命令解析为例,给出各子功能基本通用的程序设计框架。

第一步,编写核心文件mycd.c,主要任务是自定义函数void cd_cmd(char *path),函数名为cd_cmd,参数是用户输入的路径字符串,即函数功能实现切换目录(旧目录)到用户输入的路径(新目录)中。根据Linux操作系统的原理,要对参数分情况讨论,如符号“~”代表用户家目录,“.”代表当前目录,“..”代表上一级目录,“/”代表根目录。而且还要处理权限的问题,如普通用户无法切换到根用户家目录。在切换目录的实际操作中,主要步骤大约分三步,首先,获取用户信息包括其家目录信息,取结构体数据struct passwd,其中pw_dir成员变量是家目录;其次,获取当前目录,使用函数getcwd(),把获取当前工作目录的绝对路径,赋值给old_path地址变量;最后,切换目录,语句strcopy(old_path,new_path)则先给new_path赋值,再调用chdir(new_path)系统函数切换到新路径中。

第二步,把子功能的核心文件包含到自定义的主要头文件myshell.h中,该实例使用#include “mycd.c”语句,方便主函数调用。

第三步,在包含有myshell.h头文件的main函数中调用以上第一步的cd_cmd函数。

if(strncmp(argv[0],"mycd",4)==0) //如果argv[0]所代表的命令是mycd关键字

{

cd_cmd(argv[1]);//调用函数

continue;

}

同样的方法,如果再新增一个文件删除命令myrm的解析,则只需要再书写一个独立的myrm.c源文件,并在myshell.h的头文件中加上一句 #include “myrm.c”,最后在main函数中调用即可。限于篇幅,其他命令解析就不在此赘述。

3.3 主函数的思路

为突出个性化友好界面,增强交互性,判断(strcmp(arglist[0],"help/0")==0)则给出了帮助help命令,打开运行终端并将一直处于运行之中,所以总体来说,main()函数是一个while(1)的循环结构。依次调用初始化终端函数、命令行字符串切割函数、各子功能主要函数、内置命令函数,调用正确则输出执行结果,否则输出出错信息[15]。主函数中用整型变量cmd_cnt为计数器,以记录历史命令的个数,函数strcpy(history[cmd_cnt],cmd)则实现把所有的命令放置于一个字符串数组中。如在个性化的终端提示符下,如果用户输入是系统不能识别的EXIT命令,则输出出错信息“Command Error!”,且友好提示“You may need ′help′”,根据提示输入“help”命令后,界面下显示的正是自定义的主要命令集,对各命令功能均有描述,具体情形参考如下。

[root@hntou shell_test]# ./shell

[ly′root@hntou shell_test]#EXIT

Command Error!

You may need ′help′

[ly′root@hntou shell_test]#help

mypwd:展示目前工作目录.

mycd:切换指定工作目录.

copy:复制目录或文件到指定位置.

myps:显示进程信息.

myls:显示文件信息.

mytime:显示进程运行时间.

mytree:显示目录结构.

myrm:递归删除文件、文件夹.

mymv:移动、重命名文件、文件夹.

myline:显示文件、文件夹内全部文件的函数.

myhis:显示输入历史.

exit:退出shell.

上述主界面给出常用命令的帮助信息,界面友好,操作方便。个性化命令解析体现在以下四个方面:第一,功能全面,用户可首选自定义的命令,也可以调用Linux固有内部命令,如查看当前目录或文件信息,示例既使用了主界面提供的myls命令,也使用原ls命令验证,确保了Linux内部命令不缺失;第二,增强了命令解析的交互性,如实现文件复制操作调用的是copy命令,比原cp命令更符合用户的理解,且有操作成功的交互性提示;第三,可扩展性较好,可根据用户需要灵活增减相应功能的命令;第四,由于Linux shell默认不会记忆历史命令[16],所以使用myhis命令方便查看历史命令序列,相关操作参考如下。

[ly′root@hntou shell_test]#myls-l ./

-rwxr-xr-x 1 root root 53016 Jan 26 21:37 shell

-rw-r--r-- 1 root root 114 Jan 26 21:37 file

[ly′root@hntou shell_test]#copy file bak_file

Copy Finished!

[ly′root@hntou shell_test]#ls

bak_file file shell

[ly′root@hntou shell_test]#myhis

**Print Input History until now:**

EXIT

help

list-l ./

copy file bak_file

ls

myhis

[ly′root@hntou shell_test]#exit

Bye~

[root@hntou shell_test]#

4 结论

本研究遵照Linux用户的使用习惯,构造了对Linux常用命令重新解析的方法和思路,给出了C语言程序设计的模型,实现了Shell命令提示格式个性化显示、命令解析多样化、主界面友好的设计及记忆历史命令等功能,框架严谨,思路清晰,方法灵活。该命令解析器个性化特征突出,易于扩展,在移动互联网设备方面适用性会越来越好。

免责声明

我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自各大过期杂志,内容仅供学习参考,不准确地方联系删除处理!