Node.js:readline(逐行读取)

readline 用来从可读流里 一次读一行。交互式问句走 process.stdin,读文件则把 fs.createReadStream 接到 input。引入:require('readline')require('node:readline')

现行还有 node:readline/promisesquestion 返回 Promise,方便 asyncawait。下面先按回调版把接口讲完,文末补 Promise 与 for await...of

简单示例

// readlineDemo.js
const readline = require('readline');

const readlineInst = readline.createInterface({
  input: process.stdin,
  output: process.stdout
});
readlineInst.question('请输入些什么吧?', (answer) => {
  console.log(`输入:${answer}`);
  readlineInst.close();
});

运行 node ./readlineDemo.js。创建接口后进程不会自己退出,它在 input 上等数据;处理完后必须 close()(或让 input 走到 EOF)。若 inputstdin 又不想一直等用户,可 process.stdin.unref()

逐行地读取文件流

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

const rl = readline.createInterface({
  input: fs.createReadStream('test.txt'),
  crlfDelay: Infinity
});
rl.on('line', (line) => {
  console.log(`单行:${line}`);
});

crlfDelay: Infinity 表示 \r\n 始终当成 一个 换行,适合读 Windows 文本。

Interface 类

实例由 readline.createInterface() 构造,不要自己 new。每个实例绑一条 可读 input 和一条 可写 outputoutput 用来打提示符;用户输入从 input 读。

close 事件

监听关闭,回调无参数。下列任一情况会触发:

  • 调用了 close(),实例放弃对 inputoutput 的控制
  • input 收到 end
  • input 收到 Ctrl+D,表示传输结束(EOT)
  • input 收到 Ctrl+C(SIGINT),且实例上 没有 注册 SIGINT 监听器
readlineInst.on('close', () => {
  console.log('关闭');
});

一旦触发,这个实例就结束了。

error 事件

input 出错时触发,参数是 Error

line 事件

input 收到行尾(\n\r\r\n)时触发,通常是用户按了 Enter。流在没有最后换行的情况下结束,也会再发一次 line,把最后半行交出来。

监听器拿到这一行字符串(不含换行符):

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

history 事件

历史数组变化时触发(增删都算,受 historySizeremoveHistoryDuplicates 影响)。回调参数是当前历史数组,可拿去持久化,也可在回调里改掉数组(例如不要把密码写进历史)。

readlineInst.on('history', (history) => {
  console.log(history);
});

pause 事件

