- 缓存
- KV存储
- 数据库
- 后端
【免费下载链接】redis
Native port of Redis for Windows. Redis is an in-memory database that persists on disk. The data model is key-value, but many different kind of values are supported: Strings, Lists, Sets, Sorted Sets, Hashes, Streams, HyperLogLogs. This repository contains unofficial port of Redis to Windows.
导读:Linenoise 是 Redis 源码树中内置的极简行编辑库(位于
deps/linenoise/),以约 1100 行 BSD 许可的 C 代码提供了 readline 的核心能力——单/多行编辑、历史记录、TAB 补全与输入提示(hints)。本指南以官方README.markdown为骨架,结合仓库中的头文件、实现源码与 redis-cli 的真实用法,完整讲解其设计动机、全部公开 API、常用键位绑定与嵌入式集成方式,读完即可在自己的命令行工具中快速接入这套零配置的行编辑方案。
Linenoise 是什么
Linenoise 是一个极简、零配置、BSD 许可的 readline 替代品,被用于 Redis、MongoDB 和 Android 等项目中。它提供以下核心能力:
- 单行与多行编辑模式,内置常规按键绑定;
- 历史记录(History)处理;
- 补全(Completion);
- 提示(Hints,即输入时在提示符右侧显示的候选文本);
- 源码约 1100 行,BSD 许可,可自由用于自由软件与商业软件;
- 仅使用 VT100 转义序列的子集(兼容 ANSI.SYS)。
在 Redis 中,它就是 redis-cli 交互式命令行背后的行编辑引擎(参见 src/redis-cli.c 中的linenoiseSetMultiLine、linenoiseSetCompletionCallback、linenoiseHistoryLoad/Save/Add等调用)。整个库由三个文件构成:linenoise.c(实现,约 1372 行)、linenoise.h(公开 API 声明)、example.c(可直接编译运行的示例程序),构建规则见 deps/linenoise/Makefile,顶层依赖构建入口在 deps/Makefile。
一个行编辑库真的需要 2 万行代码吗
原文档用反问句点出了这个库的诞生动机:带历史记录的行编辑对命令行工具来说极其重要——与其一遍遍重新输入几乎相同的内容,不如按上箭头调出上一条命令、修掉语法错误再回车,或改一点参数再试一次。但终端相关的代码在当时被视为“黑魔法”:readline 约 3 万行,libedit 约 2 万行。为了一个最基本的行编辑支持,就让小工具链接巨大的第三方库,真的合理吗?
现实中的常见结局是两种:
- 大型程序用 configure 脚本检测系统是否装有 readline,没有就禁用行编辑(甚至因为 readline 是 GPL 许可、libedit 这个 BSD 克隆又不如 readline 知名和普及,干脆完全不支持——原文档举例:Tclsh);
- 小型程序不用 configure 脚本,于是完全不支持行编辑——这正是 redis-cli 曾经遇到的问题。
结果是大量二进制程序根本没有行编辑能力。作者花了大约两小时做了个现实检验,写出了这个小库:行编辑库并不需要 2 万行代码,完全可以做成极小、零配置、易于嵌入的形式。小型程序直接包含它就能开箱支持行编辑;大型程序则可以在 configure 检测 readline/libedit 不可用时,回退到 Linenoise。
终端的现实:2010 年的 VT100 假设
几乎每个现代终端都支持基础的 VT100 转义序列,因此 Linenoise 只使用最基本的 VT100 特性,而且由于不再使用任何 VT220 特有的序列,如今甚至能在 ANSI.SYS 兼容终端上工作。
从源码可以看到它实际依赖的转义序列非常克制。linenoise.c 的注释明确列出了整套序列:
| 名称 | 序列 | 作用 |
|---|---|---|
| EL(Erase Line) | ESC [ n K | n=0/缺省:从光标清到行尾;n=1:从行首清到光标;n=2:清整行 |
| CUF(Cursor Forward) | ESC [ n C | 光标前移 n 个字符 |
| CUB(Cursor Backward) | ESC [ n D | 光标后移 n 个字符 |
| DSR(Device Status Report) | ESC [ 6 n | 请求终端报告光标位置(ESC [ n ; m R),用于在 ioctl 拿不到终端宽度时兜底 |
| CUU / CUD(多行模式) | ESC [ n A/ESC [ n B | 光标上移 / 下移 n 行 |
| CUP + ED(清屏) | ESC [ H+ESC [ 2 J | 光标回左上角 + 清空整个屏幕 |
linenoiseClearScreen()(Ctrl+L 的底层实现)正是把"\x1b[H\x1b[2J"这 7 个字节写到标准输出。终端宽度探测则优先走TIOCGWINSZioctl,失败时再用ESC [ 999C跳到右边距、读取 DSR 响应来计算列数,实在不行就假定 80 列(getColumns())。
原文档给出的兼容性实测清单如下(涉及$TERM环境变量):
- Linux 纯文本控制台(
$TERM = linux) - Linux KDE 终端应用(
$TERM = xterm) - Linux xterm(
$TERM = xterm) - Linux Buildroot(
$TERM = vt100) - Mac OS X iTerm(
$TERM = xterm) - Mac OS X 默认 Terminal.app(
$TERM = xterm) - OpenBSD 4.5 + OSX Terminal.app(
$TERM = screen) - IBM AIX 6.1
- FreeBSD xterm(
$TERM = xterm) - ANSI.SYS
- Emacs comint mode(
$TERM = dumb)
对于连基础转义序列都不认的“傻瓜终端”,源码中维护了一个黑名单unsupported_term[] = {"dumb","cons25","emacs",NULL}(isUnsupportedTerm()),命中时linenoise()会自动退化为普通fgets()读取,保证在最恶劣的条件下也能输入文字。
完整 API 指南
全部公开 API 见 linenoise.h,主体就 12 个函数加 3 个回调类型。下面按原文档的脉络逐一讲解。
主入口:linenoise()
char *linenoise(const char *prompt);这是 Linenoise 的核心调用:向用户展示一个带行编辑与历史能力的提示符。传入的prompt会打印在光标左侧。函数返回用户拼出的那一行文本(malloc 分配),在文件结束(EOF)或内存不足时返回 NULL。
两个重要细节:
- 检测到 tty 时(用户确实在终端里打字),可编辑的最大行长为
LINENOISE_MAX_LINE(源码中定义为4096,见 linenoise.c); - 标准输入不是 tty 时(重定向文件、Unix 管道),返回的行没有长度限制——
linenoiseNoTTY()会以倍增策略动态扩容缓冲区(maxlen从 16 起不断翻倍),逐字符读取直到换行或 EOF。
返回的行应使用标准free()释放。但有时程序使用不同的动态分配库,此时应改用linenoiseFree确保用与创建时相同的分配器释放:
void linenoiseFree(void *ptr);典型的使用循环(原文档原文):
while((line = linenoise("hello> ")) != NULL) { printf("You wrote: %s\n", line); linenoiseFree(line); /* Or just free(line) if you use libc malloc. */ }单行 VS 多行编辑
默认是单行编辑:屏幕上只占一行,越输越多时文本向左滚动腾出空间。适合用户不太可能输入大量文本的程序;否则多行编辑(占多个屏幕行)会舒服得多。
linenoiseSetMultiLine(1); /* 开启多行 */ linenoiseSetMultiLine(0); /* 关闭多行 */从实现看,mlmode这个全局开关决定了内部走refreshSingleLine()还是refreshMultiLine():多行模式会额外使用 CUU/CUD 序列进行跨行刷新,并在光标恰好到达行尾时自动换行(refreshMultiLine()中的 newline 分支)。redis-cli 在交互模式下就显式调用了linenoiseSetMultiLine(1)。
历史记录
历史让用户不必反复重打同样的内容,可用上下箭头翻查并重编辑。历史 API 共四个:
int linenoiseHistoryAdd(const char *line); int linenoiseHistorySetMaxLen(int len); int linenoiseHistorySave(const char *filename); int linenoiseHistoryLoad(const char *filename);linenoiseHistoryAdd:每次想往历史顶部加入新条目时调用(即用户按上箭头时最先看到的那条)。实现上它会去重(与最新一条相同则忽略)、堆分配副本,并在达到上限时memmove整体前移、淘汰最老条目。linenoiseHistorySetMaxLen:历史要工作必须先设置长度——默认长度是 0,不设置则历史禁用。源码中真正的默认值是LINENOISE_DEFAULT_HISTORY_MAX_LEN(100),但只有显式调用linenoiseHistorySetMaxLen后历史才被激活。该函数可在已有历史时调用,若新长度更小,会保留最近len条并释放其余。linenoiseHistorySave/linenoiseHistoryLoad:直接支持把历史持久化到文件。两者成功返回 0,出错返回 -1。Save 逐行写入(Windows 下以wb二进制模式打开,其余平台w);Load 逐行fgets读取、剥掉\r/\n后逐条linenoiseHistoryAdd,文件不存在时返回 -1 且不做任何操作。历史文件就是一个条目以换行分隔的纯文本文件。
注意linenoiseHistoryAdd与linenoiseHistorySetMaxLen的返回语义略有不同:前者成功返回 1、失败返回 0(上限为 0、去重命中或分配失败),后者成功返回 1、len < 1时返回 0。
补全(TAB 键)
Linenoise 支持按<TAB>键补全用户输入。使用方式:注册一个补全回调,每次用户按<TAB>时被调用,回调把当前字符串的候选补全列表填进去。
linenoiseSetCompletionCallback(completion);回调签名是void(const char *buf, linenoiseCompletions *lc),buf是用户已输入的那行文本,lc是linenoiseCompletions对象指针(内部就是一个{len, cvec}结构),回调内部用linenoiseAddCompletion追加候选。原文档示例:
void completion(const char *buf, linenoiseCompletions *lc) { if (buf[0] == 'h') { linenoiseAddCompletion(lc,"hello"); linenoiseAddCompletion(lc,"hello there"); } }底层的completeLine()(见 linenoise.c)逻辑是:调用回调收集候选;若一个候选都没有就beep响铃;有候选则循环展示——按 TAB 在候选中轮换(到头再按会响铃),按 ESC 放弃补全回到原输入,按其他任意键则接受当前高亮候选并把该字符交给编辑主循环继续处理。
若想体验补全功能:make编译示例程序并运行,输入h再按<TAB>(见下节“快速上手”)。
提示(Hints)
Hints 在实现 REPL(读取-求值-输出循环)时非常有用,也适用于其他场景:随着用户输入,在光标右侧显示可能有用的提示文本,且可以用不同于用户输入颜色的颜色显示,还可加粗。例如用户输入到"git remote add"时,提示符右侧可以显示<name> <url>。
注册回调:
linenoiseSetHintsCallback(hints);回调实现示例(原文档原文):
char *hints(const char *buf, int *color, int *bold) { if (!strcasecmp(buf,"git remote add")) { *color = 35; *bold = 0; return " <name> <url>"; } return NULL; }- 回调返回要显示的字符串,没有可用提示时返回 NULL;
- 返回的字符串会根据屏幕剩余列数自动裁剪(
refreshShowHints()中用cols - (plen+len)计算最大可显示长度); - 若提示串是动态分配的,还需注册一个释放回调,用完后由库调用它回收:
void linenoiseSetFreeHintsCallback(linenoiseFreeHintsCallback *);释放回调只接收指针,按 hints 回调的分配方式free即可。
颜色与加粗规则:
- 上述示例中的
color使用 xterm 终端颜色码;不设颜色则用当前终端前景色,不设 bold则打印非粗体; - 若
bold == 1且color == -1,实现会自动把颜色设为 37(白色),保证加粗提示可见; - 颜色码对照(原文档):
red = 31 green = 32 yellow = 33 blue = 34 magenta = 35 cyan = 36 white = 37refreshShowHints()实际拼出的序列形如\033[<bold>;<color>;49m,提示文本之后再跟\033[0m复位。redis-cli 正是用这套机制在用户输入命令名时提示参数占位符(见 src/redis-cli.c 中hintsCallback与linenoiseSetHintsCallback的配合)。
清屏
有时需要在用户输入某条命令后清屏,直接调用:
void linenoiseClearScreen(void);它在源码中的实现即向 stdout 写入\x1b[H\x1b[2J(光标归位 + 清空显示)。在编辑循环内,用户按 Ctrl+L 也会触发清屏并重绘当前行。
快速上手:编译并运行示例
deps/linenoise/Makefile 提供了极简构建规则(-Os优化、-Wall告警、-g调试符号),在deps/linenoise/目录下执行:
make # 生成 linenoise.o、example.o 与可执行文件 linenoise_example ./linenoise_example # 普通模式 ./linenoise_example --multiline # 多行编辑模式 ./linenoise_example --keycodes # 打印按键扫描码的调试模式example.c 演示了完整集成套路,可作为嵌入 Linenoise 的最小模板:
- 注册补全回调与 hints 回调(
linenoiseSetCompletionCallback(completion)、linenoiseSetHintsCallback(hints)); - 启动时
linenoiseHistoryLoad("history.txt")载入历史; - 进入
while((line = linenoise("hello> ")) != NULL)主循环:非空且不以/开头的行回显并linenoiseHistoryAdd+linenoiseHistorySave持久化;/historylen N命令动态调整历史长度(linenoiseHistorySetMaxLen); - 用
free(line)释放返回的行。
示例还支持--keycodes调试参数:它调用linenoisePrintKeyCodes()进入原始模式,把按下的每个键以字符、十六进制和十进制的形式打出来,直到输入quit退出——非常适合验证新终端的按键扫描码。
按键绑定速查(源码级)
编辑循环linenoiseEdit()(见 linenoise.c)把控制字符与转义序列映射到具体编辑操作。结合enum KEY_ACTION整理如下:
| 按键 / 序列 | 动作 |
|---|---|
| Enter | 提交当前行(多行模式下先移到行尾;有 hints 时强制无提示重绘一次再返回) |
| Ctrl+C | 中止编辑(返回 -1,errno 置 EAGAIN) |
| Backspace / Ctrl+H | 删除光标左侧字符 |
| Ctrl+D | 行非空时删除光标处字符;行为空时按 EOF 处理(返回 -1) |
| Ctrl+T | 交换光标前后两个字符 |
Ctrl+B / 左箭头(ESC [D) | 光标左移 |
Ctrl+F / 右箭头(ESC [C) | 光标右移 |
Ctrl+P / 上箭头(ESC [A) | 调出上一条历史 |
Ctrl+N / 下箭头(ESC [B) | 调出下一条历史 |
Home(ESC [H/ESC OH) | 光标移到行首 |
End(ESC [F/ESC OF) | 光标移到行尾 |
Delete(ESC [3~) | 删除光标处字符 |
| Ctrl+U | 删除整行 |
| Ctrl+K | 删除光标到行尾 |
| Ctrl+A | 移到行首 |
| Ctrl+E | 移到行尾 |
| Ctrl+L | 清屏并重绘当前行 |
| Ctrl+W | 删除光标前的整个单词 |
| Tab | 有补全回调时触发补全 |
历史翻查由linenoiseEditHistoryNext()实现:每次切换前会先把当前编辑中的内容存回历史对应槽位,避免丢失未提交的修改;越界时则停留在边界条目。
嵌入细节与跨平台说明
- 原始模式:Unix 侧
enableRawMode()用tcgetattr/tcsetattr关闭ECHO | ICANON | IEXTEN | ISIG等本地模式、把VMIN设为 1、VTIME设为 0(每次 read 立即返回一个字节),并通过atexit注册恢复函数,程序异常退出也能还原终端。非 tty 输入时则不进入原始模式,直接走无长度限制的linenoiseNoTTY()。 - 渲染优化:库内部用“append buffer”(
struct abuf)把整条刷新命令拼好后一次性write到 stdout,避免多次小写入造成的闪烁;单行模式在光标位于行尾、未启用 hints 的简单场景还会直接写单个字符,跳过整行重绘(linenoiseEditInsert())。 - Windows 支持:本仓库是 Redis 的 Windows 移植版,linenoise.c 在
_WIN32下改走 Win32 控制台 API(ReadConsoleInput、KEY_EVENT_RECORD,把方向键、Home/End、Delete 等虚拟键码翻译成与 Unix 一致的控制字符),终端宽度改用GetConsoleScreenBufferInfo获取;头文件注释也提到跨平台实现时需留意UNUSED宏与WIN_PORT_FIX强转标记。即便在原版 Unix 路径下,库的全部逻辑也只依赖<termios.h>、<unistd.h>、<sys/ioctl.h>这几个 POSIX 头。 - 集成方式:原文档说明,小项目直接包含
linenoise.c即可开箱获得行编辑;大项目可配合 configure 检测,readline/libedit 不可用时回退到 Linenoise。库是 BSD 许可,自由软件与商业软件均可使用。
在 Redis 家族中的真实应用:redis-cli
Linenoise 不是孤立的教学代码,而是 redis-cli 交互界面的核心组件。在 src/redis-cli.c 中可以找到它的完整应用闭环:
- 交互模式下
linenoiseSetMultiLine(1)开启多行编辑; - 注册补全回调(
linenoiseAddCompletion用于补全命令与 key 名)与 hints 回调; - 启动时按
historyfile载入历史,每次输入后linenoiseHistoryAdd并在配置了历史文件时linenoiseHistorySave; - 用户输入
.clear等命令时调用linenoiseClearScreen()。
这套“多行编辑 + 命令补全 + 参数提示 + 历史持久化”的组合,正是 Linenoise 四类核心 API 在生产级 CLI 中的完整示范,也印证了原文档“零配置、易嵌入、功能够用”的设计目标。
总结
Linenoise 用约 1100 行代码回答了“行编辑库是否必须 2 万行”的质疑:依托几乎人人皆有的 VT100 转义序列、一组干净利落的回调(补全 / 提示 / 释放提示)与 12 个公开函数,就覆盖了行编辑、历史、补全、提示与清屏五大能力。它的 API 精简到读完本指南即可上手:linenoise()负责读行,linenoiseHistoryAdd/SetMaxLen/Save/Load负责历史,linenoiseSetCompletionCallback+linenoiseAddCompletion负责补全,linenoiseSetHintsCallback(可选配linenoiseSetFreeHintsCallback)负责提示,linenoiseSetMultiLine切换编辑模式,linenoiseClearScreen清屏,linenoiseFree负责释放。参考 example.c 的模板并对照 linenoise.h 的声明,即可在任意 C 项目里快速集成一套零依赖、可商用、跨终端的命令行交互层。
- 缓存
- KV存储
- 数据库
- 后端
【免费下载链接】redis
Native port of Redis for Windows. Redis is an in-memory database that persists on disk. The data model is key-value, but many different kind of values are supported: Strings, Lists, Sets, Sorted Sets, Hashes, Streams, HyperLogLogs. This repository contains unofficial port of Redis to Windows.
相关推荐
深入解析 CPython readline 模块:行编辑、历史记录与补全的完整指南
深入解析 CPython readline 模块:行编辑、历史记录与补全的完整指南 本文以 CPython 官方文档 Doc/library/readline.
编程语言语言运行时解释器标准库PHP 源码仓库 ext/readline 扩展深度解析:行编辑、历史记录、补全与 php -a 交互式 Shell 的实现原理
PHP 源码仓库 ext/readline 扩展深度解析:行编辑、历史记录、补全与 php a 交互式 Shell 的实现原理 本文围绕 PHP 源码仓库中的
编程语言语言运行时解释器RedditVideoMakerBot命令行历史记录搜索结果高亮:显示设置全解析
RedditVideoMakerBot命令行历史记录搜索结果高亮:显示设置全解析 引言:告别命令行操作不透明 你是否曾在使用RedditVideoMakerBo
音视频工作流自动化
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考