readline库的简单使用

article/2025/9/22 12:15:29

readline库的简单使用

这周要实现一个简单的 shell, 平时使用bash, zsh这些shell的时候, 如果文件名或命令太长,又或者要频繁执行几条命令的话,最常用的应该就是tab键补全和上下键切换历史命令了。

想要在自己的shell里面实现这两个功能很困难,但有一个C语言库集成了这些功能,只需要调用几个函数就可以实现这两个功能。

The GNU Readline Library

可以在这里找到有关 readline 库的相关资料和下载地址,软件包里面也提供了很多手册和示例。
这里写图片描述

实现shell用到的函数不是很多,tab键补全,上下键切换历史命令,添加历史命令等等

readline()

readline.h 里可以找到关于他的定义:

/* Readline functions. */
/* Read a line of input.  Prompt with PROMPT.  A NULL PROMPT means none. */
extern char *readline PARAMS((const char *));

readline() 的参数是一个字符串,调用函数的时候会在屏幕上输出,这个函数会读取一行输入,然后返回一个指向输入字符串的指针,readline 会为输入的字符串动态分配内存,所以使用完之后需要free掉。

下面举一个简单的例子

#include <stdlib.h>
#include <readline/readline.h>int main(void)
{while (1){char * str = readline("Myshell $ ");free(str);}
}

由于readline是一个动态库,编译的时候需要加上 -lreadline,不然会找不到相关的函数
当我们按下tab键之后发现就可以实现bash里面的补全功能了。
这里写图片描述
用惯了zsh后发现黑白的提示符好难看,于是也想着给里面的参数加上颜色。C语言中输出有颜色的字符printf就可以实现,模板类似这样 printf("\033[47;31m string \033[0m");

