repl(交互式解释器)#

稳定性: 2 - 稳定

源代码: lib/repl.js

repl 模块提供了一种“读取-求值-输出”循环(REPL)的实现,它可作为一个独立的程序或嵌入到其他应用中。 可以通过以下方式使用它:

const repl = require('repl');

设计与特性#

repl 模块导出了 repl.REPLServer 类。 当 repl.REPLServer 实例运行时,它接收用户输入的每一行,根据用户定义的解释函数解释这些输入,然后输出结果。 输入可以是 stdin,输出可以是 stdout,或者也可以连接到其他任何 Node.js

repl.REPLServer 实例支持输入的自动补全、完成的预览、精简 Emacs 风格的行编辑、多行输入、类似 ZSH 的反向i搜索、类似 ZSH 的基于子字符串的历史搜索、ANSI 风格的输出、当前 REPL 会话状态的保存与恢复、错误校正、以及可定制的解释函数。 不支持 ANSI 风格和 Emacs 风格的行编辑的终端会自动地回退到有限的特性集。

命令与特殊键#

所有 REPL 的实例都支持下列特殊命令:

  • .break - 在输入一个多行表达式的过程中,输入 .break 命令(或按下 Ctrl+C)将终止表达式的继续输入。
  • .clear - 重置 REPL 的 context 为一个空对象,并清除正在输入中的所有多行表达式。
  • .exit - 关闭输入输出流,退出 REPL。
  • .help - 显示特定命令的帮助列表。
  • .save - 保存当前 REPL 会话到一个文件: > .save ./file/to/save.js
  • .load - 读取一个文件到当前 REPL 会话。 > .load ./file/to/load.js
  • .editor 进入编辑模式(Ctrl+D 完成,Ctrl+C 取消)
> .editor
// 进入编辑模式(^D 完成,^C 取消)
function welcome(name) {
  return `你好 ${name}!`;
}

welcome('Node.js 用户');

// ^D
'你好 Node.js 用户!'
>

REPL 中下列按键组合有特殊作用:

  • Ctrl+C: 当按下一次时,与 .break 命令的效果一样。当在空白行按下两次时,与 .exit 命令的效果一样。
  • Ctrl+D: 与 .exit 命令的效果一样。
  • Tab: 当在空白行按下时,显示全局和本地作用域内的变量。当在输入时按下,显示相关的自动补全选项。

有关与反向i搜索相关的快捷键,请参见反向i搜索。 有关所有的其他快捷键,请参见 TTY 快捷键

默认的解释器#

默认情况下,所有 repl.REPLServer 实例使用了一个解释函数,它可以解释 JavaScript 表达式、提供对 Node.js 内置模块的访问。 当 repl.REPLServer 实例被创建时可以传入一个替换的解释函数,覆盖其默认的功能。

JavaScript 表达式#

默认的解释器支持直接解释 JavaScript 表达式:

> 1 + 1
2
> const m = 2
undefined
> m + 1
3

除非在块级作用域中或函数中,否则变量不管是隐式地声明还是使用 constletvar 关键字声明,都是声明在全局作用域中。

全局作用域与局部作用域#

默认的解释器提供了获取存在于全局作用域中的任何变量的途径。 可以通过给每个 REPLServer 绑定的 context 对象指定变量,来显式地把变量暴露给 REPL。 例如:

const repl = require('repl');
const msg = 'message';

repl.start('> ').context.m = msg;

context 对象的属性表现为 REPL 中的局部变量:

$ node repl_test.js
> m
'message'

默认情况下 context 的属性不是只读的。 要指定只读的全局变量, context 的属性必须使用 Object.defineProperty() 来定义:

const repl = require('repl');
const msg = 'message';

const r = repl.start('> ');
Object.defineProperty(r.context, 'm', {
  configurable: false,
  enumerable: true,
  value: msg
});

访问 Node.js 核心模块#

默认的解释器会自动加载被调用的 Node.js 核心模块到 REPL 环境中。 例如,除非被声明为一个全局变量或一个有限范围的变量,否则输入 fs 会被解释为 global.fs = require('fs')

> fs.createReadStream('./some/file');

全局的未捕获异常#

REPL 使用 domain 模块来捕获该 REPL 会话的所有未捕获的异常。

在 REPL 中对 domain 模块的这种使用具有以下的副作用:

作为独立程序:

process.on('uncaughtException', () => console.log('未捕获的异常'));

throw new Error('foobar');
// 未捕获的异常

当在另一个应用程序中使用时:

process.on('uncaughtException', () => console.log('未捕获的异常'));
// TypeError [ERR_INVALID_REPL_INPUT]: Listeners for `uncaughtException`
// cannot be used in the REPL

throw new Error('foobar');
// 抛出:
// Error: foobar

