Node.js v14.18.1 文档


目录

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('What do you think of Node.js? ', (answer) => {
  // TODO:记录答案到数据库中
  console.log(`Thank you for your valuable feedback: ${answer}`);

  rl.close();
});

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

Interface#

中英对照

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

'close' 事件#

中英对照

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

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

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

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

'line' 事件#

中英对照

每当 input 流接收到行尾输入(\n\r\r\n)时,则会触发 '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` 会自动恢复流
  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', () => {
  // 这将覆盖 SIGTSTP 
  // 并且阻止程序进入后台。
  console.log('Caught 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() 方法将配置为 promptreadline.Interface 实例写入 output 中的新行,以便为用户提供用于提供输入的新位置。

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

如果 readline.Interface 是在 output 设置为 nullundefined 的情况下创建的,则不会写入提示。

rl.question(query[, options], callback)#

中英对照

  • query <string> 要写入 output 的语句或查询,位于提示之前。
  • options <Object>
    • signal <AbortSignal> 可选择允许使用 AbortController 取消 question()
  • callback <Function> 使用用户输入调用的回调函数以响应 query

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

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

如果 readline.Interface 是在 output 设置为 nullundefined 的情况下创建的,则不会写入 query

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

用法示例:

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

如果此方法被调用为 util.promisify() 的版本,则它会返回使用答案履行的 Promise。 如果使用 AbortController 取消问题,则它将使用 AbortError 拒绝。

const util = require('util');
const question = util.promisify(rl.question).bind(rl);

async function questionExample() {
  try {
    const answer = await question('What is you favorite food? ');
    console.log(`Oh, so your favorite food is ${answer}`);
  } catch (err) {
    console.error('Question rejected', err);
  }
}
questionExample();

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

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

如果 readline.Interface 是在 output 设置为 nullundefined 的情况下创建的,则不会写入 datakey

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

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

rl[Symbol.asyncIterator]()#

中英对照

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

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

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

性能无法与传统的 'line' 事件 API 相提并论。 对于性能敏感的应用程序,请改用 'line'

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

  for await (const line of rl) {
    // 逐行读取输入中的每一行
    // 都将在此处作为 `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()#

中英对照

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

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> 可选的用于制表符自动补全的函数。
    • terminal <boolean> 如果 inputoutput 流应该被视为终端,并且写入了 ANSI/VT100 转义码,则为 true默认值: 在实例化时检查 output 流上的 isTTY
    • history <string[]> 历史行的初始列表。 仅当 terminal 由用户或内部的 output 检查设置为 true 时,此选项才有意义,否则历史缓存机制根本不会初始化。 默认值: []
    • historySize <number> 保留的最大历史行数。 要禁用历史记录,则将此值设置为 0。 仅当 terminal 由用户或内部的 output 检查设置为 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> 允许使用中止信号关闭接口。 中止信号将在内部调用接口上的 close
  • 返回: <readline.Interface>

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(`Received: ${line}`);
});

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

当使用 stdin 作为输入创建 readline.Interface 时,程序将不会终止,直到它收到 EOF(在 Linux/macOS 上是 Ctrl+D,在 Windows 上是 Ctrl+Z 后跟 回车 )。 如果希望应用程序在不等待用户输入的情况下退出,则可以 unref() 标准输入流:

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));
  // 如果没有找到,则显示所有补全
  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 实例不会阻止 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: '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 循环:

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 from file: ${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('File processed.');
  } catch (err) {
    console.error(err);
  }
})();

TTY 快捷键#

中英对照

快捷键 描述 注意事项
Ctrl+Shift+Backspace 删除行左 不适用于 Linux、Mac 和 Windows
Ctrl+Shift+Delete 删除行右 不适用于 Mac
Ctrl+C 触发 SIGINT 或关闭逐行读取实例
Ctrl+H 删除左边
Ctrl+D 如果当前行为空或 EOF,则向右删除或关闭逐行读取实例 不适用于 Windows
Ctrl+U 从当前位置删除到行首
Ctrl+K 从当前位置删除到行尾
Ctrl+A 转到行首
Ctrl+E 转到行尾
Ctrl+B 后退一个字符
Ctrl+F 前进一个字符
Ctrl+L 清屏
Ctrl+N 下一个历史子项
Ctrl+P 上一个历史子项
Ctrl+Z 将正在运行的进程移到后台。 输入 fg 并按 回车键 返回。 不适用于 Windows
Ctrl+WCtrl +退格键 向后删除到单词边界 Ctrl+退格键 不适用于 Linux、Mac 和 Windows
Ctrl+Delete 向前删除到单词边界 不适用于 Mac
Ctrl+左箭头Meta+B 左边的单词 Ctrl+左箭头 不适用于 Mac
Ctrl+右箭头Meta+F 右边的单词 Ctrl+右箭头 不适用于 Mac
Meta+DMeta +删除键 删除右边的单词 Meta+删除键 不适用于 Windows
Meta+退格键 删除左边的单词 不适用于 Mac
返回顶部