益友软件工作室

加密金刚锁相关技术 → C语言类技术文章 → 增强 unix(xenix)命令行的编辑功能

增强 unix(xenix)命令行的编辑功能

文章作者:李延春

摘要: unix(xenix)系统中,shell 命令行的编辑功能较弱。为弥补这一缺憾, 本文给出了一种实现命令行编辑功能的方法,其使用与 PC-DOS 系统的编辑键类似,实现简单,使用方便。

关键词:unix xenix 命令行 复制 编辑键

一、问题的提出

用惯了 PC-DOS 系统的用户都知道,DOS 的命令行编辑功能比较强,它提供了编辑样板,定义了诸多的功能键 (F1--F4,Esc,Ins,DEL等)。这一机制为用户提供了极大的方便。比如,若想重复执行上一条命令,只需安“F3”键即可。但是,Xenix 在这方面却没有提供类似功能。Xenix 的标准 shell是一个功能很强的用户接口,用户以交互方式输入的所有命令都要由它处理,才能申请核心服务;并且它还提供了一套功能强大的命令处理语言。但是 shell提供的命令行编辑功能较差,它只定义了一个退格键(Backspace)。由于 Xenix 系统中的命令可选参数多,而且多个命令可在一行输入,所以 Xenix的命令行一般都比较长。

如果某命令行需要重复执行,或命令行中有输错的字符而未能正确执行时,用户只得重新输入,比较麻烦。尤其是用惯了 DOS系统的用户,使用起来深感不便。那么有没有办法使 Xenix 能象 DOS 那样具有命令行编辑功能(功能键 F1--F4 等)呢?本文旨就这方面进行一些探讨,并提出具体的实现方法。

二、方案的确定

DOS 系统定义了诸多的编辑功能键,在命令行的编辑及行编辑程序 EDLIN 中均可使用这些功能键。而 Xenix系统中,编辑功能键是在一些实用程序内部定义的,如 vi、ed 等,在接收命令时没有此功能。所以在日常输入命令时很不方便。如何解决这一问题呢?

首先我们想到调用 csh。csh 是具有类似 C 语法的 shell 命令解释程序,它提供了历史替换机制,将用户输入的命令行放进命令历史清单中保存下来,以便将来随时引用。命令从 1 开始顺序编号,历史替换以字符 ! 开始。例如输入“!!”引用前一命令,所以可用“!!”重复执行某一命令。又如“!-2”引用由此回溯的第二条命令。

csh 的这些历史替换功能请参阅 Xenix系统的有关手册,在此不一一细述。

调用 csh 的方法有两种:一是将 csh 做为用户的注册 shell,即建用户时指定 csh 为注册 shell,或通过修改 /etc/passwd 文件中对应本用户的 /bin/sh 为 /bin/csh 来实现。二是在标准 shell 提示符下键入 csh 亦可调用 csh 。csh 的这一机制,使命令行的编辑、复制成为可能。而且引用的可以是历史清单中的任一命令,功能很强,这是 DOS的编辑功能所望尘莫及的。但是正由于这一机制功能很强,所以其指示符和修饰符较多,不便于记忆,具体使用比较复杂,而且不能即时回显其引用结果,用户界面不太直观,也就是说,这不是一种面向屏幕行的编辑机制,而是一种对历史命令的引用机制。所以一般用户很少使用这一机制。

基于以上原因,我们提出了一种实现简单、使用方便的方案来完成这一功能。本方案的基本思想是设计一新的 shell界面,定义有关的编辑功能键,处理用户输入的命令行,实现各种编辑功能,然后将结果交标准 shell 进行处理。也就是在标准 shell 的上面再加一层适配性用户接口,这样做的优点是:*(1).保留标准 shell的全部功能;(2).实现简单。(3).用户界面直观、友好。

三、具体实现

新的 shell的设计目标是:完成有关功能键的解释处理,根据其意义对当前命令行进行加工处理,并要将结果送标准 shell解释执行。程序设计过程中需要考虑的问题:

1、编辑功能键的定义。从使用方便的目的出发,同时也为了照顾 DOS 用户的习惯,程序提供的编辑键及其功能基本上与 DOS一致(定义了F1--F4,Ins,Esc等键)。但考虑到多用户环境中终端类型的复杂性,个别键做了调整:以“↑”键代替“Ins”(插入键),以“↓”键代替“Esc”键。在主机键盘上功能键 F1--F4 的串码是由 Xenix定义的,但是在各种终端上就不同了。有的终端 F1--F4键定义为本地功能键,所以只得考虑用其它键代替 F1--F4。

本程序提供了三种类型的终端供选择 (通过调用 newsh 时给定不同的参数 0、1、2 进行选择):

(1).当 F1--F4 没被终端定义时, F1--F4 即是对应的 4 个功能键,这时参数为 0;