47是背景色,31是字符的颜色,string 是要输出的字符串,\033[5m是ANSI控制码,意思是关闭输出的属性,不然以后的输出都会是之前设置的颜色。相关的内容网上有很多可以自行查阅。

为了方便使用,加上了这些宏定义

#define CLOSE "\033[0m"                 // 关闭所有属性
#define BLOD  "\033[1m"                 // 强调、加粗、高亮
#define BEGIN(x,y) "\033["#x";"#y"m"    // x: 背景,y: 前景

在修改一下readline()这个函数

char * str = readline(BEGIN(49, 34)"Myshell $ "CLOSE);

然后编译运行:
这里写图片描述
似乎一切完美,但当我们输入很长很长的字符串之后:

这里写图片描述

emmmm……………输入太多会导致提示符被输入覆盖,写个shell出现这种状况岂不是贼尴尬

查资料查了很久才找到解决方法:
这个bug需要在非打印字符前后加上 \001 和 \002 才能解决

其实头文件就有提到
这里写图片描述

在之前定义的宏里面加上这两个字符之后终于解决了
这里写图片描述
最后的代码为:

#include <stdio.h>
#include <stdlib.h>
#include <readline/readline.h>#define CLOSE "\001\033[0m\002"                 // 关闭所有属性
#define BLOD  "\001\033[1m\002"                 // 强调、加粗、高亮
#define BEGIN(x,y) "\001\033["#x";"#y"m\002"    // x: 背景,y: 前景int main(void)
{while (1){char * str = readline(BEGIN(49, 34)"Myshell $ "CLOSE);free(str);}
}

readline使用的时候默认了tab补全,但是我们平时用到的shell不但可以补全文件名,还可以补全命令。readline库当然也提供了这个功能,具体如何使用可以看这篇博客。

GNU Readline 库及编程简介

单独的使用readline()并没有上下键切换补全的功能,实现这个需要用到另一个函数 - add_history()

history.h

上下键切换需要我们把输入的字符串加入到历史命令中,需要调用

/* Place STRING at the end of the history list.The associated data field (if any) is set to NULL. */
extern void add_history PARAMS((const char *));

函数接受一个字符串作为参数存入到历史文件中,函数的定义在history.h中,使用的时候需要包含头文件

        char * str = readline(BEGIN(49, 34)"Myshell $ "CLOSE);add_history(str);free(str);

编译后测试了一下发现功能完美运行。

但是关掉程序在尝试一下发现,诶?我不能切换到上一次运行程序的历史命令,只能记录本次运行中输入的命令。然后开始查看头文件的内容,发现了不少和history有关的函数。

其中有两个正好用的上

/* Add the contents of FILENAME to the history list, a line at a time.If FILENAME is NULL, then read from ~/.history.  Returns 0 ifsuccessful, or errno if not. */
extern int read_history PARAMS((const char *));
/* Write the current history to FILENAME.  If FILENAME is NULL,then write the history list to ~/.history.  Values returnedare as in read_history ().  */
extern int write_history PARAMS((const char *));

read_history() 和 write_history() 都接受一个字符串做参数,成功返回0,错误则把相应的错误码赋值给errno。

两个函数接受的参数都是一个文件名,read_history() 从指定的文件中读取历史记录,write_history() 将历史记录存入指定的文件。如果参数为NULL默认的文件是:~/.history

有了这个函数,我们只要在程序最开处加上read_history(NULL), add_history(str)之后加上 write_history() 就可以了。

这样下次运行程序的时候我们就可以找到上次运行的历史命令了。

shell 的内置命令不多,cd 是一个, history也是一个shell内置的命令。

这里写图片描述
readline既然可以把输入加入历史,读入和写进历史,那么自然可以读取历史文件列表,头文件中我们可以找到这样一个函数:

/* Return a NULL terminated array of HIST_ENTRY which is the current input history.  Element 0 of this list is the beginning of time.  If there is no history, return NULL. */
extern HIST_ENTRY **history_list PARAMS((void));

这个函数可以查看存储的 history 列表,HIST_ENTRY 是一个结构体类型,存储了很多信息:

这里写图片描述

我们要的历史内容就存储在 data 元素里面。

这个函数返回一个数组,以空指针为结束标志,我们简单封装一下就可以实现一个自己 shell 内置的 history 函数了。

void ShowHistory()
{int i = 0;HIST_ENTRY ** his;his = history_list();while(his[i] != NULL){printf("%s\n", his[i]->line);i++;}
}

history.h 里面提供了很多函数,我们的要实现一个简单的shell用到的函数上面都提到过,更多的函数可以在官方文档里面查看。

realine 这个库很强大,现在只是发现了他的冰山一角,提供的功能远远超过上述所说的。


http://chatgpt.dhexx.cn/article/WYwMElcA.shtml

相关文章

readlines()的使用与readline()的使用

readlines()的使用 readlines可以按照行的方式把整个文件中的内容进行一次性读取 &#xff0c;并且返回的是一个列表&#xff0c;其中每一行的数据为一个元素。 fopen("text.text",r) af.readlines() print(a) f.close()** 输出的结果是 [“aaa\n”,“bbb”] 因…

Python文件操作----Read()、Readline()与Readlines()详解

前言:在上一篇博文中,我们主讲了文件的操作模式,主讲了写入数据的形式,但是没有讲到如何对文件的数据进行读取,那么本博文主针对读取数据的两种方法来进行细谈。(上一篇博文友情链接python文件操作) 文章目录: 一:Read()方法:1.无参数读取:2.有参数进行读取: 二.Readline()方法…

python:read()/readline()/readlines()区别

在我们使用python打开或者读取txt文件时&#xff0c;我们经常会用到read()/readline()/readlines()三种方法&#xff0c;首先我们贴出结论&#xff0c;即三种方法的异同。 方法类型返回文本read()str以原格式返回全部文本readline()str只返回第一行文本readlines()list以列表的…

python readline()和readlines()

文件对象的方法 描述 fp.readline() 从文件中读取一行内容&#xff0c;并以此作为一个 字符串返回 fp.readlines() 将文件中的每行内容作为一个字符串存入 列表中&#xff0c;并返回该列表 注意事项&#xff1a; readline()和readlines()&#xff0c;都是从当前位置进行读取&am…

f.readline()和f.readline()和f.read()

一、操作文件 [rootnode6 ~]# cat cs.txt 12 13 14 aa bb vv15 cc 16二、f.readline() 整行读取&#xff0c;每次一行&#xff0c;空行也会当做一行读取。 因为文本带了一个 ‘\n’ 了&#xff0c;print 默认也是 ‘\n’&#xff0c;所以读取每一行都会多打印一个空行 [root…

readlines和readline

readlines和readline python之readlines和readline的区别1.txtreadlinesreadline 很久很久没写博客了&#xff0c;这真的是一个很不好的习惯&#xff0c;因为遇到了很多很多说需要技术文章的。。。。 python之readlines和readline的区别 很久很久没有写py脚本了&#xff0c;今…

python中read、readline和readlines的区别

python中有神奇的三种读操作&#xff1a;read、readline和readlines read() &#xff1a; 一次性读取整个文件内容。推荐使用read(size)方法&#xff0c;size越大运行时间越长 readline() &#xff1a;每次读取一行内容。内存不够时使用&#xff0c;一般不太用 readlines(…

read(),readline(),readlines()的区别

这三个方法偶尔会混淆&#xff0c;直接做个记录。 自己随便写了个txt文件&#xff1a; 1、read() read()方法会直接读取整个文本的内容&#xff0c;并将它们保存在字符串变量。 f open(D:\Procedure\pycharm\python\op.txt,)print(f.read()) print(type(f.read())) 2、readl…

关于readline ,readlines, for循环读取文件内容

1、readlines: readlines() 用于一次性读取所有行&#xff0c;然后将它们作为列表中的字符串元素作为每一行返回。 此函数可用于小文件&#xff0c;因为它将整个文件内容读取到内存中&#xff0c;然后将其拆分为单独的行。 我们可以遍历列表并使用 strip() 函数去除换行符“\n”…

Node.js学习六(readline)

文章目录 一、什么是readline二、如何使用readline1、Interface类2、close事件3、readline.createInterface(options) 三、实例&#xff1a;输入输出四、示例&#xff1a;模拟命令行的输入输出 一、什么是readline readline是Node.js里实现标准输入输出的封装好的模块&#xff…

java以前版本下载方式

java以前版本下载 1.创建Oracle账号 2.网址https://www.oracle.com/java/technologies/downloads/archive/

java -version

一、JDK下载 1.官网下载 点击官网下载地址 往下划&#xff0c;找到自己电脑相对应的JDK&#xff0c;点击下载。 二、安装步骤 初学者建议傻瓜式安装&#xff0c;直接点击下一步即可。 三、搭配环境变量 1.Path变量 解决方法&#xff1a;配置Path变量。 右键点击“此电脑”&…

最全的Java版本历史

JDK 1.0&#xff08;1996&#xff09; Sun公司发布Java1.0&#xff0c;发布初期叫Oak&#xff0c;后改名为Java&#xff08;JDK1.0基本上只支持Java语言基础特性&#xff09; JDK 1.1&#xff08;1997&#xff09; 引入内部类引入JDBC&#xff1a; 是Java语言中用来规范客户…

JDK各个版本的区别

jdk1.5的新特性&#xff1a; 1. 泛型 ArrayList listnew ArrayList()------>ArrayList<Integer>listnew ArrayList<Integer>(); 2 自动装箱/拆箱 nt ilist.get(0).parseInt();-------->int ilist.get(0);原始类型与对应的包装类不用显式转换 3 for-eac…

Java 版本区分

Java分为如下三个版本&#xff1a; Java SE&#xff1a;Standard Edition&#xff1a; Java SE就是标准版&#xff0c;包含标准的JVM和标准库。 Java EE&#xff1a;Enterprise Edition 而Java EE是企业版&#xff0c;它只是在Java SE的基础上加上了大量的API和库&#xff0c;…

更换Java版本

当windows系统中存在多个java版本时&#xff0c;怎么切换&#xff1f; 如果是1.7以前的版本&#xff0c;则直接修改环境变量JAVA_HOME&#xff1b; 如果是1.7及以后的&#xff0c;则可能要修改C:\ProgramData\Oracle\Java\javapath 中的三个java&#xff0c;可重命名&#xff…

JAVA版本号的问题 Java版本号与JDK版本

初学Java时便一直疑惑Java版本号到底是如何命名的&#xff1f;时常在网上看到Java5、Java6、Java7、Java8 (到今天已经到了Java12了&#xff0c;2019.4.5) 这一类 “Java X” 的Java版本名称&#xff0c;同时又会看到诸如JDK1.5、JDK1.6这中 “JDK1.X” 的JDK叫法。一直以来都在…

查看java/jdk版本

查看当前电脑的Java/JDK版本的方法 1.winR 打开运行窗口&#xff0c;输入 cmd 2.在控制台中输入java --version或者java -version&#xff0c;即可查看Java版本号 Java所有版本 版本号发布日期JDK Version 1.01996-01-23 Oak(橡树)JDK Version 1.11997-02-19JDK Version …

java的所有版本

图解 最开始 Java 是由 Sun Microsystems 公司于 1995 年 5 月推出的高级程序设计语言。 Oak也是一种精简的语言&#xff0c;程序非常小&#xff0c;适合在网络上传输。Sun公司首先推出了可以嵌入网页并且可以随同网页在网络上传输的Applet&#xff08;Applet是一种将小程序嵌…

JDK版本区别

1. 泛型 ArrayList listnew ArrayList()------>ArrayList<Integer>listnew ArrayList<Integer>(); 2 自动装箱/拆箱 nt ilist.get(0).parseInt();-------->int ilist.get(0);原始类型与对应的包装类不用显式转换 3 for-each i0;i<a.length;i------------&…