Redis 7.0.5 源码阅读笔记:基于linenoise 的 redis-client 命令行实现
Redis162 字
redis-client 提供了类似于与 mysql 的交互式命令行工具,我们输入命令,redis-client 会将我们的命令发送给 redis-server 然后返回取到的值。
linenoise 是非常轻量级的命令行处理工具,除了 redis 还有很著名的开源软件在使用 linenoise 其中就包括 MongoDB 和 Android 都将 Linenoise 作为命令行解析工具,那么今天我们就来解锁这个开源的命令行处理工具,也许某一天在你的项目里会派上用场。
其使用方法我们可以参考这篇文章:https://zhuanlan.zhihu.com/p/103845625
其源代码被 redis 放在 deps 目录下。
redis 对齐的调用封装在 src/redis-cli.c 中的 repl 函数中.
static void repl(void) {
sds historyfile = NULL;
int history = 0;
char *line;
int argc;
sds *argv;
/* There is no need to initialize redis HELP when we are in lua debugger mode.
* It has its own HELP and commands (COMMAND or COMMAND DOCS will fail and got nothing).
* We will initialize the redis HELP after the Lua debugging session ended.*/
if (!config.eval_ldb) {
/* Initialize the help using the results of the COMMAND command. */
cliInitHelp();
}
config.interactive = 1;
linenoiseSetMultiLine(1);
linenoiseSetCompletionCallback(completionCallback);
linenoiseSetHintsCallback(hintsCallback);
linenoiseSetFreeHintsCallback(freeHintsCallback);
/* Only use history and load the rc file when stdin is a tty. */
if (isatty(fileno(stdin))) {
historyfile = getDotfilePath(REDIS_CLI_HISTFILE_ENV,REDIS_CLI_HISTFILE_DEFAULT);
//keep in-memory history always regardless if history file can be determined
history = 1;
if (historyfile != NULL) {
linenoiseHistoryLoad(historyfile);
}
cliLoadPreferences();
}
cliRefreshPrompt();
while((line = linenoise(context ? config.prompt : "not connected> ")) != NULL) {
if (line[0] != '\0') {
long repeat = 1;
int skipargs = 0;
char *endptr = NULL;
argv = cliSplitArgs(line,&argc);
if (argv == NULL) {
printf("Invalid argument(s)\n");
fflush(stdout);
if (history) linenoiseHistoryAdd(line);
if (historyfile) linenoiseHistorySave(historyfile);
linenoiseFree(line);
continue;
} else if (argc == 0) {
sdsfreesplitres(argv,argc);
linenoiseFree(line);
continue;
}
/* check if we have a repeat command option and
* need to skip the first arg */
errno = 0;
repeat = strtol(argv[0], &endptr, 10);
if (argc > 1 && *endptr == '\0') {
if (errno == ERANGE || errno == EINVAL || repeat <= 0) {
fputs("Invalid redis-cli repeat command option value.\n", stdout);
sdsfreesplitres(argv, argc);
linenoiseFree(line);
continue;
}
skipargs = 1;
} else {
repeat = 1;
}
if (!isSensitiveCommand(argc - skipargs, argv + skipargs)) {
if (history) linenoiseHistoryAdd(line);
if (historyfile) linenoiseHistorySave(historyfile);
}
if (strcasecmp(argv[0],"quit") == 0 ||
strcasecmp(argv[0],"exit") == 0)
{
exit(0);
} else if (argv[0][0] == ':') {
cliSetPreferences(argv,argc,1);
sdsfreesplitres(argv,argc);
linenoiseFree(line);
continue;
} else if (strcasecmp(argv[0],"restart") == 0) {
if (config.eval) {
config.eval_ldb = 1;
config.output = OUTPUT_RAW;
sdsfreesplitres(argv,argc);
linenoiseFree(line);
return; /* Return to evalMode to restart the session. */
} else {
printf("Use 'restart' only in Lua debugging mode.\n");
fflush(stdout);
}
} else if (argc == 3 && !strcasecmp(argv[0],"connect")) {
sdsfree(config.conn_info.hostip);
config.conn_info.hostip = sdsnew(argv[1]);
config.conn_info.hostport = atoi(argv[2]);
cliRefreshPrompt();
cliConnect(CC_FORCE);
} else if (argc == 1 && !strcasecmp(argv[0],"clear")) {
linenoiseClearScreen();
} else {
long long start_time = mstime(), elapsed;
issueCommandRepeat(argc-skipargs, argv+skipargs, repeat);
/* If our debugging session ended, show the EVAL final
* reply. */
if (config.eval_ldb_end) {
config.eval_ldb_end = 0;
cliReadReply(0);
printf("\n(Lua debugging session ended%s)\n\n",
config.eval_ldb_sync ? "" :
" -- dataset changes rolled back");
cliInitHelp();
}
elapsed = mstime()-start_time;
if (elapsed >= 500 &&
config.output == OUTPUT_STANDARD)
{
printf("(%.2fs)\n",(double)elapsed/1000);
}
}
/* Free the argument vector */
sdsfreesplitres(argv,argc);
}
/* linenoise() returns malloc-ed lines like readline() */
linenoiseFree(line);
}
exit(0);
}