Readline#

稳定性:2 - 稳定

node:readline 模块提供了一个接口,用于从 可读 (Readable) 流(例如 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 实例即告完成。

事件:'error'#

当关联到 node:readline Interfaceinput 流发生错误时,会触发 'error' 事件。

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

事件:'line'#

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

如果从流中读取了新数据且流在没有最终行尾标记的情况下结束,也会触发 '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'#

当之前使用 Ctrl+Z(即 SIGTSTP)移至后台的 Node.js 进程随后使用 fg(1p) 调回前台时,会触发 'SIGCONT' 事件。

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

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

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

Windows 不支持 'SIGCONT' 事件。

事件:'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.');
});

Windows 不支持 'SIGTSTP' 事件。

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() 方法会将 datakey 标识的按键序列写入 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#

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.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> 一个以用户响应 query 的输入完成的 Promise。
  • 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:清除整行
    • 返回:此实例

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

    rl.clearScreenDown()#
    • 返回:此实例

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

    rl.commit()#

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

    rl.cursorTo(x[, y])#

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

    rl.moveCursor(dx, dy)#

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

    rl.rollback()#
    • 返回:此实例

    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[]> 历史记录行的初始列表。仅当用户设置 terminaltrue 或由内部 output 检查触发时,此选项才有意义,否则根本不会初始化历史记录缓存机制。默认值: []
    • historySize <number> 保留的历史记录行的最大数量。要禁用历史记录,请将此值设为 0。仅当用户设置 terminaltrue 或由内部 output 检查触发时,此选项才有意义,否则根本不会初始化历史记录缓存机制。默认值: 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 属性,并且在列数发生变化时触发 'resize' 事件,那么该 output 流将获得最佳兼容性(当 process.stdout 是 TTY 时会自动执行此操作)。

    使用 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() 方法清除给定 TTY 流中 dir 指定方向的当前行。

    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[]> 历史记录行的初始列表。仅当用户设置 terminaltrue 或由内部 output 检查触发时,此选项才有意义,否则根本不会初始化历史记录缓存机制。默认值: []
    • historySize <number> 保留的历史记录行的最大数量。要禁用历史记录,请将此值设为 0。仅当用户设置 terminaltrue 或由内部 output 检查触发时,此选项才有意义,否则根本不会初始化历史记录缓存机制。默认值: 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 属性,并且在列数发生变化时触发 'resize' 事件,那么该 output 流将获得最佳兼容性(当 process.stdout 是 TTY 时会自动执行此操作)。

    使用 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,则它必须处于原始模式 (raw mode)。

    任何 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 Yank(撤销删除/粘贴)之前删除的文本 仅适用于通过 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 上无效