On this page

稳定性:2 - 稳定

node:readline 模块提供一个接口,用于一次一行地从 Readable 流(例如 process.stdin)读取数据。

要使用基于 Promise 的 API:

要使用回调和同步 API:

以下简单示例说明了 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 类的实例是使用 readlinePromises.createInterface()readline.createInterface() 方法构造的。每个实例都与单个 input Readable 流和单个 output Writable 流关联。 output 流用于打印提示符,以便用户在 input 流上提供输入并从中读取。

  • 调用了 rl.close() 方法,并且 InterfaceConstructor 实例已放弃对 inputoutput 流的控制;
  • input 流接收到其 'end' 事件;
  • input 流接收到 Ctrl+D 以信号传输结束 (EOT);
  • input 流接收到 Ctrl+C 以信号 SIGINT,并且在 InterfaceConstructor 实例上没有注册 'SIGINT' 事件监听器。

监听器函数被调用时不传递任何参数。

一旦发出 'close' 事件,InterfaceConstructor 实例即完成。

监听器函数被调用时,会传入一个 Error 对象作为单个参数。

如果已从流中读取新数据且该流结束时没有最终的行尾标记,也会发出 'line' 事件。

监听器函数被调用时,会传入一个包含单行接收输入的字符串。

rl.on('line', (input) => {
  console.log(`Received: ${input}`);
});

监听器函数被调用时,会传入一个包含历史数组的数组。它将反映所有更改,包括因 historySizeremoveHistoryDuplicates 而添加和删除的行。

主要目的是允许监听器持久化历史。监听器也可以更改历史对象。这对于防止某些行(如密码)被添加到历史中可能很有用。

rl.on('history', (history) => {
  console.log(`Received: ${history}`);
});
  • input 流被暂停。
  • input 流未暂停并接收到 'SIGCONT' 事件。(参见事件 'SIGTSTP''SIGCONT'。)

监听器函数被调用时不传递任何参数。

rl.on('pause', () => {
  console.log('Readline paused.');
});

监听器函数被调用时不传递任何参数。

rl.on('resume', () => {
  console.log('Readline resumed.');
});

如果在 SIGTSTP 请求 之前 input 流已暂停,则不会发出此事件。

监听器函数被调用时不传递任何参数。

rl.on('SIGCONT', () => {
  // `prompt` 将自动恢复流
  rl.prompt();
});

Windows 上 支持 'SIGCONT' 事件。

监听器函数被调用时不传递任何参数。

rl.on('SIGINT', () => {
  rl.question('Are you sure you want to exit? ', (answer) => {
    if (answer.match(/^y(es)?$/i)) rl.pause();
  });
});

当使用 fg(1p) 恢复程序时,将发出 'pause''SIGCONT' 事件。这些可用于恢复 input 流。

如果在进程发送到后台之前 input 已暂停,则不会发出 'pause''SIGCONT' 事件。

监听器函数被调用时不传递任何参数。

rl.on('SIGTSTP', () => {
  // 这将覆盖 SIGTSTP 并防止程序进入
  // 后台。
  console.log('Caught SIGTSTP.');
});

Windows 上 支持 'SIGTSTP' 事件。

调用 rl.close() 不会立即停止 InterfaceConstructor 实例发出其他事件(包括 'line')。

调用 rl.pause() 不会立即暂停 InterfaceConstructor 实例发出其他事件(包括 'line')。

rl.prompt() 方法将 InterfaceConstructor 实例配置的 prompt 写入 output 中的新行,以便为用户提供提供输入的新位置。

调用时,如果 input 流已暂停,rl.prompt() 将恢复它。

如果创建 InterfaceConstructor 时将 output 设置为 nullundefined,则不会写入提示符。

rl.setPrompt() 方法设置每当调用 rl.prompt() 时将写入 output 的提示符。

rl.getPrompt() 方法返回 rl.prompt() 使用的当前提示符。

rl.write() 方法会将 data 或由 key 标识的键序列写入 output。仅当 outputTTY 文本终端时,才支持 key 参数。有关键组合列表,请参阅 TTY 键绑定

如果指定了 key,则忽略 data

调用时,如果 input 流已暂停,rl.write() 将恢复它。

如果创建 InterfaceConstructor 时将 output 设置为 nullundefined,则不会写入 datakey

rl.write('Delete this!');
// 模拟 Ctrl+U 删除之前写入的行
rl.write(null, { ctrl: true, name: 'u' });

rl.write() 方法会将数据写入 readline Interfaceinput就像是由用户提供的一样

创建一个 AsyncIterator 对象,该对象将输入流中的每一行作为字符串进行迭代。此方法允许通过 for await...of 循环异步迭代 InterfaceConstructor 对象。

输入流中的错误不会转发。

如果循环使用 breakthrowreturn 终止,将调用 rl.close()。换句话说,迭代 InterfaceConstructor 将始终完全消耗输入流。

性能不如传统的 'line' 事件 API。对于性能敏感的应用程序,请改用 'line'