(2).当 F1--F4 被终端定义但 PF1--PF4 没被终端定义时,以 PF1--PF4 代替 F1--F4 四个功能键,这时参数为 1;

(3).当 F1--F4、PF1--PF4 都由终端定义为特殊用途的功能键时,只有借助组合键了,这时以 “Alt+F1”、“Alt+F2”、“Alt+F3”、“Alt+F4”分别代替 F1--F4 四个功能键,这时参数为 2。

以上这些功能键的代码在不同的终端上可能有些不同,请根据你的终端特性修改第 12 -- 26 行的有关内容。

2、标准 shell 的引用。程序中我们没有用系统调用 fork 和 exec,而是用系统调用 system 完成用户命令的执行。因为 system 要引用标准 shell,所以,可以保留标准 shell 的全部功能(如通配符 *、? 等的处理以及 shell 内部实现的一些命令)。如用 fork 和 exec 实现,编码工作量太大,等于将shell 重写。

3、cd 命令的处理。由于 Xenix 的系统调用 chdir 只是改变了调用它的进程的当前目录,而不会改变其它进程的当前目录。所以标准shell 中的 cd 命令是在内部实现的,即 cd 是 shell的一个内部命令。而 system 调用要通过引用标准 shell (生成新进程)执行命令,因此对 cd 命令要单独处理。(程序中第 132 行--第 142 行) 。

4、命令行的即时显示。既然我们提供的是一命令行的编辑功能,那么各功能键的编辑结果应即时显示。我们采取的方法是:通过系统调用 ioctl 重新修改终端控制参数,将终端设置为原始工作模式;将读操作返回需要的最小字符数设为 1,这样,只要输入一个字符读操作就立即返回;关掉回显功能,通过系统调用 write 回显处理后的字符。 5.程序中设定命令行最大长度为 100 个字符 (LENGTH),用户可根据需要做相应修改 (第 29 行 );提示符从用户环境中获取,若用户环境中没有明显定义时,则根据是否超级用户分别以“#”、“$”做为提示符,用户可根据需要对提示符进行修改(第 33 行和第 34 行)。

四、使用方法

1、将 newsh.c 编译连接为可执行文件,并移入 /usr/bin 目录下。

即 cc -s -O newsh.c -o newsh (回车) mv newsh /usr/bin/newsh (回车)

2、通过下列任一方式调用 newsh:

(1).在shell提示符下键入 newsh [0/1/2](回车)。

(2).将用户目录下的 .profile 文件中增加一行 newsh [0/1/2] 。

(3).将用户的注册shell改为 newsh。即将 /etc/passwd 文件中对应本崐用户的 /bin/sh 改为 /usr/bin/newsh,这样用户注册成功后直接引用 newsh。

3、各功能键的使用。

定义的所有功能键基本上与 DOS 系统的编辑键一致,只是以“↑”、“↓”分别代替了“Ins”和“Esc”键。这里只做一简要说明,详细内容及示例请参考 DOS 手册。

F1 :复制样板中的一个字符,并显示在屏幕上;

F2 :复制从当前位置到一个你指定的字符为止的所有字符,并显示在屏幕上,如样板中找不到你指定的字符,则什么也不做;

F3 :复制样板中剩余的全部字符,并显示在屏幕上;

F4 :删除从当前位置到一个你指定的字符为止的所有字符,如样板中找不到你指定的字符,则什么也不做;

退格键(Backspace):光标回退一个位置并消除一个字符;

↓ :作废当前编辑的命令行,光标移到下一行,但样板不改变;

↑ :允许你插入若干字符,此键为一反复键。

附源程序清单 newsh.c 。

#Newsh.c 源程序清单

#include<termio.h>

#include<signal.h>

#define ESCAP 27 /* 功能键引导符*/

/* 应根据所用设备及工作需要,

修改以下各值,

请参考你的有关手册. */

/* 以下4行为主机键盘功能键的值:*/

#define CF1 77 /* F1键 */

#define CF2 78 /* F2键 */

#define CF3 79 /* F3键 */

#define CF4 80 /* F4键 */

/*以下4行为终端键盘功能键的值: */

#define PF1 80 /* PF1键 */

#define PF2 81 /* PF2键 */

#define PF3 82 /* PF3键 */

#define PF4 83 /* PF4键 */

/*以下4行为终端键盘功能键的值: */

#define AF1 32 /* Alt+F1键*/

#define AF2 33 /* Alt+F2键*/

#define AF3 34 /* Alt+F3键*/

#define AF4 35 /* Alt+F4键*/

/*以下4行为终端键盘功能键的值: */

#define TF1 77 /* F1键 */

#define TF2 78 /* F2键 */

#define TF3 79 /* F3键 */

