Readline#

稳定性:2 - 稳定

源代码: lib/readline.js

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

要使用基于 Promise 的 API

import * as readline from 'node:readline/promises';const readline = require('node:readline/promises');

要使用回调和同步 API

import * as readline from 'node:readline';const readline = require('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();const readline = require('node:readline');
const { stdin: input, stdout: output } = require('node:process');

const rl = readline.createInterface({ input, output });

rl.question('What do you think of Node.js? ', (answer) => {
  // TODO: Log the answer in a database
  console.log(`Thank you for your valuable feedback: ${answer}`);

  rl.close();
});

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

类:InterfaceConstructor#

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

事件:'close'#

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

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

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

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

事件:'line'#

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

如果从流中读取了新数据,并且该流在没有最终行尾标记的情况下结束,也会触发 'line' 事件。

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

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

事件:'history'#

每当历史记录数组发生变化时,就会触发 'history' 事件。

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

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

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

事件:'pause'#

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

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

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

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

事件:'resume'#

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

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

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

事件:'SIGCONT'#

当一个 Node.js 进程先前使用 Ctrl+Z(即 SIGTSTP)移至后台,然后使用 fg(1p) 重新带回前台时,会触发 'SIGCONT' 事件。

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

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

rl.on('SIGCONT', () => {
  // `prompt` will automatically resume the stream
  rl.prompt();
}); 

'SIGCONT' 事件在 Windows 上支持。

事件:'SIGINT'#

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'#

input 流接收到 Ctrl+Z 输入(通常称为 SIGTSTP)时,会触发 'SIGTSTP' 事件。如果在 input 流接收到 SIGTSTP 时没有注册 'SIGTSTP' 事件监听器,Node.js 进程将被发送到后台。

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

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

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

rl.on('SIGTSTP', () => {
  // This will override SIGTSTP and prevent the program from going to the
  // background.
  console.log('Caught SIGTSTP.');
}); 

'SIGTSTP' 事件在 Windows 上支持。

rl.close()#

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

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

rl[Symbol.dispose]()#

rl.close() 的别名。

rl.pause()#

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

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

rl.prompt([preserveCursor])#

  • preserveCursor <boolean> 如果为 true,则防止光标位置被重置为 0

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

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

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

rl.resume()#

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

rl.setPrompt(prompt)#

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

rl.getPrompt()#

  • 返回:<string> 当前的提示字符串

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

rl.write(data[, key])#

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

如果指定了 key,则忽略 data

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

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

rl.write('Delete this!');
// Simulate Ctrl+U to delete the line written previously
rl.write(null, { ctrl: true, name: 'u' }); 

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

rl[Symbol.asyncIterator]()#

创建一个 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) {
    // Each line in the readline input will be successively available here as
    // `line`.
  }
} 

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

rl.line#

节点正在处理的当前输入数据。

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

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

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

rl.getCursorPos()#

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

Promises API#

类:readlinePromises.Interface#

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

rl.question(query[, options])#
  • query <string> 要写入 output 的语句或查询,前置于提示符。
  • options <Object>
    • signal <AbortSignal> 可选地允许使用 AbortSignal 取消 question()
  • 返回:<Promise> 一个 Promise,它会在用户响应 query 的输入时被 fulfill。

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

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

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

如果在 rl.close() 之后调用此问题,它将返回一个被拒绝的 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#

new readlinePromises.Readline(stream[, options])#
rl.clearLine(dir)#
  • dir <integer>
    • -1: 从光标向左
    • 1: 从光标向右
    • 0: 整行
  • 返回:this

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

rl.clearScreenDown()#
  • 返回:this

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

rl.commit()#

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

rl.cursorTo(x[, y])#

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

rl.moveCursor(dx, dy)#

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

rl.rollback()#
  • 返回:this

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

readlinePromises.createInterface(options)#

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

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

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

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

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

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

completer 函数的用法#

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));
  // Show all completions if none found
  return [hits.length ? hits : completions, line];
} 

completer 函数也可以返回一个 <Promise>,或者可以是异步的:

async function completer(linePartial) {
  await someAsyncWork();
  return [['123'], linePartial];
} 

回调 API#

类:readline.Interface#

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

