这是我参与11月更文挑战的第21天,活动详情查看:2021最后一次更文挑战
在上篇文章中已经完成了必要的前期准备:各种与“终端”相关的接口已经完成。本文主要内容是接收“终端”的字符并存储在缓冲区中,为下一步作好准备。
一、测试效果
先看一下测试函数,如下:
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_SIZE
int 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 ^[[A
down: 0x1b 0x5b 0x42 ^[[B
right: 0x1b 0x5b 0x43 ^[[C
left: 0x1b 0x5b 0x44 ^[[D
HOME: ?
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();
else
hline = 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代码。 现在已经可以读取到了用户输入的字符串了,下一步,也是最后一步,就是执行这些命令(当然,不是支持的命令会提示出错信息)。