readline(逐行读取)#

稳定性: 2 - 稳定

源代码: lib/readline.js

readline 模块提供了一个接口,用于一次一行地读取可读流(例如 process.stdin)中的数据。 可以使用以下方式访问它:

const readline = require('readline');

以下的简单示例说明了 readline 模块的基本用法。

const readline = require('readline');

const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout
});

rl.question('你如何看待 Node.js 中文网?', (answer) => {
  // TODO:将答案记录在数据库中。
  console.log(`感谢您的宝贵意见:${answer}`);

  rl.close();
});

一旦调用此代码,Node.js 应用程序将不会终止,直到 readline.Interface 关闭,因为接口在 input 流上等待接收数据。

Interface 类#

readline.Interface 类的实例是使用 readline.createInterface() 方法构造的。 每个实例都关联一个 input 可读流和一个 output 可写流output 流用于为到达的用户输入打印提示,并从 input 流读取。

'close' 事件#

当发生以下任一情况时会触发 'close' 事件:

  • 调用 rl.close() 方法,且 readline.Interface 实例放弃对 input 流和 output 流的控制;
  • input 流接收到其 'end' 事件;
  • input 流接收到 <ctrl>-D 以发信号传输结束(EOT);
  • input 流接收到 <ctrl>-C 以发信号 SIGINT,并且 readline.Interface 实例上没有注册 'SIGINT' 事件监听器。

调用监听器函数不传入任何参数。

一旦触发 'close' 事件,则 readline.Interface 实例就完成了。

'line' 事件#

每当 input 流接收到行尾输入(\n\r\r\n)时就会触发 'line' 事件。 这种情况通常发生在当用户按下 <Enter> 键或 <Return> 键。

调用监听器函数时会带上包含接收到的那一行输入的字符串。

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

'pause' 事件#

当发生以下任一情况时会触发 'pause' 事件:

  • input 流被暂停。
  • input 流未暂停,且接收到 'SIGCONT' 事件。(参见 'SIGTSTP' 事件和 'SIGCONT' 事件)

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

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

'resume' 事件#

每当 input 流恢复时,就会触发 'resume' 事件。

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

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

'SIGCONT' 事件#

当先前使用 <ctrl>-Z(即 SIGTSTP)移入后台的 Node.js 进程使用 fg(1p) 返回到前台时,就会触发 'SIGCONT' 事件。

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

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

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

Windows 上不支持 'SIGCONT' 事件。

'SIGINT' 事件#

每当 input 流接收到 <ctrl>-C 输入(通常称为 SIGINT)时,就会触发 'SIGINT' 事件。 如果当 input 流接收到 SIGINT 时没有注册 'SIGINT' 事件监听器,则会触发 'pause' 事件。

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

rl.on('SIGINT', () => {
  rl.question('确定要退出吗?', (answer) => {
    if (answer.match(/^y(es)?$/i)) rl.pause();
  });
});

'SIGTSTP' 事件#

每当 input 流接收到 <ctrl>-Z 输入(通常称为 SIGTSTP)时,就会触发 'SIGTSTP' 事件。 如果当 input 流接收到 SIGTSTP 时没有注册 'SIGTSTP' 事件监听器,则 Node.js 进程将被发送到后台。

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

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

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

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

Windows 上不支持 'SIGTSTP' 事件。

rl.close()#

rl.close() 方法会关闭 readline.Interface 实例,并放弃对 inputoutput 流的控制。 当调用时,将触发 'close' 事件。

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

rl.pause()#

rl.pause() 方法会暂停 input 流,允许稍后在必要时恢复它。

调用 rl.pause() 不会立刻暂停 readline.Interface 实例触发的其他事件(包括 'line')。

rl.prompt([preserveCursor])#

  • preserveCursor <boolean> 如果为 true,则阻止将光标落点重置为 0

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

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

如果 readline.Interface 创建时 output 被设置为 nullundefined,则不会写入提示。

rl.question(query, callback)#

  • query <string> 要写入 output 的语句或询问,前置于提示符。
  • callback <Function> 回调函数,调用时传入用户的输入以响应 query

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

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

如果 readline.Interface 创建时 output 被设置为 nullundefined,则不会写入 query

用法示例:

rl.question('你最喜欢的食物是什么?', (answer) => {
  console.log(`你最喜欢的食物是 ${answer}`);
});

