• 软件测试技术
  • 软件测试博客
  • 软件测试视频
  • 开源软件测试技术
  • 软件测试论坛
  • 软件测试沙龙
  • 软件测试资料下载
  • 软件测试杂志
  • 软件测试人才招聘
    暂时没有公告

字号: | 推荐给好友 上一篇 | 下一篇

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

发布: 2007-6-08 22:43 | 作者: seanhe | 来源: | 查看: 34次 | 进入软件测试论坛讨论

领测软件测试网
增强 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/


关于领测软件测试网 | 领测软件测试网合作伙伴 | 广告服务 | 投稿指南 | 联系我们 | 网站地图 | 友情链接
版权所有(C) 2003-2010 TestAge(领测软件测试网)|领测国际科技(北京)有限公司|软件测试工程师培训网 All Rights Reserved
北京市海淀区中关村南大街9号北京理工科技大厦1402室 京ICP备10010545号-5
技术支持和业务联系:info@testage.com.cn 电话:010-51297073

软件测试 | 领测国际ISTQBISTQB官网TMMiTMMi认证国际软件测试工程师认证领测软件测试网