_(下划线)变量的赋值#

默认的解释器会把最近一次解释的表达式的结果赋值给变量 _ (下划线)。 显式地设置 _ 为某个值能禁用该特性。

> [ 'a', 'b', 'c' ]
[ 'a', 'b', 'c' ]
> _.length
3
> _ += 1
Expression assignment to _ now disabled.
4
> 1 + 1
2
> _
4

同样, _error 将指向最后一次看到的错误(如果有的话)。 将 _error 显式设置为值将禁用此行为。

> throw new Error('foo');
Error: foo
> _error.message
'foo'

await 关键词#

使用 --experimental-repl-await 命令行选项,将启用对 await 关键字的实验性支持。

> await Promise.resolve(123)
123
> await Promise.reject(new Error('REPL await'))
Error: REPL await
    at repl:1:45
> const timeout = util.promisify(setTimeout);
undefined
> const old = Date.now(); await timeout(1000); console.log(Date.now() - old);
1002
undefined

反向i搜索#

The REPL supports bi-directional reverse-i-search similar to ZSH. It is triggered with Ctrl+R to search backward and Ctrl+S to search forwards.

Duplicated history entires will be skipped.

Entries are accepted as soon as any button is pressed that doesn't correspond with the reverse search. Cancelling is possible by pressing Esc or Ctrl+C.

Changing the direction immediately searches for the next entry in the expected direction from the current position on.

自定义的解释函数#

当创建一个新的 repl.REPLServer 时,可以提供一个自定义的解释函数。 这可以用于实现完全定制化的 REPL 应用。

以下是 REPL 的一个假设的示例,执行从一种语言到另一种语言的文本转换:

const repl = require('repl');
const { Translator } = require('translator');

const myTranslator = new Translator('en', 'fr');

function myEval(cmd, context, filename, callback) {
  callback(null, myTranslator.translate(cmd));
}

repl.start({ prompt: '> ', eval: myEval });

可恢复的错误#

当用户正在 REPL 中输入时,按下 Enter 键会把当前行的输入发送到 eval 函数。 为了支持多行输入, eval 函数可以返回一个 repl.Recoverable 实例给提供的回调函数:

function myEval(cmd, context, filename, callback) {
  let result;
  try {
    result = vm.runInThisContext(cmd);
  } catch (e) {
    if (isRecoverableError(e)) {
      return callback(new repl.Recoverable(e));
    }
  }
  callback(null, result);
}

function isRecoverableError(error) {
  if (error.name === 'SyntaxError') {
    return /^(Unexpected end of input|Unexpected token)/.test(error.message);
  }
  return false;
}

自定义 REPL 输出#

默认情况下,在把输出写入到提供的可写流(默认为 process.stdout)之前,repl.REPLServer 实例会使用 util.inspect() 方法对输出进行格式化。 showProxy 检查选项会默认设置为 true, colors 选项会设置为 true,具体取决于 REPL 的 useColors 选项。

可以在构造时指定 useColors 布尔值选项,以指示默认的编写器使用 ANSI 样式代码来着色来自 util.inspect() 方法的输出。

如果 REPL 作为独立程序运行,则还可以使用 inspect.replDefaults 属性从 REPL 内部更改 REPL 的检查默认值util.inspect(),该属性是 util.inspect() 中的 defaultOptions 的镜像。

> util.inspect.replDefaults.compact = false;
false
> [1]
[
  1
]
>

在构造时,通过在 writer 选项传入一个新的函数,可以完全地自定义一个 repl.REPLServer 实例的输出。 例子,把输入的任何文本转换为大写:

const repl = require('repl');

const r = repl.start({ prompt: '> ', eval: myEval, writer: myWriter });

function myEval(cmd, context, filename, callback) {
  callback(null, cmd);
}

function myWriter(output) {
  return output.toUpperCase();
}

REPLServer 类#

repl.REPLServer 的实例是使用 repl.start() 方法或直接使用 JavaScript 的 new 关键字创建。

const repl = require('repl');

const options = { useColors: true };

const firstInstance = repl.start(options);
const secondInstance = new repl.REPLServer(options);

'exit' 事件#

当接收到 .exit 命令、或按下两次 Ctrl+C 发出 SIGINT 信号、或按下 Ctrl+D 发出 'end' 信号而使 REPL 被退出时,触发 'exit' 事件。 监听器的回调函数被调用时不带任何参数。

replServer.on('exit', () => {
  console.log('从 REPL 接收到 "exit" 事件!');
  process.exit();
});

'reset' 事件#

当 REPL 的上下文被重置时,触发 'reset' 事件。 每当接收到 .clear 命令时会触发该事件,除非 REPL 正在使用默认的解释器并且 repl.REPLServer 实例被创建时 useGlobal 选项被设为 true。 监听器的回调函数被调用时会带上 context 对象作为惟一的参数。