传给 rl.question()callback 函数不遵循接受 Error 对象或 null 作为第一个参数的经典模式。 调用 callback 时使用提供的答案作为唯一的参数。

rl.resume()#

如果 input 流已暂停,则 rl.resume() 方法将恢复它。

rl.setPrompt(prompt)#

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

rl.write(data[, key])#

rl.write() 方法将 datakey 标识的按键序列写入 output。 仅当 outputTTY 文本终端时才支持 key 参数。 有快捷键组合的列表,请参见 TTY 快捷键

如果指定了 key,则忽略 data

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

如果 readline.Interface 创建时 output 被设置为 nullundefined,则不会写入 datakey

rl.write('删除这个!');
// 模拟 Ctrl+u 删除先前写入的行。
rl.write(null, { ctrl: true, name: 'u' });

rl.write() 方法将数据写入 readlineInterfaceinput,就像它是由用户提供的一样。

rl[Symbol.asyncIterator]()#

创建一个 AsyncIterator 对象,该对象以字符串形式迭代输入流中的每一行。 这个方法允许 readline.Interface 对象使用 for await...of 循环的异步迭代。

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

如果循环以 breakthrowreturn 终止,则 rl.close() 将会被调用。 换句话说,对 readline.Interface 的迭代将会始终完全消费输入流。

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

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

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

readline.createInterface() will start to consume the input stream once invoked. Having asynchronous operations between interface creation and asynchronous iteration may result in missed lines.

rl.line#

The current input data being processed by node.

This can be used when collecting input from a TTY stream to retrieve the current value that has been processed thus far, prior to the line event being emitted. Once the line event has been emitted, this property will be an empty string.

Be aware that modifying the value during the instance runtime may have unintended consequences if rl.cursor is not also controlled.

If not using a TTY stream for input, use the 'line' event.

One possible use case would be as follows:

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.cursor#

The cursor position relative to rl.line.

This will track where the current cursor lands in the input string, when reading input from a TTY stream. The position of cursor determines the portion of the input string that will be modified as input is processed, as well as the column where the terminal caret will be rendered.

rl.getCursorPos()#

  • Returns: <Object>
    • rows <number> the row of the prompt the cursor currently lands on
    • cols <number> the screen column the cursor currently lands on

Returns the real position of the cursor in relation to the input prompt + string. Long input (wrapping) strings, as well as multiple line prompts are included in the calculations.

readline.clearLine(stream, dir[, callback])#

  • stream <stream.Writable>
  • dir <number>
    • -1: 从光标向左。
    • 1: 从光标向右
    • 0: 整行。
  • callback <Function> 操作完成后调用。
  • 返回: <boolean> 如果 stream 希望调用的代码在继续写入附加的数据之前等待 'drain' 事件触发,则为 false,否则为 true

readline.clearLine() 方法在由 dir 标识的指定方向上清除给定的 TTY 流的当前行。

readline.clearScreenDown(stream[, callback])#

  • stream <stream.Writable>
  • callback <Function> 操作完成后调用。
  • 返回: <boolean> 如果 stream 希望调用的代码在继续写入附加的数据之前等待 'drain' 事件触发,则为 false,否则为 true

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

readline.createInterface(options)#

  • options <Object>
    • input <stream.Readable> 要监听的可读流。此选项是必需的。
    • output <stream.Writable> 将逐行读取数据写入的可写流
    • completer <Function> 用于 Tab 自动补全的可选函数。
    • terminal <boolean> 如果 inputoutput 应该被视为 TTY,并且写入 ANSI/VT100 转义码,则为 true默认值: 实例化时在 output 流上检查 isTTY
    • historySize <number> 保留的最大历史记录行数。 要禁用历史记录,请将此值设置为 0。 仅当用户或内部 output 检查将 terminal 设置为 true 时,此选项才有意义,否则根本不会初始化历史记录缓存机制。 默认值: 30
    • prompt - 要使用的提示字符串。默认值: '> '
    • crlfDelay <number> 如果 \r\n 之间的延迟超过 crlfDelay 毫秒,则 \r\n 将被视为单独的行尾输入。 crlfDelay 将被强制转换为不小于 100 的数字。 可以设置为 Infinity, 这种情况下, \r 后跟 \n 将始终被视为单个换行符(对于使用 \r\n 行分隔符的文件读取可能是合理的)。 默认值: 100
    • removeHistoryDuplicates <boolean> 如果为 true, 则当添加到历史列表的新输入行与旧的输入行重复时,将从列表中删除旧行。 默认值: false
    • escapeCodeTimeout <number> readline 将会等待一个字符的持续时间(当以毫秒为单位读取模糊键序列时,可以使用输入读取到目前为止形成完整的键序列,并且可以采取额外的输入来完成更长的键序列)。 默认值: 500
    • tabSize <integer> 制表符的空格数(最小值为 1)。默认值: 8

