在上篇文章中已经完成了必要的前期准备:各种与“终端”相关的接口已经完成。本文主要内容是接收“终端”的字符并存储在缓冲区中,为下一步作好准备。同时也涉及到“终端”按键的处理,以弥补约2年前定的一篇文章的不足。

一、测试效果

先看一下测试函数,如下:

int readline_test(void)
{static char lastcommand[CB_SIZE] = {0};int len;while (1){len = readline(PROMPT, lastcommand);if (len > 0){if (len > CB_SIZE){myprintf("command line too large.\n");break;}}else if (len == 0){// do nothing }if (len == -1){myputs("\n");}else{myprintf("YOUR INPUT: %s\n", lastcommand);}}return 0;
}

效果如下:

NotAShell> hello world!
YOUR INPUT: hello world!
NotAShell> help
YOUR INPUT: help
NotAShell> clear
YOUR INPUT: clear
NotAShell>

二、实现

下面逐一介绍其实现。

2.1 函数声明及宏定义相关:

#define PROMPT          "NotAShell> "
#define CB_SIZE         256  /* console buffer size */
#define MAX_CMDBUF_SIZE CB_SIZE/* history command */
#define HIST_MAX 10
#define HIST_SIZE MAX_CMDBUF_SIZEint readline (const char *const prompt, char* line_buf);

这里定义了“终端”提示符,缓冲区大小,保存的历史命令最大数,等等。

2.2 ASCII码定义及光标控制相关:

#define CTL_CH(c)           ((c) - 'a' + 1)#define CTL_BACKSPACE       ('\b')
#define DEL                 ((char)255)
#define DEL7                ((char)127)
#define BACKSPACE           ((char)8)
#define CREAD_HIST_CHAR     ('!')#define putnstr(str,n)    do {          \getcmd_printf ("%.*s", (int)n, str);\
} while (0)// 将光标移到命令行首
#define BEGINNING_OF_LINE() {       \while (num) {                   \getcmd_putch(CTL_BACKSPACE);    \num--;                          \}                               \
}// 删除当前光标到行尾所有字符
#define ERASE_TO_EOL() {                     \if (num < eol_num) {                     \getcmd_printf("%*s", (int)(eol_num - num), ""); \do {                                     \getcmd_putch(CTL_BACKSPACE);             \} while (--eol_num > num);               \}                                        \
}// 将光标移到命令行尾
#define REFRESH_TO_EOL() {      \if (num < eol_num) {        \wlen = eol_num - num;       \putnstr(buf + num, wlen);   \num = eol_num;              \}                           \
}

上面最重要的一个宏定义是CTL_CH,它用于判断用户输入的按键组合,代码中处理的按键有:

0)、上下左右方向键
1)、c-a: 移到命令行首
2)、c-e: 移到命令行尾
3)、c-f: 前移一字符
4)、c-b: 后移一字符
5)、c-x/c-u: 删除整行
6)、c-d: 删除光标当前字符
7)、c-k: 删除当前光标至行尾字符
8)、c-p: 前一条历史命令
9)、c-n: 后一条历史命令
10)、HOME/END: 移至行首/行尾
11)、Delete:Windows-删除当前光标字符 Linux-删除光标前字符

很多快捷键与GNU Readline库相同。实际中,Linux有很多工具、软件都使用了Readline库,像bash shell,因此,你可以在bash shell使用c-a将光标移到行首,c-e移到行尾——这一点在一个长命令中尤其有用。更多关于Readline库的快捷键,请在搜索引擎中使用关键字“Readline shortcuts”进行搜索。
上面提到的c-a等就是组合按键,c表示Ctrl键,c-a表示同时按Ctrl键和a键,从ASCII码表(请搜索多一些ASCII码表,最好用google,或者搜索笔者写的文章《u-boot移植随笔:u-boot shell与ASCII码)中可以知道,c-a的ASCII码是1,即:CTL_CH(‘a’) = ‘a’ - ‘a’ + 1。