这主要被用于重新初始化 REPL 上下文,使之达到某些预定义的状态,如下面的例子:

const repl = require('repl');

function initializeContext(context) {
  context.m = 'test';
}

const r = repl.start({ prompt: '> ' });
initializeContext(r.context);

r.on('reset', initializeContext);

当代码被执行时,全局的 'm' 变量可以被修改,但随后的 .clear 命令会把它重置回初始值:

$ ./node example.js
> m
'test'
> m = 1
1
> m
1
> .clear
Clearing context...
> m
'test'
>

replServer.defineCommand(keyword, cmd)#

  • keyword <string> 命令关键字(开头不带 . 字符)。
  • cmd <Object> | <Function> 当命令被执行时调用的函数。

replServer.defineCommand() 方法用于添加新的前缀为 . 的命令到 REPL 实例。 这些命令通过输入一个 .keyword 来调用。 cmd 可以是一个函数或一个具有以下属性的对象:

  • help <string> 当键入 .help 时显示的帮助说明(可选)。
  • action <Function> 要执行的函数,可接受一个字符串参数。

例子,添加两个新命令到 REPL 实例:

const repl = require('repl');

const replServer = repl.start({ prompt: '> ' });
replServer.defineCommand('sayhello', {
  help: '打招呼',
  action(name) {
    this.clearBufferedCommand();
    console.log(`你好, ${name}!`);
    this.displayPrompt();
  }
});
replServer.defineCommand('saybye', function saybye() {
  console.log('再见!');
  this.close();
});

在 REPL 实例中使用新的命令:

> .sayhello Node.js中文网
你好,Node.js中文网!
> .saybye
再见!

replServer.displayPrompt([preserveCursor])#

replServer.displayPrompt() 方法会让 REPL 实例做好用户输入的准备,打印配置的 promptoutput 中新的一行,然后返回 input 等待新的输入。

当正在键入多行输入时,会打印省略号而不是提示符。

preserveCursortrue 时,游标位置不会被复位到 0

replServer.displayPrompt 方法主要被使用 replServer.defineCommand() 方法注册的命令的 action 函数调用。

replServer.clearBufferedCommand()#

replServer.clearBufferedCommand() 方法清除已缓冲但尚未执行的任何命令。 此方法主要用于在使用 replServer.defineCommand() 方法注册的命令的 action 函数内调用。

replServer.parseREPLKeyword(keyword[, rest])#

稳定性: 0 - 弃用.

  • keyword <string> the potential keyword to parse and execute
  • rest <any> any parameters to the keyword command
  • Returns: <boolean>

An internal method used to parse and execute REPLServer keywords. Returns true if keyword is a valid keyword, otherwise false.

replServer.setupHistory(historyPath, callback)#

初始化 REPL 实例的历史记录日志文件。 当执行 Node.js 二进制文件并使用命令行 REPL 时,默认情况下会初始化历史记录文件。 但是,以编程方式创建 REPL 时不是这种情况。 当以编程方式使用 REPL 实例时,使用此方法初始化历史记录日志文件。

repl.builtinModules#

A list of the names of all Node.js modules, e.g., 'http'.

repl.start([options])#

  • options <Object> | <string>
    • prompt <string> 要显示的输入提示符。默认值: '> '(末尾有一个空格)。
    • input <stream.Readable> REPL 输入要被读取的可读流。默认值: process.stdin
    • output <stream.Writable> REPL 输出要被写入的可写流。默认值: process.stdout
    • terminal <boolean> 如果为 true,则指定 output 应被当作一个 TTY 终端。 默认值: 初始化时检查 output 流的 isTTY 属性的值。
    • eval <Function> 当解释每行输入时使用的函数。默认值: JavaScript eval() 函数的异步封装。 eval 函数出错时会返回 repl.Recoverable,表明输入不完整并提示用户完成输入。
    • useColors <boolean> 如果为 true,则指定默认的 writer 函数可以在 REPL 输出中包含 ANSI 颜色风格。 如果提供了自定义的 writer 函数,则该参数无效。 默认值: 如果 REPL 实例的 terminal 值为 true,则检查 output 流上的颜色支持。
    • useGlobal <boolean> 如果为 true,则指定默认的解释函数使用 JavaScript global 作为上下文,而不是为 REPL 实例创建一个新的独立的上下文。    在node命令行(node CLI)交互解释器中,这个值为 true默认值: false
    • ignoreUndefined <boolean> 如果为 true,则指定默认的输出器不会输出命令返回的 undefined 值。 默认值: false
    • writer <Function> 在写入到 output 之前,该函数被调用用来格式化每个命令的输出。 默认值: util.inspect()
    • completer <Function> 可选的函数,用来自定义 Tab 键的自动补全。 详见 readline.InterfaceCompleter
    • replMode <symbol> 一个标志位,指定默认的解释器使用严格模式或默认(sloppy)模式来执行 JavaScript 命令。 可选的值有:
      • repl.REPL_MODE_SLOPPY 要使用默认模式解释表达式。
      • repl.REPL_MODE_STRICT 要使用严格模式解释表达式。该模式等同于在每个 repl 声明前加上 'use strict'
    • breakEvalOnSigint <boolean> 当接收到 SIGINT 时停止解释当前代码,比如当按下 Ctrl+C。 不能与自定义的 eval 函数同时使用。 默认值: false
    • preview <boolean> 定义 repl 是否打印自动补全并输出预览。 默认值: 如果使用默认的 eval 函数,则为 true,如果使用自定义的 eval 函数,则为 false。 如果 terminal 为假,则没有预览,并且 preview 的值无效。
  • 返回: <repl.REPLServer>

