Readline#

穩定度:2 - 穩定

node:readline 模組提供了一個介面,用於逐行讀取來自可讀取 (Readable) 串流(例如 process.stdin)的資料。

要使用基於 Promise 的 API

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

要使用回呼 (callback) 與同步 (sync) 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 Interface 相關聯的 input 串流發生錯誤時,會觸發 '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() 方法會在 output 的新行寫入 InterfaceConstructor 執行個體設定的 prompt,以便為使用者提供提供輸入的新位置。

呼叫時,如果 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#

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 串流讀取輸入時,這將追蹤目前游標在輸入字串中的位置。游標的位置決定了輸入字串中隨著輸入處理而修改的部分,以及終端機插入符號 (caret) 的渲染欄位。

rl.getCursorPos()#

  • 傳回:<Object>
    • rows <number> 游標目前所在的提示訊息行數
    • cols <number> 游標目前所在的螢幕欄數

回傳游標相對於輸入提示 + 字串的實際位置。計算中包含長輸入(換行)字串以及多行提示。

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 的輸入回應。

rl.question() 方法透過將 query 寫入 output 來顯示問題,等待使用者在 input 上提供輸入,然後調用 callback 函式並將提供的輸入作為第一個參數傳遞。

呼叫時,如果 input 串流已暫停,rl.question() 會將其恢復。

如果建立 readlinePromises.Interface 時將 output 設定為 nullundefined,則不會寫入 query

如果在 rl.close() 之後呼叫問題,它會回傳一個被拒絕 (rejected) 的 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() 方法會將清除相關 stream 目前行(由 dir 指定方向)的動作加入內部待處理動作列表中。除非在建構子中傳遞了 autoCommit: true,否則需呼叫 rl.commit() 才能看到此方法的成效。

rl.clearScreenDown()#
  • 回傳:this

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()#
  • 回傳: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> 一個 tab 等於的空格數(最小為 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' 事件,則會獲得最佳相容性(當 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];
}

Callback 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[]> 歷史紀錄行的初始列表。僅當使用者或內部 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> 一個 tab 等於的空格數(最小為 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' 事件,則會獲得最佳相容性(當 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 模式。

如果輸入是終端機,任何 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 快捷鍵#

快捷鍵 (Keybindings) 說明 備註
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 上無效