3.3 处理普通字符

static void cread_add_char(char ichar, int insert, unsigned long *num,unsigned long *eol_num, char *buf, unsigned long len)
{unsigned long wlen;/* room ??? */if (insert || *num == *eol_num){if (*eol_num > len - 1){getcmd_cbeep();return;}(*eol_num)++;}if (insert){wlen = *eol_num - *num;if (wlen > 1){memmove(&buf[*num+1], &buf[*num], wlen-1);}buf[*num] = ichar;putnstr(buf + *num, wlen);(*num)++;while (--wlen){getcmd_putch(CTL_BACKSPACE);}}else{/* echo the character */wlen = 1;buf[*num] = ichar;putnstr(buf + *num, wlen);(*num)++;}
}static void cread_add_str(char *str, int strsize, int insert, unsigned long *num,unsigned long *eol_num, char *buf, unsigned long len)
{while (strsize--){cread_add_char(*str, insert, num, eol_num, buf, len);str++;}
}

4.4 关键处理函数

这部分代码比较长,所以先说关键代码。
处理按键,包括方向键及HOME、END等,Windows及Linux按键如下:

       Windows: up:     0xe0 'H'down:   0xe0 'P'right:  0xe0 'M'left:   0xe0 'K'HOME:   0xe0 'G'END:    0xe0 'O'(大写字母O)Delete: 0xe0 'S'Linux:up:     0x1b 0x5b 0x41  ^[[Adown:   0x1b 0x5b 0x42  ^[[Bright:  0x1b 0x5b 0x43  ^[[Cleft:   0x1b 0x5b 0x44  ^[[DHOME:   ?END:    ? Delete: 0x7f

处理代码如下:

static int cread_line(const char *const prompt, char *buf, unsigned int *len)
{unsigned long num = 0;unsigned long eol_num = 0;unsigned long wlen;unsigned char ichar;int insert = 1;int esc_len = 0;char esc_save[8];int init_len = (int)strlen(buf);int cur_num = 0;if (init_len)cread_add_str(buf, init_len, 1, &num, &eol_num, buf, *len);while (1){ichar = getcmd_getch();if ((ichar == '\n') || (ichar == '\r')){getcmd_putch('\n');break;}/** handle Windows arrow key, etc.* note: arrow keys have two char, the first is 0xe0*/if (ichar == 0xe0){ichar = getcmd_getch();switch(ichar){case 'H':    /* up arrow */ichar = CTL_CH('p');break;case 'P':    /* down arrow */ichar = CTL_CH('n');break;case 'K':    /* left arrow */ichar = CTL_CH('b');break;case 'M':    /* right arrow */ichar = CTL_CH('f');break;case 'G':    /* Home */ichar = CTL_CH('a');break;case 'O':    /* End */ichar = CTL_CH('e');break;case 'S':    /* Delete */ichar = CTL_CH('d');break;default:break;}}/** handle standard linux xterm esc sequences for arrow key, etc.*/if (esc_len != 0){if (esc_len == 1){if (ichar == '['){esc_save[esc_len] = ichar;esc_len = 2;}else{cread_add_str(esc_save, esc_len, insert,&num, &eol_num, buf, *len);esc_len = 0;}continue;}switch (ichar){case 'D':     /* <- key */ichar = CTL_CH('b');esc_len = 0;break;case 'C':     /* -> key */ichar = CTL_CH('f');esc_len = 0;break;     /* pass off to ^F handler */case 'H':     /* Home key */ichar = CTL_CH('a');esc_len = 0;break;     /* pass off to ^A handler */case 'A':     /* up arrow */ichar = CTL_CH('p');esc_len = 0;break;     /* pass off to ^P handler */case 'B':     /* down arrow */ichar = CTL_CH('n');esc_len = 0;break;     /* pass off to ^N handler */default:esc_save[esc_len++] = ichar;cread_add_str(esc_save, esc_len, insert,&num, &eol_num, buf, *len);esc_len = 0;continue;}}/* End of linux arrow key */switch (ichar){/* linux esc sequences */case 0x1b:if (esc_len == 0){esc_save[esc_len] = ichar;esc_len = 1;}else{getcmd_puts("impossible condition #876\n");esc_len = 0;}break;case CTL_CH('a'):BEGINNING_OF_LINE();break;case CTL_CH('c'):     /* ^C - break */*buf = '\0';     /* discard input */return (-1);case CTL_CH('f'):if (num < eol_num){getcmd_putch(buf[num]);num++;}break;case CTL_CH('b'):if (num){getcmd_putch(CTL_BACKSPACE);num--;}break;case CTL_CH('d'):if (num < eol_num){wlen = eol_num - num - 1;if (wlen){memmove(&buf[num], &buf[num+1], wlen);putnstr(buf + num, wlen);}getcmd_putch(' ');do {getcmd_putch(CTL_BACKSPACE);} while (wlen--);eol_num--;}break;case CTL_CH('k'):ERASE_TO_EOL();break;case CTL_CH('e'):REFRESH_TO_EOL();break;case CTL_CH('o'):insert = !insert;break;case CTL_CH('x'):case CTL_CH('u'):BEGINNING_OF_LINE();ERASE_TO_EOL();break;case DEL:case DEL7:case BACKSPACE:if (num){wlen = eol_num - num;num--;memmove(&buf[num], &buf[num+1], wlen);getcmd_putch(CTL_BACKSPACE);putnstr(buf + num, wlen);getcmd_putch(' ');do {getcmd_putch(CTL_BACKSPACE);} while (wlen--);eol_num--;}break;case CTL_CH('p'):case CTL_CH('n'):{char * hline;esc_len = 0;if (ichar == CTL_CH('p'))hline = hist_prev();elsehline = hist_next();if (!hline){getcmd_cbeep();continue;}/* nuke the current line *//* first, go home */BEGINNING_OF_LINE();/* erase to end of line */ERASE_TO_EOL();/* copy new line into place and display */strcpy(buf, hline);eol_num = (unsigned long)strlen(buf);REFRESH_TO_EOL();continue;}default:cread_add_char(ichar, insert, &num, &eol_num, buf, *len);break;}}*len = eol_num;buf[eol_num] = '\0';     /* lose the newline */if (buf[0] && buf[0] != CREAD_HIST_CHAR)cread_add_to_hist(buf);hist_cur = hist_add_idx;return 0;
}

完整处理代码请参考工程代码,或者参考uboot代码。
现在已经可以读取到了用户输入的字符串了,下一步,也是最后一步,就是执行这些命令(当然,不是支持的命令会提示出错信息)。

李迟 2020.9.30

“命令终端”的实现2-字符读取及按键控制相关推荐

  1. “命令终端”的实现1-准备篇

    李迟注: 这几篇文章写于2012年底,因故未发表,前不久,音视频群里的树哥询问一个技术方案,想到以前曾经实现过,就把工程发给他.现在发表出来,除修正个别严重的病句外,其它没有修改.从行文看,还有很大提 ...

  2. angular过滤字符_如何使用Angular和Azure计算机视觉创建光学字符读取器

    angular过滤字符 介绍 (Introduction) In this article, we will create an optical character recognition (OCR) ...

  3. Linux 的命令终端(CMD)的快捷键(Keyboard of MacBook)

    文章目录 常用 移动光标 编辑命令 查找历史命令 控制命令 命令终端界面滚屏 命令终端页签切换 奇葩 常用 快捷键 说明 Ctrl + A 光标跳到本行的行首 Ctrl + E 光标跳到本行的行尾 C ...

  4. Windows 命令终端(CMD)的快捷键

    这些快捷键只在Windows系统操作有效,连接远程Linux主机,再操作这些快捷键是无效的,因为连接远程的Linux主机后,你用的是Linux命令终端. 快捷键: F1:按F1逐字显示最后一次执行的命 ...

  5. “命令终端”的实现3-命令的执行

    前面已经能获取到输入的字符了,接着就是解析这些字符,判断,符合要求的,执行对应的函数.而对应的函数,就是需要实现的命令.本文从具体的命令实现逐步倒推,最后对接上一文章. 一.命令函数 Linux 中, ...

  6. Linux命令——终端格式命令,两种help 方法查找命令使用方式

    1.终端命令格式 command [-options] [parameter] 举个例子: 像前面的6 个命令,它们只用一个字符就可以完成工作, ls--列出当前目录的文件夹, pwd--显示当前路径 ...

  7. Ubuntu命令终端查看使用过的命令

    使用history命令 cyf@ubuntu:~$ history 但是这样会显示出所有使用过的命令,可以在history后加上less cyf@ubuntu:~$ history | less 会显 ...

  8. 编程模板-R语言脚本写作:最简单的统计与绘图,包安装、命令行参数解析、文件读取、表格和矢量图输出

    写在前面 个人认为:是否能熟悉使用Shell(项目流程搭建)+R(数据统计与可视化)+Perl/Python等(胶水语言,数据格式转换,软件间衔接)三门语言是一位合格生物信息工程师的标准. 之前分享过 ...

  9. php tr 用法,Linux_Linux系统中tr命令删除和替换文本字符的基本用法,通过使用 tr,您可以非常容易 - phpStudy...

    Linux系统中tr命令删除和替换文本字符的基本用法 通过使用 tr,您可以非常容易地实现 sed 的许多最基本功能.您可以将 tr 看作为 sed 的(极其)简化的变体:它可以用一个字符来替换另一个 ...

最新文章

  1. iOS 自定义返回按钮,保留系统滑动返回
  2. 微软全球副总裁洪小文:应对数字化转型挑战,跨界共创正当时
  3. 算法----------字符串相乘(Java 版本)
  4. python绘制一个圆_Python在网格上绘制一个填充的“圆”
  5. android高德自定义图标,Android 高德地图显示在线图标
  6. 《网易编程题》下厨房
  7. 实话实说:中文自然语言处理的N个真实情况
  8. 《Cracking the Coding Interview》——第9章:递归和动态规划——题目8
  9. Java基础学习总结(106)——高级JAVA工程师必需技能
  10. [转载] LeetCode题解(面试16.22):兰顿蚂蚁(Python)
  11. 我是做Java的,刚入职了,月薪20k,面试题还是很管用的
  12. sigar 监控服务器硬件信息
  13. 俄亥俄州立大学哥伦布分校计算机科学,美国俄亥俄州立大学哥伦布分校计算机科学与工程硕士专业入学要求精选.pdf...
  14. CATIA二次开发——元素隐藏
  15. Linux系统进程优化理论与方法
  16. 如何将eclipse项目和svn关联(从服务器取项目)
  17. 品牌的成功取决于质量的好坏
  18. HTML tabindex用法及使用场景详解
  19. 在Caffe的训练过程中打印验证集的预测结果
  20. 善用并行,让构建倍速进行

热门文章

  1. 小米12系列旗舰最新爆料:内藏5000mAh电池但机身更薄
  2. 欧菲光修正2020年业绩为预计亏损18.5亿元
  3. iQOO Neo5入网:搭载双电芯方案 标配66W快充
  4. 三星发布110寸大屏MicroLED面板电视
  5. 李想:欣赏特斯拉 但更喜欢苹果、乔布斯
  6. 苹果首款自研芯片Mac成本可能上升 因设计改变
  7. 一加手机回应“滤镜透视”争议:为消除担忧 暂时禁用该滤镜
  8. 中国移动老功臣退休致辞:工作结束了 人生没结束
  9. 阿里第一颗芯片问世!平头哥发布含光800:全球最强
  10. 新一代“土豪专享”机来了!三星W2020通过WiFi联盟认证