Readline
History
稳定性:2 - 稳定
node:readline 模块提供一个接口,用于一次一行地从 Readable 流(例如 process.stdin)读取数据。
要使用基于 Promise 的 API:
import * as readline from 'node:readline/promises';要使用回调和同步 API:
import * as readline from 'node:readline';以下简单示例说明了 node:readline 模块的基本用法。
import * as readline from 'node:readline/promises';
import { stdin as input, stdout as output } from 'node:process';
const rl = readline.createInterface({ input, output });
const answer = await rl.question('What do you think of Node.js? ');
console.log(`Thank you for your valuable feedback: ${answer}`);
rl.close();一旦调用此代码,Node.js 应用程序将不会终止,直到 readline.Interface 关闭,因为该接口等待在 input 流上接收数据。
类:InterfaceConstructor
History
- 继承:{EventEmitter}
InterfaceConstructor 类的实例是使用 readlinePromises.createInterface() 或 readline.createInterface() 方法构造的。每个实例都与单个 input Readable 流和单个 output Writable 流关联。
output 流用于打印提示符,以便用户在 input 流上提供输入并从中读取。
事件:'close'
History
当发生以下情况之一时,会发出 'close' 事件:
- 调用了
rl.close()方法,并且InterfaceConstructor实例已放弃对input和output流的控制; input流接收到其'end'事件;input流接收到 Ctrl+D 以信号传输结束 (EOT);input流接收到 Ctrl+C 以信号SIGINT,并且在InterfaceConstructor实例上没有注册'SIGINT'事件监听器。
监听器函数被调用时不传递任何参数。
一旦发出 'close' 事件,InterfaceConstructor 实例即完成。
事件:'error'
History
当与 node:readline Interface 关联的 input 流发生错误时,会发出 'error' 事件。
监听器函数被调用时,会传入一个 Error 对象作为单个参数。
事件:'line'
History
每当 input 流接收到行尾输入(\n、\r 或 \r\n)时,就会发出 'line' 事件。这通常发生在用户按下 Enter 或 Return 时。
如果已从流中读取新数据且该流结束时没有最终的行尾标记,也会发出 'line' 事件。
监听器函数被调用时,会传入一个包含单行接收输入的字符串。
rl.on('line', (input) => {
console.log(`Received: ${input}`);
});事件:'history'
History
每当历史数组发生变化时,就会发出 'history' 事件。
监听器函数被调用时,会传入一个包含历史数组的数组。它将反映所有更改,包括因 historySize 和 removeHistoryDuplicates 而添加和删除的行。
主要目的是允许监听器持久化历史。监听器也可以更改历史对象。这对于防止某些行(如密码)被添加到历史中可能很有用。
rl.on('history', (history) => {
console.log(`Received: ${history}`);
});事件:'pause'
History
当发生以下情况之一时,会发出 'pause' 事件:
监听器函数被调用时不传递任何参数。
rl.on('pause', () => {
console.log('Readline paused.');
});事件:'resume'
History
每当 input 流恢复时,就会发出 'resume' 事件。
监听器函数被调用时不传递任何参数。
rl.on('resume', () => {
console.log('Readline resumed.');
});事件:'SIGCONT'
History
当之前使用 Ctrl+Z(即 SIGTSTP)移至后台的 Node.js 进程使用 fg(1p) 带回前台时,会发出 'SIGCONT' 事件。
如果在 SIGTSTP 请求 之前 input 流已暂停,则不会发出此事件。
监听器函数被调用时不传递任何参数。
rl.on('SIGCONT', () => {
// `prompt` 将自动恢复流
rl.prompt();
});Windows 上 不 支持 'SIGCONT' 事件。
事件:'SIGINT'
History
每当 input 流接收到 Ctrl+C 输入(通常称为 SIGINT)时,就会发出 'SIGINT' 事件。如果当 input 流接收到 SIGINT 时没有注册 'SIGINT' 事件监听器,则会发出 'pause' 事件。
监听器函数被调用时不传递任何参数。
rl.on('SIGINT', () => {
rl.question('Are you sure you want to exit? ', (answer) => {
if (answer.match(/^y(es)?$/i)) rl.pause();
});
});事件:'SIGTSTP'
History
当 input 流接收到 Ctrl+Z 输入(通常称为 SIGTSTP)时,会发出 'SIGTSTP' 事件。如果当 input 流接收到 SIGTSTP 时没有注册 'SIGTSTP' 事件监听器,Node.js 进程将被发送到后台。
当使用 fg(1p) 恢复程序时,将发出 'pause' 和 'SIGCONT' 事件。这些可用于恢复 input 流。
如果在进程发送到后台之前 input 已暂停,则不会发出 'pause' 和 'SIGCONT' 事件。
监听器函数被调用时不传递任何参数。
rl.on('SIGTSTP', () => {
// 这将覆盖 SIGTSTP 并防止程序进入
// 后台。
console.log('Caught SIGTSTP.');
});Windows 上 不 支持 'SIGTSTP' 事件。
rl.close(): voidrl.close() 方法关闭 InterfaceConstructor 实例并放弃对 input 和 output 流的控制。调用时,将发出 'close' 事件。
调用 rl.close() 不会立即停止 InterfaceConstructor 实例发出其他事件(包括 'line')。
rl[Symbol.dispose](): voidrl.close() 的别名。
rl.pause(): voidrl.pause() 方法暂停 input 流,允许必要时稍后恢复。
调用 rl.pause() 不会立即暂停 InterfaceConstructor 实例发出其他事件(包括 'line')。
rl.prompt(preserveCursor?): void<boolean>true
,防止光标位置重置为
0
。rl.prompt() 方法将 InterfaceConstructor 实例配置的 prompt 写入 output 中的新行,以便为用户提供提供输入的新位置。
调用时,如果 input 流已暂停,rl.prompt() 将恢复它。
如果创建 InterfaceConstructor 时将 output 设置为 null 或 undefined,则不会写入提示符。
rl.resume(): void如果 input 流已暂停,rl.resume() 方法将恢复它。
rl.setPrompt(prompt): void<string>rl.setPrompt() 方法设置每当调用 rl.prompt() 时将写入 output 的提示符。
rl.getPrompt(): void- 返回:
<string>当前提示符字符串
rl.getPrompt() 方法返回 rl.prompt() 使用的当前提示符。
rl.write(data, key?): voidrl.write() 方法会将 data 或由 key 标识的键序列写入 output。仅当 output 是 TTY 文本终端时,才支持 key 参数。有关键组合列表,请参阅 TTY 键绑定。
如果指定了 key,则忽略 data。
调用时,如果 input 流已暂停,rl.write() 将恢复它。
如果创建 InterfaceConstructor 时将 output 设置为 null 或 undefined,则不会写入 data 和 key。
rl.write('Delete this!');
// 模拟 Ctrl+U 删除之前写入的行
rl.write(null, { ctrl: true, name: 'u' });rl.write() 方法会将数据写入 readline Interface 的 input,就像是由用户提供的一样。
rl[Symbol.asyncIterator]
History
Symbol.asyncIterator 支持不再是实验性的。
rl[Symbol.asyncIterator](): void创建一个 AsyncIterator 对象,该对象将输入流中的每一行作为字符串进行迭代。此方法允许通过 for await...of 循环异步迭代 InterfaceConstructor 对象。
输入流中的错误不会转发。
如果循环使用 break、throw 或 return 终止,将调用 rl.close()。换句话说,迭代 InterfaceConstructor 将始终完全消耗输入流。
性能不如传统的 'line' 事件 API。对于性能敏感的应用程序,请改用 'line'。
async function processLineByLine() {
const rl = readline.createInterface({
// ...
});
for await (const line of rl) {
// readline 输入中的每一行将在此处依次作为
// `line` 可用。
}
}readline.createInterface() 一旦调用就会开始消耗输入流。在接口创建和异步迭代之间进行异步操作可能会导致遗漏行。
- 类型:
<string>
当前正在由 node 处理的输入数据。
当从 TTY 流收集输入时,这可用于检索在发出 line 事件之前迄今为止已处理的当前值。一旦发出 line 事件,此属性将为空字符串。
请注意,如果在实例运行时修改值且未同时控制 rl.cursor,可能会产生意外后果。
如果不使用 TTY 流作为输入,请使用 'line' 事件。
一个可能的用例如下:
const values = ['lorem ipsum', 'dolor sit amet'];
const rl = readline.createInterface(process.stdin);
const showResults = debounce(() => {
console.log(
'\n',
values.filter((val) => val.startsWith(rl.line)).join(' '),
);
}, 300);
process.stdin.on('keypress', (c, k) => {
showResults();
});- 类型:
<number>|<undefined>
相对于 rl.line 的光标位置。
当从 TTY 流读取输入时,这将跟踪当前光标落在输入字符串中的位置。光标的位置决定了在处理输入时将修改的输入字符串部分,以及将呈现终端插入符号的列。
rl.getCursorPos(): void- 返回:
<Object>Attributes
返回相对于输入提示符 + 字符串的光标实际位置。长输入(换行)字符串以及多行提示符都包含在计算中。
Promises API
History
标记该 API 为稳定版。
类:readlinePromises.Interface
History
readlinePromises.Interface 类的实例是使用 readlinePromises.createInterface() 方法构造的。每个实例都与单个 input Readable 流和单个 output Writable 流相关联。output 流用于打印提示符,以获取在 input 流上到达并从中读取的用户输入。
rl.question(query, options?): void<string>output
的语句或查询, prepend 到提示符之前。<Object><AbortSignal>AbortSignal
取消
question()
。<Promise>
一个 Promise,在用户响应
query
的输入后 fulfilled。rl.question() 方法通过将 query 写入 output 来显示它,等待在 input 上提供用户输入,然后调用 callback 函数并将提供的输入作为第一个参数传递。
调用时,如果 input 流已暂停,rl.question() 将恢复它。
如果 readlinePromises.Interface 创建时 output 设为 null 或 undefined,则不会写入 query。
如果在 rl.close() 之后调用 question,它将返回一个被拒绝的 promise。
使用示例:
const answer = await rl.question('What is your favorite food? ');
console.log(`Oh, so your favorite food is ${answer}`);使用 AbortSignal 取消问题。
const signal = AbortSignal.timeout(10_000);
signal.addEventListener('abort', () => {
console.log('The food question timed out');
}, { once: true });
const answer = await rl.question('What is your favorite food? ', { signal });
console.log(`Oh, so your favorite food is ${answer}`);类:readlinePromises.Readline
History
new readlinePromises.Readline(stream, options?): void<stream.Writable>rl.clearLine(dir): void<integer>rl.clearLine() 方法将清除关联 stream 当前行的操作添加到内部待处理操作列表中,清除方向由 dir 标识。
调用 rl.commit() 以查看此方法的效果,除非构造函数传入了 autoCommit: true。
rl.clearScreenDown(): void- 返回:this
rl.clearScreenDown() 方法将从光标当前位置向下清除关联流的操作添加到内部待处理操作列表中。
调用 rl.commit() 以查看此方法的效果,除非构造函数传入了 autoCommit: true。
rl.commit(): void- 返回:
<Promise>
rl.commit() 方法将所有待处理操作发送到关联的 stream 并清除内部待处理操作列表。
rl.cursorTo(x, y?): voidrl.cursorTo() 方法将移动光标到关联 stream 中指定位置的操作添加到内部待处理操作列表中。
调用 rl.commit() 以查看此方法的效果,除非构造函数传入了 autoCommit: true。
rl.moveCursor(dx, dy): voidrl.moveCursor() 方法将相对于关联 stream 中当前位置移动光标的操作添加到内部待处理操作列表中。
调用 rl.commit() 以查看此方法的效果,除非构造函数传入了 autoCommit: true。
rl.rollback(): void- 返回:this
rl.rollback 方法清除内部待处理操作列表,而不将其发送到关联的 stream。
readlinePromises.createInterface(options): void<Object><stream.Readable><stream.Writable><Function><boolean>input
和
output
流应被视为 TTY,并向其写入 ANSI/VT100 转义码,则为
true
。
默认:
实例化时检查
output
流上的
isTTY
。<string[]>terminal
设置为
true
或通过内部
output
检查设置为
true
时,此选项才有意义,否则历史记录缓存机制根本不会初始化。
默认:
[]
。<number>0
。仅当用户将
terminal
设置为
true
或通过内部
output
检查设置为
true
时,此选项才有意义,否则历史记录缓存机制根本不会初始化。
默认:
30
。<boolean>true
,当添加到历史记录列表的新输入行重复较旧的行时,这会从列表中删除较旧的行。
默认:
false
。<string>'> '
。<number>\r
和
\n
之间的延迟超过
crlfDelay
毫秒,则
\r
和
\n
都将被视为单独的行尾输入。
crlfDelay
将被强制转换为不小于
100
的数字。它可以设置为
Infinity
,在这种情况下,
\r
后跟
\n
将始终被视为单个换行符(这对于
读取文件
使用
\r\n
行分隔符可能是合理的)。
默认:
100
。<number>readlinePromises
等待字符的持续时间(当读取模糊键序列时,以毫秒为单位,该序列既可以使用到目前为止读取的输入形成完整的键序列,又可以接受额外的输入来完成更长的键序列)。
默认:
500
。<integer>8
。<AbortSignal>readlinePromises.createInterface() 方法创建一个新的 readlinePromises.Interface 实例。
import { createInterface } from 'node:readline/promises';
import { stdin, stdout } from 'node:process';
const rl = createInterface({
input: stdin,
output: stdout,
});一旦创建了 readlinePromises.Interface 实例,最常见的情况是监听 'line' 事件:
rl.on('line', (line) => {
console.log(`Received: ${line}`);
});如果此实例的 terminal 为 true,则如果 output 流定义了 output.columns 属性,并且在列发生变化时或如果列发生变化时在 output 上发出 'resize' 事件,将获得最佳兼容性(process.stdout 在它是 TTY 时会自动执行此操作)。
completer 函数将用户输入的当前行作为参数,并返回一个包含 2 个条目的 Array:
- 一个
Array,包含匹配的补全条目。 - 用于匹配的子字符串。
例如:[[substr1, substr2, ...], originalsubstring]。
function completer(line) {
const completions = '.help .error .exit .quit .q'.split(' ');
const hits = completions.filter((c) => c.startsWith(line));
// 如果未找到则显示所有补全
return [hits.length ? hits : completions, line];
}completer 函数也可以返回一个 <Promise>,或者是异步的:
async function completer(linePartial) {
await someAsyncWork();
return [['123'], linePartial];
}回调 API
History
类:readline.Interface
History
类 readline.Interface 现在继承自 Interface。
readline.Interface 类的实例是使用 readline.createInterface() 方法构造的。每个实例都与单个 input Readable 流和单个 output Writable 流相关联。output 流用于打印提示符,以获取在 input 流上到达并从中读取的用户输入。
rl.question(query, options?, callback): void<string>output
的语句或查询,prepend 到提示符之前。<Object><AbortSignal>AbortController
取消
question()
。<Function>query
的输入来调用。rl.question() 方法通过将 query 写入 output 来显示它,等待在 input 上提供用户输入,然后调用 callback 函数并将提供的输入作为第一个参数传递。
调用时,如果 input 流已暂停,rl.question() 将恢复它。
如果 readline.Interface 创建时 output 设为 null 或 undefined,则不会写入 query。
传递给 rl.question() 的 callback 函数不遵循接受 Error 对象或 null 作为第一个参数的典型模式。
callback 仅使用提供的答案作为唯一参数调用。
如果在 rl.close() 之后调用 rl.question(),将抛出错误。
使用示例:
rl.question('What is your favorite food? ', (answer) => {
console.log(`Oh, so your favorite food is ${answer}`);
});使用 AbortController 取消问题。
const ac = new AbortController();
const signal = ac.signal;
rl.question('What is your favorite food? ', { signal }, (answer) => {
console.log(`Oh, so your favorite food is ${answer}`);
});
signal.addEventListener('abort', () => {
console.log('The food question timed out');
}, { once: true });
setTimeout(() => ac.abort(), 10000);readline.clearLine(stream, dir, callback?): voidreadline.clearLine() 方法清除给定 TTY 流的当前行,清除方向由 dir 标识。
readline.clearScreenDown(stream, callback?): voidreadline.clearScreenDown() 方法从光标的当前位置向下清除给定 TTY 流。
readline.createInterface(options): void<Object><stream.Readable><stream.Writable><Function><boolean>input
和
output
流应被视为 TTY,并向其写入 ANSI/VT100 转义码,则为
true
。
默认:
实例化时检查
output
流上的
isTTY
。<string[]>terminal
设置为
true
或通过内部
output
检查设置为
true
时,此选项才有意义,否则历史记录缓存机制根本不会初始化。
默认:
[]
。<number>0
。仅当用户将
terminal
设置为
true
或通过内部
output
检查设置为
true
时,此选项才有意义,否则历史记录缓存机制根本不会初始化。
默认:
30
。<boolean>true
,当添加到历史记录列表的新输入行重复较旧的行时,这会从列表中删除较旧的行。
默认:
false
。<string>'> '
。<number>\r
和
\n
之间的延迟超过
crlfDelay
毫秒,则
\r
和
\n
都将被视为单独的行尾输入。
crlfDelay
将被强制转换为不小于
100
的数字。它可以设置为
Infinity
,在这种情况下,
\r
后跟
\n
将始终被视为单个换行符(这对于
读取文件
使用
\r\n
行分隔符可能是合理的)。
默认:
100
。<number>readline
等待字符的持续时间(当读取模糊键序列时,以毫秒为单位,该序列既可以使用到目前为止读取的输入形成完整的键序列,又可以接受额外的输入来完成更长的键序列)。
默认:
500
。<integer>8
。<AbortSignal>close
。readline.createInterface() 方法创建一个新的 readline.Interface 实例。
import { createInterface } from 'node:readline';
import { stdin, stdout } from 'node:process';
const rl = createInterface({
input: stdin,
output: stdout,
});一旦创建了 readline.Interface 实例,最常见的情况是监听 'line' 事件:
rl.on('line', (line) => {
console.log(`Received: ${line}`);
});如果此实例的 terminal 为 true,则如果 output 流定义了 output.columns 属性,并且在列发生变化时或如果列发生变化时在 output 上发出 'resize' 事件,将获得最佳兼容性(process.stdout 在它是 TTY 时会自动执行此操作)。
当使用 stdin 作为输入创建 readline.Interface 时,程序将不会终止,直到它收到 EOF 字符。要在不等待用户输入的情况下退出,请调用 process.stdin.unref()。
completer 函数将用户输入的当前行作为参数,并返回一个包含 2 个条目的 Array:
- 一个
Array,包含匹配的补全条目。 - 用于匹配的子字符串。
例如:[[substr1, substr2, ...], originalsubstring]。
function completer(line) {
const completions = '.help .error .exit .quit .q'.split(' ');
const hits = completions.filter((c) => c.startsWith(line));
// 如果未找到则显示所有补全
return [hits.length ? hits : completions, line];
}如果 completer 函数接受两个参数,则可以异步调用它:
function completer(linePartial, callback) {
callback(null, [['123'], linePartial]);
}readline.cursorTo(stream, x, y?, callback?): voidreadline.cursorTo() 方法将光标移动到给定 TTY stream 中的指定位置。
readline.moveCursor(stream, dx, dy, callback?): voidreadline.moveCursor() 方法将光标相对于给定 TTY stream 中的当前位置移动。
readline.emitKeypressEvents(stream, interface?): void<stream.Readable><readline.InterfaceConstructor>readline.emitKeypressEvents() 方法使给定的 Readable 流开始发出与接收输入相对应的 'keypress' 事件。
可选地,interface 指定一个 readline.Interface 实例,当检测到复制粘贴输入时,该实例禁用自动完成。
如果 stream 是 TTY,则它必须处于原始模式。
如果 input 是终端,任何 readline 实例都会在其 input 上自动调用此方法。关闭 readline 实例不会停止 input 发出 'keypress' 事件。
readline.emitKeypressEvents(process.stdin);
if (process.stdin.isTTY)
process.stdin.setRawMode(true);以下示例说明了如何使用 readline.Interface 类来实现一个小型命令行界面:
import { createInterface } from 'node:readline';
import { exit, stdin, stdout } from 'node:process';
const rl = createInterface({
input: stdin,
output: stdout,
prompt: 'OHAI> ',
});
rl.prompt();
rl.on('line', (line) => {
switch (line.trim()) {
case 'hello':
console.log('world!');
break;
default:
console.log(`Say what? I might have heard '${line.trim()}'`);
break;
}
rl.prompt();
}).on('close', () => {
console.log('Have a great day!');
exit(0);
});readline 的一个常见用例是逐行消耗输入文件。最简单的方法是利用 fs.ReadStream API 以及 for await...of 循环:
import { createReadStream } from 'node:fs';
import { createInterface } from 'node:readline';
async function processLineByLine() {
const fileStream = createReadStream('input.txt');
const rl = createInterface({
input: fileStream,
crlfDelay: Infinity,
});
// 注意:我们使用 crlfDelay 选项来识别 input.txt 中所有 CR LF
// ('\r\n') 实例作为单个换行符。
for await (const line of rl) {
// input.txt 中的每一行将在此处作为 `line` 连续可用。
console.log(`Line from file: ${line}`);
}
}
processLineByLine();或者,可以使用 'line' 事件:
import { createReadStream } from 'node:fs';
import { createInterface } from 'node:readline';
const rl = createInterface({
input: createReadStream('sample.txt'),
crlfDelay: Infinity,
});
rl.on('line', (line) => {
console.log(`Line from file: ${line}`);
});目前,for await...of 循环可能有点慢。如果 async / await 流程和速度都至关重要,则可以应用混合方法:
import { once } from 'node:events';
import { createReadStream } from 'node:fs';
import { createInterface } from 'node:readline';
(async function processLineByLine() {
try {
const rl = createInterface({
input: createReadStream('big-file.txt'),
crlfDelay: Infinity,
});
rl.on('line', (line) => {
// 处理该行。
});
await once(rl, 'close');
console.log('File processed.');
} catch (err) {
console.error(err);
}
})();| 键绑定 | 描述 | 备注 |
|---|---|---|
| Ctrl+Shift+Backspace | 删除左侧行 | 在 Linux、Mac 和 Windows 上不起作用 |
| Ctrl+Shift+Delete | 删除右侧行 | 在 Mac 上不起作用 |
| Ctrl+C | 发出 SIGINT 或关闭 readline 实例 |
|
| Ctrl+H | 向左删除 | |
| Ctrl+D | 向右删除,或者如果当前行为空 / EOF 则关闭 readline 实例 | 在 Windows 上不起作用 |
| Ctrl+U | 从当前位置删除到行首 | |
| Ctrl+K | 从当前位置删除到行尾 | |
| Ctrl+Y | 粘贴(召回)之前删除的文本 | 仅适用于由 Ctrl+U 或 Ctrl+K 删除的文本 |
| Meta+Y | 在之前删除的文本之间循环 | 仅当最后一次按键是 Ctrl+Y 或 Meta+Y 时可用 |
| Ctrl+A | 转到行首 | |
| Ctrl+E | 转到行尾 | |
| Ctrl+B | 后退一个字符 | |
| Ctrl+F | 前进一个字符 | |
| Ctrl+L | 清屏 | |
| Ctrl+N | 下一条历史记录 | |
| Ctrl+P | 上一条历史记录 | |
| Ctrl+- | 撤销上一次更改 | 任何发出键码 0x1F 的按键都会执行此操作。在许多终端中,例如 xterm,这绑定到 Ctrl+-。 |
| Ctrl+6 | 重做上一次更改 | 许多终端没有默认的重做按键。我们选择键码 0x1E 来执行重做。在 xterm 中,默认绑定到 Ctrl+6。 |
| Ctrl+Z | 将运行中的进程移到后台。输入 fg 并按 Enter 返回。 |
在 Windows 上不起作用 |
| Ctrl+W 或 Ctrl +Backspace | 向后删除到单词边界 | Ctrl+Backspace 在 Linux、Mac 和 Windows 上不起作用 |
| Ctrl+Delete | 向前删除到单词边界 | 在 Mac 上不起作用 |
| Ctrl+左箭头 或 Meta+B | 向左移动一个单词 | Ctrl+左箭头 在 Mac 上不起作用 |
| Ctrl+右箭头 或 Meta+F | 向右移动一个单词 | Ctrl+右箭头 在 Mac 上不起作用 |
| Meta+D 或 Meta +Delete | 向右删除单词 | Meta+Delete 在 Windows 上不起作用 |
| Meta+Backspace | 向左删除单词 | 在 Mac 上不起作用 |