async function processLineByLine() {
  const rl = readline.createInterface({
    // ...
  });

  for await (const line of rl) {
    // readline 输入中的每一行将在此处依次作为
    // `line` 可用。
  }
}

readline.createInterface() 一旦调用就会开始消耗输入流。在接口创建和异步迭代之间进行异步操作可能会导致遗漏行。

当前正在由 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();
});

相对于 rl.line 的光标位置。

当从 TTY 流读取输入时,这将跟踪当前光标落在输入字符串中的位置。光标的位置决定了在处理输入时将修改的输入字符串部分,以及将呈现终端插入符号的列。

返回相对于输入提示符 + 字符串的光标实际位置。长输入(换行)字符串以及多行提示符都包含在计算中。

readlinePromises.Interface 类的实例是使用 readlinePromises.createInterface() 方法构造的。每个实例都与单个 input Readable 流和单个 output Writable 流相关联。output 流用于打印提示符,以获取在 input 流上到达并从中读取的用户输入。

Attributes
query:<string>
要写入  output 的语句或查询, prepend 到提示符之前。
options:<Object>
可选地允许使用  AbortSignal 取消 question()

rl.question() 方法通过将 query 写入 output 来显示它,等待在 input 上提供用户输入,然后调用 callback 函数并将提供的输入作为第一个参数传递。

调用时,如果 input 流已暂停,rl.question() 将恢复它。

如果 readlinePromises.Interface 创建时 output 设为 nullundefined,则不会写入 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}`);
Attributes
一个  TTY 流。
options:<Object>
autoCommit:<boolean>
如果为  true ,则无需调用 rl.commit()
Attributes
-1:
:光标左侧
1:
:光标右侧
0:
:整行

rl.clearLine() 方法将清除关联 stream 当前行的操作添加到内部待处理操作列表中,清除方向由 dir 标识。 调用 rl.commit() 以查看此方法的效果,除非构造函数传入了 autoCommit: true

  • 返回:this

rl.clearScreenDown() 方法将从光标当前位置向下清除关联流的操作添加到内部待处理操作列表中。 调用 rl.commit() 以查看此方法的效果,除非构造函数传入了 autoCommit: true

rl.commit() 方法将所有待处理操作发送到关联的 stream 并清除内部待处理操作列表。

Attributes

rl.cursorTo() 方法将移动光标到关联 stream 中指定位置的操作添加到内部待处理操作列表中。 调用 rl.commit() 以查看此方法的效果,除非构造函数传入了 autoCommit: true

Attributes

rl.moveCursor() 方法将相对于关联 stream 中当前位置移动光标的操作添加到内部待处理操作列表中。 调用 rl.commit() 以查看此方法的效果,除非构造函数传入了 autoCommit: true

  • 返回:this

rl.rollback 方法清除内部待处理操作列表,而不将其发送到关联的 stream

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}`);
});

如果此实例的 terminaltrue,则如果 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];
}

readline.Interface 类的实例是使用 readline.createInterface() 方法构造的。每个实例都与单个 input Readable 流和单个 output Writable 流相关联。output 流用于打印提示符,以获取在 input 流上到达并从中读取的用户输入。

Attributes
query:<string>
要写入  output 的语句或查询,prepend 到提示符之前。
options:<Object>
可选地允许使用  AbortController 取消 question()
callback:<Function>
一个回调函数,使用用户响应  query 的输入来调用。

rl.question() 方法通过将 query 写入 output 来显示它,等待在 input 上提供用户输入,然后调用 callback 函数并将提供的输入作为第一个参数传递。

调用时,如果 input 流已暂停,rl.question() 将恢复它。

如果 readline.Interface 创建时 output 设为 nullundefined,则不会写入 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() 方法清除给定 TTY 流的当前行,清除方向由 dir 标识。

readline.clearScreenDown() 方法从光标的当前位置向下清除给定 TTY 流。

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}`);
});

如果此实例的 terminaltrue,则如果 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() 方法将光标移动到给定 TTY stream 中的指定位置。

readline.moveCursor() 方法将光标相对于给定 TTY stream 中的当前位置移动。

Attributes

readline.emitKeypressEvents() 方法使给定的 Readable 流开始发出与接收输入相对应的 'keypress' 事件。

可选地,interface 指定一个 readline.Interface 实例,当检测到复制粘贴输入时,该实例禁用自动完成。

如果 streamTTY,则它必须处于原始模式。

如果 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+UCtrl+K 删除的文本
Meta+Y 在之前删除的文本之间循环 仅当最后一次按键是 Ctrl+YMeta+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+WCtrl +Backspace 向后删除到单词边界 Ctrl+Backspace 在 Linux、Mac 和 Windows 上不起作用
Ctrl+Delete 向前删除到单词边界 在 Mac 上不起作用
Ctrl+左箭头Meta+B 向左移动一个单词 Ctrl+左箭头 在 Mac 上不起作用
Ctrl+右箭头Meta+F 向右移动一个单词 Ctrl+右箭头 在 Mac 上不起作用
Meta+DMeta +Delete 向右删除单词 Meta+Delete 在 Windows 上不起作用
Meta+Backspace 向左删除单词 在 Mac 上不起作用