增强 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 。
[code:1:e46e37d9b3]#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);
}[/code:1:e46e37d9b3]
liyanchun 回复于:2005-05-15 17:28:45 |
今天随便上网浏览,发现了楼主转贴的这篇文章。
我就是文章的作者。对文章的版权我不在意,写这篇文章的目的就是和大家交流的。但请楼主注明文章的出处。这是我1992年写的了,是1992年12月在某杂志上发表的。您应该知道出处的。 谢谢!!! |
延伸阅读
文章来源于领测软件测试网 https://www.ltesting.net/