rl.question(query[, options], callback)#
  • query <string> 要写入 output 的语句或查询,前置于提示符。
  • options <Object>
    • signal <AbortSignal> 可选地允许使用 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(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> 要写入 readline 数据的可写流。
    • completer <Function> 一个可选函数,用于 Tab 自动补全。
    • terminal <boolean> 如果 inputoutput 流应被视为 TTY,并且应向其写入 ANSI/VT100 转义码,则为 true默认值:在实例化时检查 output 流的 isTTY
    • history <string[]> 历史记录行的初始列表。此选项仅在用户或内部 output 检查将 terminal 设置为 true 时才有意义,否则根本不会初始化历史记录缓存机制。默认值: []
    • historySize <number> 保留的历史记录行的最大数量。要禁用历史记录,请将此值设置为 0。此选项仅在用户或内部 output 检查将 terminal 设置为 true 时才有意义,否则根本不会初始化历史记录缓存机制。默认值: 30
    • removeHistoryDuplicates <boolean> 如果为 true,当添加到历史记录列表的新输入行与旧行重复时,将从列表中删除旧行。默认值: false
    • prompt <string> 要使用的提示字符串。默认值: '> '
    • crlfDelay <number> 如果 \r\n 之间的延迟超过 crlfDelay 毫秒,则 \r\n 都将被视为单独的行尾输入。crlfDelay 将被强制转换为不小于 100 的数字。它可以设置为 Infinity,在这种情况下,\r 后跟 \n 将始终被视为单个换行符(这对于读取具有 \r\n 行分隔符的文件可能是合理的)。默认值: 100
    • escapeCodeTimeout <number> readline 将等待一个字符的持续时间(以毫秒为单位,当读取一个模糊的键序列时,该序列既可以使用目前为止读取的输入形成一个完整的键序列,也可以接受额外的输入来完成一个更长的键序列)。默认值: 500
    • tabSize <integer> 一个制表符等于的空格数(最小为 1)。默认值: 8
    • signal <AbortSignal> 允许使用 AbortSignal 关闭接口。中止信号将在内部调用接口上的 close
  • 返回:<readline.Interface>

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

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

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

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

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

当使用 stdin 作为输入创建 readline.Interface 时,程序在收到 EOF 字符之前不会终止。要不等待用户输入就退出,请调用 process.stdin.unref()

completer 函数的用法#

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));
  // Show all completions if none found
  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.moveCursor(stream, dx, dy[, callback])#

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

readline.emitKeypressEvents(stream[, interface])#

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

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

如果 stream 是一个 TTY,那么它必须处于原始模式。

如果 input 是一个终端,任何 readline 实例都会在其 input 上自动调用此方法。关闭 readline 实例不会停止 input 触发 'keypress' 事件。

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

示例:微型 CLI#

以下示例说明了如何使用 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);
});const { createInterface } = require('node:readline');
const rl = createInterface({
  input: process.stdin,
  output: process.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!');
  process.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,
  });
  // Note: we use the crlfDelay option to recognize all instances of CR LF
  // ('\r\n') in input.txt as a single line break.

  for await (const line of rl) {
    // Each line in input.txt will be successively available here as `line`.
    console.log(`Line from file: ${line}`);
  }
}

processLineByLine();const { createReadStream } = require('node:fs');
const { createInterface } = require('node:readline');

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

  const rl = createInterface({
    input: fileStream,
    crlfDelay: Infinity,
  });
  // Note: we use the crlfDelay option to recognize all instances of CR LF
  // ('\r\n') in input.txt as a single line break.

  for await (const line of rl) {
    // Each line in input.txt will be successively available here as `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}`);
});const { createReadStream } = require('node:fs');
const { createInterface } = require('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) => {
      // Process the line.
    });

    await once(rl, 'close');

    console.log('File processed.');
  } catch (err) {
    console.error(err);
  }
})();const { once } = require('node:events');
const { createReadStream } = require('node:fs');
const { createInterface } = require('node:readline');

(async function processLineByLine() {
  try {
    const rl = createInterface({
      input: createReadStream('big-file.txt'),
      crlfDelay: Infinity,
    });

    rl.on('line', (line) => {
      // Process the line.
    });

    await once(rl, 'close');

    console.log('File processed.');
  } catch (err) {
    console.error(err);
  }
})();

TTY 快捷键绑定#

快捷键 描述 注意
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+Left arrowMeta+B 向左移动一个单词 Ctrl+Left arrow 在 Mac 上无效
Ctrl+Right arrowMeta+F 向右移动一个单词 Ctrl+Right arrow 在 Mac 上无效
Meta+DMeta +Delete 向右删除一个单词 Meta+Delete 在 Windows 上无效
Meta+Backspace 向左删除一个单词 在 Mac 上无效