readline.createInterface() 方法创建一个新的 readline.Interface 实例。

const readline = require('readline');
const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout
});

一旦创建了 readline.Interface 实例,最常见的用例是监听 'line' 事件:

rl.on('line', (line) => {
  console.log(`接收到:${line}`);
});

如果此实例的 terminaltrue,则若它定义了一个 output.columns 属性则 output 流会获得最佳兼容性,并且如果或当列发生变化时, output 上会触发 'resize' 事件(当它是 TTY 时,process.stdout 会自动执行此操作)。

completer 函数的使用#

completer 函数将用户输入的当前行作为参数,并返回包含以下两个条目的数组:

  • 包含匹配补全输入的数组。
  • 用于匹配的子字符串。

例如:[[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])#

readline.cursorTo() 方法将光标移动到给定的 TTY stream 中的指定位置。

readline.emitKeypressEvents(stream[, interface])#

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

可选的 interface 指定 readline.Interface 实例,当检测到复制粘贴输入时,将禁用自动补全。

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

如果 input 是终端,则由其 input 上的任何 readline 实例自动调用。 关闭 readline 实例不会阻止 input 触发 'keypress' 事件。

readline.emitKeypressEvents(process.stdin);
if (process.stdin.isTTY)
  process.stdin.setRawMode(true);

readline.moveCursor(stream, dx, dy[, callback])#

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

示例:微型 CLI#

以下示例说明了如何使用 readline.Interface 类来实现一个小型命令行界面:

const readline = require('readline');
const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout,
  prompt: '请输入> '
});

rl.prompt();

rl.on('line', (line) => {
  switch (line.trim()) {
    case 'hello':
      console.log('world!');
      break;
    default:
      console.log(`你输入的是:'${line.trim()}'`);
      break;
  }
  rl.prompt();
}).on('close', () => {
  console.log('再见!');
  process.exit(0);
});

示例:逐行读取文件流#

readline 的一个常见用例是每次一行地输入文件。 最简单的方法是利用 fs.ReadStream API 以及 for await...of 循环:

const fs = require('fs');
const readline = require('readline');

async function processLineByLine() {
  const fileStream = fs.createReadStream('input.txt');

  const rl = readline.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' 事件:

const fs = require('fs');
const readline = require('readline');

const rl = readline.createInterface({
  input: fs.createReadStream('sample.txt'),
  crlfDelay: Infinity
});

rl.on('line', (line) => {
  console.log(`文件中的每一行: ${line}`);
});

目前, for await...of 循环可能会慢一点。 如果 async / await 流和速度都是必不可少的,可以应用混合方法:

const { once } = require('events');
const { createReadStream } = require('fs');
const { createInterface } = require('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('文件已处理');
  } catch (err) {
    console.error(err);
  }
})();

TTY 快捷键#

Keybindings Description Notes
ctrl + shift + backspace Delete line left Doesn't work on Linux, Mac and Windows
ctrl + shift + delete Delete line right Doesn't work on Mac
ctrl + c Emit SIGINT or close the readline instance
ctrl + h Delete left
ctrl + d Delete right or close the readline instance in case the current line is empty / EOF Doesn't work on Windows
ctrl + u Delete from the current position to the line start
ctrl + k Delete from the current position to the end of line
ctrl + a Go to start of line
ctrl + e Go to to end of line
ctrl + b Back one character
ctrl + f Forward one character
ctrl + l Clear screen
ctrl + n Next history item
ctrl + p Previous history item
ctrl + z Moves running process into background. Type fg and press enter to return. Doesn't work on Windows
ctrl + w or ctrl + backspace Delete backward to a word boundary ctrl + backspace Doesn't work on Linux, Mac and Windows
ctrl + delete Delete forward to a word boundary Doesn't work on Mac
ctrl + left or meta + b Word left ctrl + left Doesn't work on Mac
ctrl + right or meta + f Word right ctrl + right Doesn't work on Mac
meta + d or meta + delete Delete word right meta + delete Doesn't work on windows
meta + backspace Delete word left Doesn't work on Mac