ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Redis 内置命令行编辑库 Linenoise 深度指南:API、历史记录、补全与提示(附源码解析)

Redis 内置命令行编辑库 Linenoise 深度指南:API、历史记录、补全与提示(附源码解析) 缓存KV存储数据库后端【免费下载链接】redisNative 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.项目地址https://gitcode.com/gh_mirrors/redis1/redis点击查看免费下载导读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 的注释明确列出了整套序列名称序列作用ELErase LineESC [ n Kn0/缺省从光标清到行尾n1从行首清到光标n2清整行CUFCursor ForwardESC [ n C光标前移 n 个字符CUBCursor BackwardESC [ n D光标后移 n 个字符DSRDevice Status ReportESC [ 6 n请求终端报告光标位置ESC [ n ; m R用于在 ioctl 拿不到终端宽度时兜底CUU / CUD多行模式ESC [ n A/ESC [ n B光标上移 / 下移 n 行CUP ED清屏ESC [ HESC [ 2 J光标回左上角 清空整个屏幕linenoiseClearScreen()CtrlL 的底层实现正是把\x1b[H\x1b[2J这 7 个字节写到标准输出。终端宽度探测则优先走TIOCGWINSZioctl失败时再用ESC [ 999C跳到右边距、读取 DSR 响应来计算列数实在不行就假定 80 列getColumns()。原文档给出的兼容性实测清单如下涉及$TERM环境变量Linux 纯文本控制台$TERM linuxLinux KDE 终端应用$TERM xtermLinux xterm$TERM xtermLinux Buildroot$TERM vt100Mac OS X iTerm$TERM xtermMac OS X 默认 Terminal.app$TERM xtermOpenBSD 4.5 OSX Terminal.app$TERM screenIBM AIX 6.1FreeBSD xterm$TERM xtermANSI.SYSEmacs 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_LEN100但只有显式调用linenoiseHistorySetMaxLen后历史才被激活。该函数可在已有历史时调用若新长度更小会保留最近len条并释放其余。linenoiseHistorySave/linenoiseHistoryLoad直接支持把历史持久化到文件。两者成功返回 0出错返回 -1。Save 逐行写入Windows 下以wb二进制模式打开其余平台wLoad 逐行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见下节“快速上手”。提示HintsHints 在实现 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 - (plenlen)计算最大可显示长度若提示串是动态分配的还需注册一个释放回调用完后由库调用它回收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光标归位 清空显示。在编辑循环内用户按 CtrlL 也会触发清屏并重绘当前行。快速上手编译并运行示例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)主循环非空且不以/开头的行回显并linenoiseHistoryAddlinenoiseHistorySave持久化/historylen N命令动态调整历史长度linenoiseHistorySetMaxLen用free(line)释放返回的行。示例还支持--keycodes调试参数它调用linenoisePrintKeyCodes()进入原始模式把按下的每个键以字符、十六进制和十进制的形式打出来直到输入quit退出——非常适合验证新终端的按键扫描码。按键绑定速查源码级编辑循环linenoiseEdit()见 linenoise.c把控制字符与转义序列映射到具体编辑操作。结合enum KEY_ACTION整理如下按键 / 序列动作Enter提交当前行多行模式下先移到行尾有 hints 时强制无提示重绘一次再返回CtrlC中止编辑返回 -1errno 置 EAGAINBackspace / CtrlH删除光标左侧字符CtrlD行非空时删除光标处字符行为空时按 EOF 处理返回 -1CtrlT交换光标前后两个字符CtrlB / 左箭头ESC [D光标左移CtrlF / 右箭头ESC [C光标右移CtrlP / 上箭头ESC [A调出上一条历史CtrlN / 下箭头ESC [B调出下一条历史HomeESC [H/ESC OH光标移到行首EndESC [F/ESC OF光标移到行尾DeleteESC [3~删除光标处字符CtrlU删除整行CtrlK删除光标到行尾CtrlA移到行首CtrlE移到行尾CtrlL清屏并重绘当前行CtrlW删除光标前的整个单词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 控制台 APIReadConsoleInput、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-cliLinenoise 不是孤立的教学代码而是 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负责历史linenoiseSetCompletionCallbacklinenoiseAddCompletion负责补全linenoiseSetHintsCallback可选配linenoiseSetFreeHintsCallback负责提示linenoiseSetMultiLine切换编辑模式linenoiseClearScreen清屏linenoiseFree负责释放。参考 example.c 的模板并对照 linenoise.h 的声明即可在任意 C 项目里快速集成一套零依赖、可商用、跨终端的命令行交互层。赞分享缓存KV存储数据库后端【免费下载链接】redisNative 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.项目地址https://gitcode.com/gh_mirrors/redis1/redis点击查看免费下载相关推荐深入解析 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),仅供参考
返回列表