REPL
源代码: lib/repl.js
node:repl
模块提供了一个 Read-Eval-Print-Loop (REPL) 实现,它既可以作为独立程序使用,也可以包含在其他应用程序中。可以使用以下方式访问它:
import repl from 'node:repl'
const repl = require('node:repl')
设计和特性
node: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
:将 REPLcontext
重置为空对象,并清除任何正在输入的多行表达式。.exit
:关闭 I/O 流,导致 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 `Hello ${name}!`;
}
welcome('Node.js User');
// ^D
'Hello Node.js User!'
>
REPL 中的以下按键组合具有以下特殊效果:
- Ctrl+C:按下一次,与
.break
命令的效果相同。在空行上按下两次,与.exit
命令的效果相同。 - Ctrl+D:与
.exit
命令的效果相同。 - Tab:在空行上按下,显示全局和局部(作用域)变量。在输入其他内容时按下,显示相关的自动完成选项。
有关反向 i 搜索相关的按键绑定,请参见 reverse-i-search
。有关所有其他按键绑定,请参见 TTY 按键绑定。
默认评估
默认情况下,所有 repl.REPLServer
实例都使用一个评估函数来评估 JavaScript 表达式并提供对 Node.js 内置模块的访问。此默认行为可以通过在创建 repl.REPLServer
实例时传入替代评估函数来重写。
JavaScript 表达式
默认评估器支持直接评估 JavaScript 表达式:
> 1 + 1
2
> const m = 2
undefined
> m + 1
3
除非在块或函数中另有限定,否则使用 const
、let
或 var
关键字隐式声明或声明的变量都在全局范围内声明。
全局和局部作用域
默认评估器可以访问全局作用域中存在的任何变量。可以通过将其赋值给与每个 REPLServer
关联的 context
对象来显式地将变量公开给 REPL:
import repl from 'node:repl'
const msg = 'message'
repl.start('> ').context.m = msg
const repl = require('node:repl')
const msg = 'message'
repl.start('> ').context.m = msg
context
对象中的属性在 REPL 中显示为局部变量:
$ node repl_test.js
> m
'message'
默认情况下,上下文属性不是只读的。要指定只读全局变量,必须使用 Object.defineProperty()
定义上下文属性:
import repl from 'node:repl'
const msg = 'message'
const r = repl.start('> ')
Object.defineProperty(r.context, 'm', {
configurable: false,
enumerable: true,
value: msg,
})
const repl = require('node: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('node:fs')
。
> fs.createReadStream('./some/file');
全局未捕获异常
[历史]
版本 | 变更 |
---|---|
v12.3.0 | 如果将 repl 用作独立程序,则现在会触发 'uncaughtException' 事件。 |
REPL 使用 domain
模块来捕获该 REPL 会话中的所有未捕获异常。
REPL 中使用 domain
模块具有以下副作用:
- 未捕获异常仅在独立 REPL 中发出
'uncaughtException'
事件。在另一个 Node.js 程序中的 REPL 中为该事件添加侦听器会导致ERR_INVALID_REPL_INPUT
。 - 尝试使用
process.setUncaughtExceptionCaptureCallback()
会抛出ERR_DOMAIN_CANNOT_SET_UNCAUGHT_EXCEPTION_CAPTURE
错误。
_
(下划线)变量的赋值
[历史]
版本 | 变更 |
---|---|
v9.8.0 | 添加了 _error 支持。 |
默认的求值器会默认将最近求值表达式的结果赋值给特殊变量 _
(下划线)。显式地将 _
设置为某个值将禁用此行为。
> [ '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');
Uncaught Error: foo
> _error.message
'foo'
await
关键字
在顶层启用了对 await
关键字的支持。
> await Promise.resolve(123)
123
> await Promise.reject(new Error('REPL await'))
Uncaught Error: REPL await
at REPL2:1:54
> const timeout = util.promisify(setTimeout);
undefined
> const old = Date.now(); await timeout(1000); console.log(Date.now() - old);
1002
undefined
使用 REPL 中的 await
关键字的一个已知限制是它会使 const
和 let
关键字的词法作用域失效。
例如:
> const m = await Promise.resolve(123)
undefined
> m
123
> const m = await Promise.resolve(234)
undefined
> m
234
--no-experimental-repl-await
将禁用 REPL 中的顶层 await。
反向增量搜索
新增于:v13.6.0, v12.17.0
REPL 支持类似于 ZSH 的双向反向增量搜索。使用 Ctrl+R
向后搜索,使用 Ctrl+S
向前搜索。
重复的历史记录条目将被跳过。
只要按下任何一个不对应反向搜索的键,条目就会被接受。可以通过按下 Esc
或 Ctrl+R
取消。
更改方向会立即搜索当前位置预期方向上的下一个条目。
自定义评估函数
创建一个新的 repl.REPLServer
时,可以提供一个自定义评估函数。例如,这可以用来实现完全自定义的 REPL 应用程序。
以下是一个将给定数字平方化的 REPL 示例:
import repl from 'node:repl'
function byThePowerOfTwo(number) {
return number * number
}
function myEval(cmd, context, filename, callback) {
callback(null, byThePowerOfTwo(cmd))
}
repl.start({ prompt: 'Enter a number: ', eval: myEval })
const repl = require('node:repl')
function byThePowerOfTwo(number) {
return number * number
}
function myEval(cmd, context, filename, callback) {
callback(null, byThePowerOfTwo(cmd))
}
repl.start({ prompt: 'Enter a number: ', 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 输出
默认情况下,repl.REPLServer
实例在将输出写入提供的 Writable
流(默认为 process.stdout
)之前,使用 util.inspect()
方法格式化输出。showProxy
检查选项默认设置为 true,colors
选项根据 REPL 的 useColors
选项设置为 true。
可以在构造时指定 useColors
布尔选项,以指示默认写入器使用 ANSI 样式代码为 util.inspect()
方法的输出着色。
如果 REPL 作为独立程序运行,也可以通过使用 inspect.replDefaults
属性(它反映了 util.inspect()
的 defaultOptions
)从 REPL 内部更改 REPL 的检查默认值。
> util.inspect.replDefaults.compact = false;
false
> [1]
[
1
]
>
要完全自定义 repl.REPLServer
实例的输出,请在构造时传入一个新的 writer
选项函数。例如,以下示例只将任何输入文本转换为大写:
import repl from 'node: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()
}
const repl = require('node: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
新增于: v0.1.91
options
<对象> | <字符串> 查看repl.start()
- 继承: <readline.Interface>
repl.REPLServer
的实例可以使用 repl.start()
方法创建,也可以直接使用 JavaScript 的 new
关键字创建。
import repl from 'node:repl'
const options = { useColors: true }
const firstInstance = repl.start(options)
const secondInstance = new repl.REPLServer(options)
const repl = require('node:repl')
const options = { useColors: true }
const firstInstance = repl.start(options)
const secondInstance = new repl.REPLServer(options)
事件: 'exit'
新增于: v0.7.7
当 REPL 通过接收 .exit
命令作为输入、用户按下两次 + 来发出 SIGINT
信号,或按下 + 来发出输入流上的 'end'
信号而退出时,会发出 'exit'
事件。监听器回调函数在没有任何参数的情况下被调用。
replServer.on('exit', () => {
console.log('Received "exit" event from repl!')
process.exit()
})
事件:'reset'
新增于:v0.11.0
当 REPL 的上下文被重置时,会发出 'reset'
事件。只要接收到 .clear
命令作为输入,就会发生这种情况,除非 REPL 使用默认的求值器并且 repl.REPLServer
实例是用 useGlobal
选项设置为 true
创建的。监听器回调函数将只使用对 context
对象的引用作为参数被调用。
这主要用于将 REPL 上下文重新初始化到某个预定义状态:
import repl from 'node:repl'
function initializeContext(context) {
context.m = 'test'
}
const r = repl.start({ prompt: '> ' })
initializeContext(r.context)
r.on('reset', initializeContext)
const repl = require('node: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)
新增于: v0.3.0
keyword
<string> 命令关键字(不含 前导.
字符)。cmd
<Object> | <Function> 当处理命令时调用的函数。
replServer.defineCommand()
方法用于向 REPL 实例添加新的以 .
为前缀的命令。此类命令通过输入 .
后跟 keyword
来调用。cmd
可以是 Function
或具有以下属性的对象:
help
<string> 输入.help
时显示的帮助文本(可选)。action
<Function> 要执行的函数,可以选择接受单个字符串参数。
以下示例显示了添加到 REPL 实例的两个新命令:
import repl from 'node:repl'
const replServer = repl.start({ prompt: '> ' })
replServer.defineCommand('sayhello', {
help: 'Say hello',
action(name) {
this.clearBufferedCommand()
console.log(`Hello, ${name}!`)
this.displayPrompt()
},
})
replServer.defineCommand('saybye', function saybye() {
console.log('Goodbye!')
this.close()
})
const repl = require('node:repl')
const replServer = repl.start({ prompt: '> ' })
replServer.defineCommand('sayhello', {
help: 'Say hello',
action(name) {
this.clearBufferedCommand()
console.log(`Hello, ${name}!`)
this.displayPrompt()
},
})
replServer.defineCommand('saybye', function saybye() {
console.log('Goodbye!')
this.close()
})
然后可以在 REPL 实例中使用新命令:
> .sayhello Node.js User
Hello, Node.js User!
> .saybye
Goodbye!
replServer.displayPrompt([preserveCursor])
新增于: v0.1.91
preserveCursor
<布尔值>
replServer.displayPrompt()
方法准备好 REPL 实例以接收用户的输入,将配置的 prompt
打印到 output
的新一行,并恢复 input
以接受新的输入。
当输入多行内容时,会打印省略号而不是 'prompt'。
当 preserveCursor
为 true
时,光标位置不会重置为 0
。
replServer.displayPrompt
方法主要用于从使用 replServer.defineCommand()
方法注册的命令的操作函数中调用。
replServer.clearBufferedCommand()
新增于: v9.0.0
replServer.clearBufferedCommand()
方法清除任何已被缓冲但尚未执行的命令。此方法主要用于从使用 replServer.defineCommand()
方法注册的命令的操作函数中调用。
replServer.setupHistory(historyPath, callback)
新增于:v11.10.0
historyPath
<string> 历史文件路径callback
<Function> 写入历史记录就绪或发生错误时调用err
<Error>repl
<repl.REPLServer>
初始化 REPL 实例的历史日志文件。执行 Node.js 二进制文件并使用命令行 REPL 时,会默认初始化历史文件。但是,以编程方式创建 REPL 时并非如此。在以编程方式使用 REPL 实例时,使用此方法初始化历史日志文件。
repl.builtinModules
新增于:v14.5.0
所有 Node.js 模块名称的列表,例如 'http'
。
repl.start([options])
[历史]
版本 | 变更 |
---|---|
v13.4.0, v12.17.0 | preview 选项现已可用。 |
v12.0.0 | terminal 选项现在在所有情况下都遵循默认说明,并且 useColors 会检查 hasColors() (如果可用)。 |
v10.0.0 | REPL_MAGIC_MODE replMode 已移除。 |
v6.3.0 | 现在支持 breakEvalOnSigint 选项。 |
v5.8.0 | options 参数现在是可选的。 |
v0.1.91 | 在 v0.1.91 中添加 |
prompt
<字符串> 要显示的输入提示。默认值:'> '
(带尾随空格)。input
<stream.Readable> 将从中读取 REPL 输入的Readable
流。默认值:process.stdin
。output
<stream.Writable> 将向其写入 REPL 输出的Writable
流。默认值:process.stdout
。terminal
<布尔值> 如果为true
,则指定应将output
视为 TTY 终端。默认值: 实例化时检查output
流的isTTY
属性的值。eval
<函数> 用于评估每行给定输入的函数。默认值: JavaScripteval()
函数的异步包装器。eval
函数可以使用repl.Recoverable
来报错,以指示输入不完整并提示输入更多行。useColors
<布尔值> 如果为true
,则指定默认的writer
函数应包含 ANSI 颜色样式以用于 REPL 输出。如果提供自定义writer
函数,则此设置无效。默认值: 如果 REPL 实例的terminal
值为true
,则检查output
流上的颜色支持。useGlobal
<布尔值> 如果为true
,则指定默认的评估函数将使用 JavaScriptglobal
作为上下文,而不是为 REPL 实例创建一个新的单独上下文。node CLI REPL 将此值设置为true
。默认值:false
。ignoreUndefined
<布尔值> 如果为true
,则指定默认的 writer 不会输出如果命令返回值计算结果为undefined
的命令的返回值。默认值:false
。writer
<函数> 在写入output
之前,用于格式化每个命令的输出的函数。默认值:util.inspect()
。completer
<函数> 用于自定义 Tab 自动补全的可选函数。有关示例,请参阅readline.InterfaceCompleter
。replMode
<符号> 一个标志,用于指定默认评估器是否以严格模式或默认(宽松)模式执行所有 JavaScript 命令。可接受的值为:repl.REPL_MODE_SLOPPY
以宽松模式评估表达式。repl.REPL_MODE_STRICT
以严格模式评估表达式。这等效于在每个 repl 语句前加上'use strict'
。breakEvalOnSigint
<布尔值> 接收SIGINT
(例如按下 + 键)时停止评估当前代码段。这不能与自定义eval
函数一起使用。默认值:false
。preview
<布尔值> 定义 repl 是否打印自动完成和输出预览。默认值: 使用默认 eval 函数时为true
,使用自定义 eval 函数时为false
。如果terminal
为假值,则没有预览,并且preview
的值无效。
repl.start()
方法创建并启动一个 repl.REPLServer
实例。
如果 options
是一个字符串,则它指定输入提示:
import repl from 'node:repl'
// Unix 风格提示
repl.start('$ ')
const repl = require('node:repl')
// Unix 风格提示
repl.start('$ ')
Node.js REPL
Node.js 本身使用 node: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_history
文件来在 node
REPL 会话之间持久化历史记录。可以通过设置环境变量 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"
对单个运行实例启动多个 REPL 实例
可以针对单个运行的 Node.js 实例创建和运行多个 REPL 实例,这些实例共享单个 global
对象,但具有单独的 I/O 接口。
例如,以下示例在 stdin
、Unix 套接字和 TCP 套接字上提供了单独的 REPL:
import net from 'node:net'
import repl from 'node:repl'
import process from 'node:process'
let connections = 0
repl.start({
prompt: 'Node.js via stdin> ',
input: process.stdin,
output: process.stdout,
})
net
.createServer(socket => {
connections += 1
repl
.start({
prompt: 'Node.js via 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 via TCP socket> ',
input: socket,
output: socket,
})
.on('exit', () => {
socket.end()
})
})
.listen(5001)
const net = require('node:net')
const repl = require('node:repl')
let connections = 0
repl.start({
prompt: 'Node.js via stdin> ',
input: process.stdin,
output: process.stdout,
})
net
.createServer(socket => {
connections += 1
repl
.start({
prompt: 'Node.js via 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 via TCP socket> ',
input: socket,
output: socket,
})
.on('exit', () => {
socket.end()
})
})
.listen(5001)
从命令行运行此应用程序将在 stdin 上启动一个 REPL。其他 REPL 客户端可以通过 Unix 套接字或 TCP 套接字连接。例如,telnet
用于连接到 TCP 套接字,而 socat
可用于连接到 Unix 和 TCP 套接字。
通过从基于 Unix 套接字的服务器而不是 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。
此示例纯粹是为了演示如何使用不同的 I/O 流启动 Node.js REPL,仅供教育目的。它不应用于生产环境或任何安全至关重要的环境,除非采取额外的保护措施。如果您需要在实际应用程序中实现 REPL,请考虑采用替代方法来减轻这些风险,例如使用安全的输入机制并避免开放网络接口。