repl.start() 方法创建并启动一个 repl.REPLServer 实例。

如果 options 是一个字符串,则它指定了输入提示符:

const repl = require('repl');

// 一个 Unix 风格的提示符。
repl.start('$ ');

Node.js 的 REPL#

Node.js 自身也使用 repl 模块为执行 JavaScript 代码提供交互接口。 可以通过不带任何参数(或使用 -i 参数)地执行 Node.js 二进制文件来使用它:

$ node
> const a = [1, 2, 3];
undefined
> a
[ 1, 2, 3 ]
> a.forEach((v) => {
...   console.log(v);
...   });
1
2
3

环境变量选项#

使用以下环境变量,可以自定义 Node.js REPL 的各种行为:

  • NODE_REPL_HISTORY - 当给定了一个有效的路径,则 REPL 的历史记录将被保存到指定的文件,而不是用户目录下的 .node_repl_history 文件。 设为 ''(空字符串)将会禁用持久的 REPL 历史记录。 值两头的空格键会被去掉。 在 Windows 平台上,具有空值的环境变量是无效的,因此将此变量设置为一个或多个空格可以禁用持久的 REPL 历史记录。
  • NODE_REPL_HISTORY_SIZE - 控制历史记录的最大行数。必须是正数。默认值: 1000
  • NODE_REPL_MODE - 可以是 'sloppy''strict'。  默认值: 'sloppy',允许代码在非严格模式下运行。

历史记录#

默认情况下,Node.js REPL 模块会把 node REPL 会话之间的历史记录保存到用户目录中的 .node_repl_history 文件。 修改环境变量 NODE_REPL_HISTORY='' 可以禁用该功能。

在高级的行编辑器中使用 Node.js REPL#

对于高级的行编辑器,可以使用环境变量 NODE_NO_READLINE=1 来启动 Node.js。 这会以标准的终端配置来启动主 REPL 和调试 REPL,可以使用 rlwrap

例如,可以在 .bashrc 文件中添加:

alias node="env NODE_NO_READLINE=1 rlwrap node"

在一个 Node.js 实例中启动多个 REPL 实例#

可以在一个 Node.js 实例中创建并运行多个 REPL 实例,它们共享一个 global 对象但有独立的 I/O 接口。

例子,在 stdin、Unix socket、和 TCP socket 上分别提供了独立的 REPL:

const net = require('net');
const repl = require('repl');
let connections = 0;

repl.start({
  prompt: 'Node.js 使用 stdin> ',
  input: process.stdin,
  output: process.stdout
});

net.createServer((socket) => {
  connections += 1;
  repl.start({
    prompt: 'Node.js 使用 Unix socket> ',
    input: socket,
    output: socket
  }).on('exit', () => {
    socket.end();
  });
}).listen('/tmp/node-repl-sock');

net.createServer((socket) => {
  connections += 1;
  repl.start({
    prompt: 'Node.js 使用 TCP socket> ',
    input: socket,
    output: socket
  }).on('exit', () => {
    socket.end();
  });
}).listen(5001);

从命令行运行这个应用会在 stdin 上启动一个 REPL。 其他 REPL 客户端可以通过 Unix socket 或 TCP socket 进行连接。 例如,可以使用 telnet 连接到 TCP socket,使用 socat 连接到 Unix socket 或 TCP socket。

通过从一个基于 Unix socket 的服务器(而不是 stdin)启动一个 REPL,可以连接到一个长期运行的 Node.js 进程而无需重启它。

例子,在一个 net.Server 实例和一个 net.Socket 实例上运行一个全特性的(terminal)REPL,详见:https://gist.github.com/TooTallNate/2209310

例子,在 curl(1) 上运行一个 REPL 实例,详见:https://gist.github.com/TooTallNate/2053342