#define TF4 80 /* F4键 */

#define ABORT 68 /* 放弃键 */

#define INSERT 65 /* 插入键 */

#define LENGTH 100 /* 命令行长度 */

#define LF 10 /* 换行符 */

#define BACKSP 8 /* 退格符 */

#define QUIT 4 /*退出键Ctrl+d*/

#define PROMT0 "# " /* 提示符 */

#define PROMT1 "$ " /* 提示符 */

#define ERROR "error,bad directory\n"

#define MESS "\n\nEnter terminal flag: "

char buff[LENGTH*2];

char *str1=buff;

char *str2=buff+LENGTH;

char *ii,*jj,*tmp;

char chr,flag0,flag1,flag2;

char f1,f2,f3,f4;

main(argc,argv)

int argc;

char *argv[];

{ /* main */

struct termio tdes;

char *getenv(char*);

char *ttyname(int);

char *pt;

tmp=ttyname(0);

for( ;*tmp;tmp++);

if (flag1=(*(--tmp)<'A')) {

f1=CF1;

f2=CF2;

f3=CF3;

f4=CF4;

}

else { /* else 0 */

if (argc==1){

write(1,MESS,23);

read(0,&flag2,1);

} else

flag2=*argv[1];

switch (flag2) { /*switch*/

case '0':

f1=TF1;

f2=TF2;

f3=TF3;

f4=TF4;

break;

case '1':

f1=PF1;

f2=PF2;

f3=PF3;

f4=PF4;

break;

case '2':

default:

f1=AF1;

f2=AF2;

f3=AF3;

f4=AF4;

break;

} /*switch*/

} /* else 0 */

if(!(pt=getenv("PS1")))

pt=getuid()?PROMT1:PROMT0;

do { /* do while 1 */

ioctl(0,TCGETA,&tdes);

tdes.c_lflag &=~ECHO;

tdes.c_lflag &=~ICANON;

tdes.c_cc[VMIN]=1;

tdes.c_cc[VTIME]=0;

ioctl(0,TCSETA,&tdes);

signal(SIGINT,SIG_IGN);

ii=str2; str2=str1;

str1=ii; jj=str2;

flag0=1;

write(1,pt,2);

do { /* do while2 */

read(0,&chr,1);

switch(chr)

{ /*switch1*/

case ESCAP:

funkey();

break;

case BACKSP:

if (ii>str1) {

ii--;

if (jj>str2)

jj--;

write(1,"\b \b",3);

}

break;

default:

*ii++=chr;

write(1,&chr,1);

if (flag0) jj++;

} /* switch 1 */

} /* do while2 */

while (chr!=LF && chr!=QUIT);

for (--ii;*ii;*ii++=0) ;

tdes.c_lflag |=ECHO;

tdes.c_lflag |=ICANON;

tdes.c_cc[VMIN]=4;

ioctl(0,TCSETA,&tdes);

signal(SIGINT,SIG_DFL);

tmp=str1;

for(;*tmp==' ';tmp++);

if (*tmp++=='c' && *tmp++=='d'

&& (*tmp==' ' || *tmp==0))

{

for(;*tmp==' ';tmp++);

if (!*tmp)

tmp=getenv("HOME");

if (chdir(tmp))

write(1,ERROR,20);

continue;

}

system(str1); /*执行用户命令*/

} while(chr!=QUIT); /*dowhlie1*/

} /* main */

funkey()

{ /* funkey */

read(0,&chr,1);

if (flag1 || (flag2=='1'))

read(0,&chr,1);

if (chr==f1) {

if (*jj) {

write(1,jj,1);

*ii++=*jj++;

}

return(0);

}

if (chr==f2) {

if(finchr())

do {

write(1,jj,1);

*ii++=*jj++;

}

while(jj<tmp);

return(0);

}

if (chr==f3) {

while ( *jj ) {

write(1,jj,1);

*ii++=*jj++;

}

return(0);

}

if (chr==f4) {

if(finchr()) jj=tmp;

return(0);

}

if (!flag1 && !(flag2=='1'))

read(0,&chr,1);

if (chr==ABORT) {

ii=str1; jj=str2;

write(1,"\\\n ",4);

return(0);

}

if (chr==INSERT)

flag0 ^= 1;

return(0);

} /* funkey */

finchr()

{

read(0,&chr,1);

tmp=jj;

do tmp++;

while (*tmp && *tmp!=chr);

return(*tmp);

}

特别申明

本栏目的文章都是本人从网上搜集而来,仅供大家学习研究之用,请不要用于商业目的!其中署名“佚名”的,意思是作者不详。如果某些文章未署你的名字,请来信告知,我会补上的。如果你认为某些文章侵犯了你的正当权宜,也请来信,我会将它删除。