下列情况触发,回调无参数:

  • input 被暂停
  • input 并未暂停,但收到了 SIGCONT(见下面的 SIGTSTPSIGCONT

没有注册 SIGINT 监听器时,Ctrl+C 会先走到 pause,这一点写在 SIGINT 事件里。

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

resume 事件

input 恢复时触发,回调无参数。

SIGINT 事件

input 收到 Ctrl+C(SIGINT)时触发,回调无参数。若此时没有注册 SIGINT 监听器,则改为触发 pause

readlineInst.on('SIGINT', () => {
  console.log('按键:Ctrl + C');
});

SIGCONT 事件

进程曾被 Ctrl+Z(SIGTSTP)送到后台,再用 fg(1p) 拉回前台时触发。若在 SIGTSTP 之前 input 已经暂停,则不会触发。回调无参数。收到后可再调 prompt(),它会自动 resume。

Windows 上不支持 SIGCONT。

readlineInst.on('SIGCONT', () => {
  readlineInst.prompt();
});

SIGTSTP 事件

input 收到 Ctrl+Z(SIGTSTP)时触发。若没有注册 SIGTSTP 监听器,Node 进程会被送到后台。用 fg(1p) 恢复后会触发 pauseSIGCONT,可在那里恢复 input。若送进后台之前 input 已经暂停,则不会触发这两项。回调无参数。

readlineInst.on('SIGTSTP', () => {
  console.log('按键:Ctrl + Z');
});

Windows 上不支持 SIGTSTP。

close()

关闭实例,撤回对 inputoutput 的控制,并触发 close 事件。

pause()

暂停 input,之后可用 resume() 恢复。

注意: pause() 不会立刻停掉实例上已经排队的其他事件(包括 line)。

prompt([preserveCursor])

output 新的一行写入当前配置的提示符,给用户一个输入位置。若 input 已暂停会先恢复;创建时 outputnullundefined 则不写提示。preserveCursortrue 时,不把光标重置到 0。

setPrompt(prompt)

设置之后 prompt() 要写到 output 的提示文案。prompt 是字符串。

getPrompt()

返回当前提示字符串。

question(query[, options], callback)

query 写到 output,等用户在 input 里回车,再把这一行交给 callbackinput 若已暂停会先恢复;output 为空则不写提示。

  • query:提示前要写出的问句
  • options.signalAbortSignal,中止这次提问(回调不再调用)
  • callback:只接收用户输入这一个参数
readlineInst.question('请输入些什么吧?', (answer) => {
  console.log(`输入:${answer}`);
});

注意: 这个 callback 不是 Node 常见的 (err, result) 形式,没有第一个 Error 参数。

带超时中止:

const ac = new AbortController();
const { signal } = ac;
readlineInst.question('请输入些什么吧?', { signal }, (answer) => {
  console.log(`输入:${answer}`);
});
setTimeout(() => ac.abort(), 10000);

resume()

input 已暂停,则恢复。

write(data[, key])

data,或 key 指定的按键序列,写到 outputkey 只在 outputTTY 时有效。input 已暂停会先恢复;output 为空则什么也不写。指定了 key 就忽略 data

  • data:字符串
  • key.ctrlkey.metakey.shift:对应修饰键
  • key.name:按键名
readlineInst.write('输出!');
readlineInst.write(null, { ctrl: true, name: 'u' }); // 模拟 Ctrl+U,删到行首

注意: write() 写进的是接口的 input,效果等同用户亲手键入。

readline 方法

clearLine(stream, dir[, callback])

dir 清除给定 TTY 流的当前行。stream可写 流。dir-1 光标左边、1 光标右边、0 整行。可选 callback 在写完后调用。返回值与 stream.write 相同:需要等 drain 时返回 false

clearScreenDown(stream[, callback])

从光标当前位置向下清屏。stream 是可写流。

createInterface(options)

创建 Interface 实例。

  • input可读 流,必填
  • output可写 流,用来写提示和回显
  • completer:Tab 补全函数,见下文
  • terminal:是否把 inputoutput 当 TTY 并写 ANSI、VT100。默认在实例化时看 output.isTTY
  • history:初始历史行数组,默认 []。只在 terminaltrue(或内部根据 output 判断为 TTY)时才会启用历史
  • historySize:最多保留多少行历史。0 关闭历史。默认 30。同样只在 TTY 时有意义
  • prompt:提示字符串,默认 '> '(带一个空格)
  • crlfDelay\r\n 间隔超过该毫秒数,则两者都当换行。会被收成不小于 100 的数。设为 Infinity\r\n 始终是一个换行。默认 100
  • removeHistoryDuplicates:新行与旧行重复时删掉旧的。默认 false
  • escapeCodeTimeout:读歧义按键序列时等待后续字符的毫秒数,默认 500
  • tabSize:一个 Tab 等于几个空格,最小 1,默认 8
  • signalAbortSignal,中止时内部调用 close()
const readlineInst = readline.createInterface({
  input: process.stdin,
  output: process.stdout,
  completer: (lines) => {
    return [['1111', '2222', '3333'], lines];
  },
  terminal: true,
  historySize: 30,
  prompt: '自定义提示符:',
  crlfDelay: 100,
  removeHistoryDuplicates: true
});
readlineInst.prompt();
readlineInst.on('line', (line) => {
  console.log(`接收到:${line}`);
});

completer 收到当前行,返回长度为 2 的数组:[匹配列表, 用来匹配的子串]。函数也可以收两个参数,第二个是回调,以便异步补全:

function completer(linePartial, callback) {
  callback(null, [['123'], linePartial]);
}

terminaltrue 时,output 最好提供 columns,并在宽度变化时发 resizeprocess.stdout 在 TTY 下会自己做)。

cursorTo(stream, x[, y][, callback])

把光标移到给定 TTY 可写流的绝对坐标 (x, y)

moveCursor(stream, dx, dy[, callback])

相对当前位置移动光标。

emitKeypressEvents(stream[, interface])

让给定 可读 流按收到的输入触发 keypressstream 若是 TTY,必须处于 raw 模式。可选传入一个 Interface:检测到粘贴时会暂时关掉自动补全。

readline 实例若发现 input 是终端,会自动调用本方法。关掉 Interface 不会 停止 input 继续发 keypress

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

Promise 与异步迭代

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

async function main() {
  const answer = await rl.question('请输入些什么吧?');
  console.log(`输入:${answer}`);
  rl.close();
}
main();

Interface 实现了异步迭代器,适合读文件,但吞吐不如直接听 line。循环被 breakthrowreturn 打断时会 close()。创建接口后就会开始消费 input,中间若先 await 别的事,前面的行会丢掉。

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

async function processLineByLine() {
  const rl = readline.createInterface({
    input: fs.createReadStream('test.txt'),
    crlfDelay: Infinity
  });
  for await (const line of rl) {
    console.log(`单行:${line}`);
  }
}
processLineByLine();

参考资料

Readline | Node.js

TTY | Node.js

© lizhao all right reserved,powered by Gitbook文件修订时间: 2026-09-02 01:42:26

results matching ""

    No results matching ""