首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

shell

模块

shell

模块摘要

Erlang shell。

描述

这个模块提供了一个Erlang shell。

shell是用于输入表达式序列的用户界面程序。表达式被评估并返回一个值。历史机制保存先前的命令和它们的值,然后可以将它们合并到后面的命令中。用户可以通过交互方式,通过调用history/1以及results/1设置应用程序配置参数shell_history_lengthshell_saved_resultsSTDLIB应用程序来确定要保存的命令和结果的数量。

shell使用辅助进程来评估命令以保护历史机制免受异常。默认情况下,评估程序进程在发生异常时被终止,但通过调用catch_exception/1或通过设置shell_catch_exceptionSTDLIB应用程序的应用程序配置参数,可以更改此行为。另见下面的例子。

用户表达式中生成的变量绑定和本地进程字典更改将被保留,并且可以在以后的命令中使用这些变量来访问它们的值。绑定也可以被遗忘,因此变量可以被重用。

特殊的shell命令都具有(本地)函数调用的语法。它们被评估为正常函数调用,并且许多命令可以用于一个表达式序列中。

如果一个命令(本地函数调用)不被shell识别,首先尝试在模块中找到该函数,在该函数中user_default可以放置自定义的本地命令。如果找到,则评估该功能,否则尝试评估模块中的功能shell_default。模块user_default必须显式加载。

该shell还允许用户启动多个并发作业。作业可以被看作是一组可以与shell进行通信的进程。

有一些支持在shell中读取和打印记录。在编译期间,记录表达式被转换为元组表达式。在运行时,它不知道一个元组是否代表一条记录,并且编译器使用的记录定义在运行时不可用。因此,为了读取记录语法并尽可能将记录元素打印为记录,记录定义必须由外壳本身维护。

下面介绍用于阅读,定义,遗忘,列表和打印记录的shell命令。请注意,每个作业都有自己的一组记录定义。为了便于处理,每次开始新作业时都要读取模块中的定义shell_default以及user_default(如果加载)记录。例如,添加以下行以user_default使file_infoshell中的定义可用:

代码语言:javascript
复制
-include_lib("kernel/include/file.hrl").

shell以两种模式运行:

  • Normal (possibly restricted)模式,在该模式中可以编辑命令和计算表达式。
  • 工作控制模式JCL,其中作业可以启动、删除、分离和连接。

只有当前连接的作业可以与外壳“talk”。

shell命令

b()

打印当前变量绑定。

f()

移除所有变量绑定。

f(X)

删除变量的绑定X

h()

打印历史记录。

history(N)

设置要保留在历史列表中的先前命令的数量N。之前的号码被返回。默认为20。

results(N)

设置要保留在历史列表中的以前命令的结果数N。之前的号码被返回。默认为20。

e(N)

重复命令N,如果N是肯定的。如果它是负数,N则重复上一个命令(即e(-1)重复上一个命令)。

v(N)

N如果N是正值,则使用当前命令中的命令返回值。如果为负数,N则使用前一个命令的返回值(即,v(-1)使用前一个命令的值)。

help()

评估shell_default:help()

c(Mod)

评估shell_default:c(Mod)。这将编译和加载模块,Mod并在必要时清除旧版本的代码。Mod可以是模块名称或源文件路径,有或没有.erl扩展名。

catch_exception(Bool)

设置评估程序进程的异常处理。之前的异常处理被返回。default(false)将在发生异常时终止评估程序进程,这会导致shell创建新的评估程序进程。当异常处理设置true为时,评估程序将继续。这意味着,例如,端口和ETS表格以及链接到评估程序的流程都可以承受例外。

rd(RecordName, RecordDefinition)

在shell中定义一条记录。RecordName是一个原子并RecordDefinition列出字段名称和默认值。通常使用rr/1,2,3下面描述的命令将记录定义通知给shell ,但有时在动态定义记录时很方便。

rf()

删除所有记录定义,然后从模块中读取记录定义shell_defaultuser_default(如果已加载)。返回定义记录的名称。

rf(RecordNames)

删除选定的记录定义。RecordNames是记录名称或记录名称列表。要删除所有记录定义,请使用'_'

rl()

打印所有记录定义。

rl(RecordNames)

打印选定的记录定义。RecordNames是记录名称或记录名称列表。

rp(Term)

使用shell已知的记录定义打印一个术语。所有的Term都是印刷的;深度不限于打印返回值时的情况。

rr(Module)

从模块的BEAM文件中读取记录定义。如果BEAM文件中没有记录定义,则会找到并读取源文件。返回读取的记录定义的名称。Module是一个原子。

rr(Wildcard)

从文件中读取记录定义。任何记录名称的现有定义都将被替换。Wildcard是定义的通配符字符串filelib(3),但不是原子。

rr(WildcardOrModule, RecordNames)

从文件中读取记录定义,但放弃RecordNames(记录名称或记录名称列表)中未提及的记录名称。

rr(WildcardOrModule, RecordNames, Options)

从文件中读取记录定义。编译器选项{i, Dir}{d, Macro}{d, Macro, Value}被识别并用于设置包含路径和宏定义。要读取所有记录定义,请将其'_'用作值RecordNames

以下示例与shell的对话框很长。开始的命令>是shell的输入。所有其他行都从shell输出。

代码语言:javascript
复制
strider 1> erl
Erlang (BEAM) emulator version 5.3 [hipe] [threads:0]

Eshell V5.3  (abort with ^G)
1> Str = "abcd".
"abcd"

命令1将变量设置Str为字符串"abcd"

代码语言:javascript
复制
2> L = length(Str).
4

命令2设置L为字符串的长度Str

代码语言:javascript
复制
3> Descriptor = {L, list_to_atom(Str)}.
{4,abcd}

命令3构建元组Descriptor,评估BIF list_to_atom/1

代码语言:javascript
复制
4> L. 
4

命令4打印变量的值L

代码语言:javascript
复制
5> b().
Descriptor = {4,abcd}
L = 4
Str = "abcd"
ok

命令5评估内部shell命令b(),它是“绑定”的缩写。这将打印当前shell变量及其绑定。ok最后是函数的返回值b()

代码语言:javascript
复制
6> f(L). 
ok

命令6评估内部shell命令f(L)(“忘记”的缩写)。变量的值L被删除。

代码语言:javascript
复制
7> b().
Descriptor = {4,abcd}
Str = "abcd"
ok

命令7打印新绑定。

代码语言:javascript
复制
8> f(L).
ok

命令8没有效果,因为L没有价值。

代码语言:javascript
复制
9> {L, _} = Descriptor.
{4,abcd}

命令9执行模式匹配操作Descriptor,将一个新值绑定到L

代码语言:javascript
复制
10> L.
4

命令10打印当前值L

代码语言:javascript
复制
11> {P,Q,R} =描述符。
**异常错误:右侧值不匹配{4,abcd}

命令11尝试匹配{P, Q, R}反对Descriptor,这是{4, abc}。匹配失败并且没有新的变量被绑定。以“ ** exception error:” 开头的打印输出不是表达式的值(该表达式因为其评估失败而没有值),而是由系统打印以警告用户发生了错误。其他变量(LStr等等)的值保持不变。

代码语言:javascript
复制
12> P.
* 1: variable 'P' is unbound
13> Descriptor.
{4,abcd}

命令12和13显示P未绑定,因为上一个命令失败,并且Descriptor没有更改。

代码语言:javascript
复制
14>{P, Q} = Descriptor.
{4,abcd}
15> P.
4

命令14和15显示正确的匹配位置PQ都被绑住了。

代码语言:javascript
复制
16> f().
ok

命令16清除所有绑定。

接下来的几个命令假定它test1:demo(X)被定义如下:

demo(X) ->

put(aa, worked),

X = 1,

X + 10.

代码语言:javascript
复制
17> put(aa, hello).
undefined
18> get(aa).
hello

命令17和18设置并检查aa过程字典中项目的值。

代码语言:javascript
复制
19> Y = test1:demo(1).
11

命令19评估test1:demo(1)。评估成功,并且在进程字典中所做的更改对shell可见。aa在命令20中可以看到字典项目的新价值。

代码语言:javascript
复制
20> get()。
[{AA,工作}]
21> put(aa,你好)。
工作
22> Z = test1:demo(2)。
**异常错误:右侧值不匹配1
     在函数test1:demo / 1中

命令21和22将字典项目的值更改aahello并呼叫test1:demo(2)。评估失败test1:demo(2),丢弃在错误发生前对字典所做的更改。

代码语言:javascript
复制
23> Z.
* 1: variable 'Z' is unbound
24> get(aa).
hello

命令23和24显示Z未被绑定,并且该字典项目aa保留其原始值。

代码语言:javascript
复制
25> erase(), put(aa, hello).
undefined
26> spawn(test1, demo, [1]).
<0.57.0>
27> get(aa).
hello

命令25,26和27显示了test1:demo(1)在后台评估的效果。在这种情况下,表达式在新产生的进程中被评估。进程字典中所做的任何更改都是新生成的进程的本地操作,因此对于shell不可见。

代码语言:javascript
复制
28> io:format("hello hello\n").
hello hello
ok
29> e(28).
hello hello
ok
30> v(28).
ok

命令28,29和30使用shell的历史功能。命令29重新评估命令28.命令30使用命令28的值(结果)。在纯函数(没有副作用的函数)的情况下,结果是相同的。对于具有副作用的功能,结果可能不同。

接下来的几条命令显示了一些记录操作。假设ex.erl定义一条记录如下:

-record(rec, {a, b = val()}).

val() ->

3.

代码语言:javascript
复制
31> c(ex).
{ok,ex}
32> rr(ex).
[rec]

命令31和32编译文件ex.erl并在其中读取记录定义ex.beam。如果编译器未在BEAM文件上输出任何记录定义,则rr(ex)尝试从源文件中读取记录定义。

代码语言:javascript
复制
33> rl(rec).
-record(rec,{a,b = val()}).
ok

命令33打印名为记录的定义rec

代码语言:javascript
复制
34> #rec {}。
**异常错误:未定义的shell命令val / 0

命令34试图创建一条rec记录,但由于函数val/0未定义而失败。

代码语言:javascript
复制
35> #rec{b = 3}.
#rec{a = undefined,b = 3}

命令35显示了解决方法:显式地为无法初始化的记录字段分配值。

代码语言:javascript
复制
36> rp(v(-1)).
#rec{a = undefined,b = 3}
ok

命令36使用shell维护的记录定义打印新创建的记录。

代码语言:javascript
复制
37> rd(rec, {f = orddict:new()}).
rec

命令37直接在shell中定义一条记录。该定义取代了从文件读取的定义ex.beam

代码语言:javascript
复制
38> #rec{}.
#rec{f = []}
ok

命令38使用新定义创建记录,并打印结果。

代码语言:javascript
复制
39> rd(rec, {c}), A.
* 1: variable 'A' is unbound
40> #rec{}.
#rec{c = undefined}
ok

命令39和40显示记录定义被更新为副作用。命令的计算失败,但是rec已经完成了。

对于下一个命令,假定test1:loop(N)定义如下:

loop(N) ->

io:format("Hello Number: ~w~n", [N]),

loop(N+1).

代码语言:javascript
复制
41> test1:loop(0).
Hello Number: 0
Hello Number: 1
Hello Number: 2
Hello Number: 3

User switch command
 --> i
 --> c
.
.
.
Hello Number: 3374
Hello Number: 3375
Hello Number: 3376
Hello Number: 3377
Hello Number: 3378
** exception exit: killed

命令41进行评估test1:loop(0),使系统进入无限循环。此时用户键入^G(Control G),它暂停当前进程中的输出,该进程停留在循环中并激活JCL模式。在JCL模式下,用户可以启动和停止作业。

在这种情况下,命令i(“中断”)终止循环程序,并且命令c再次连接到shell。由于该过程在我们杀死它之前在后台运行,因此在显示消息“ ** exception exit: killed” 之前会发生更多打印输出。

代码语言:javascript
复制
42> E = ets:new(t, []).
#Ref<0.1662103692.2407923716.214192>

命令42创建一个ETS表。

代码语言:javascript
复制
43> ets:insert({d,1,2}).
** exception error: undefined function ets:insert/1

命令43尝试向ETS表中插入一个元组,但第一个参数(表)缺失。异常会杀死评估者进程。

代码语言:javascript
复制
44> ets:insert(E, {d,1,2}).
** exception error: argument is of wrong type
     in function  ets:insert/2
        called as ets:insert(16,{d,1,2})

命令44纠正了错误,但ETS表已被销毁,因为它被死亡的评估者进程所拥有。

代码语言:javascript
复制
45> f(E).
ok
46> catch_exception(true).
false

命令46将评估程序进程的异常处理设置为true。启动Erlang时,也可以设置异常处理erl -stdlib shell_catch_exception true

代码语言:javascript
复制
47> E = ets:new(t, []).
#Ref<0.1662103692.2407923716.214197>
48> ets:insert({d,1,2}).
* exception error: undefined function ets:insert/1

命令48与命令43一样犯了同样的错误,但这次评估者进程仍在继续。打印输出开始处的单星表明异常已被捕获。

代码语言:javascript
复制
49> ets:insert(E, {d,1,2}).
true

命令49成功地将元组插入到ETS表中。

代码语言:javascript
复制
50> ets:insert(#Ref<0.1662103692.2407923716.214197>, {e,3,4}).
true

命令50将另一个元组插入到ETS表中。这次第一个参数是表标识符本身。shell可以用pids(<0.60.0>),ports(#Port<0.536>),references(#Ref<0.1662103692.2407792644.214210>)和外部函数(#Fun<a.b.1>)解析命令,但是命令会失败,除非可以在正在运行的系统中创建相应的pid,port,reference或function。

代码语言:javascript
复制
51> halt().
strider 2>

命令51退出Erlang运行时系统。

JCL模式

当shell启动时,它会启动一个评估程序。这个过程以及它所产生的任何本地过程都被称为a job。据说只有当前的工作connected可以用标准I/O执行操作。所有其他的工作,这被认为是detached,是blocked,如果他们试图使用标准I/O。

所有不使用标准I/O的作业都以正常方式运行。

shell转义键^G(Control G)分离当前作业并激活JCL模式。该JCL模式提示符"-->"。如果"?"在提示中输入,将显示以下帮助消息:

代码语言:javascript
复制
--> ?
c [nn]            - connect to job
i [nn]            - interrupt job
k [nn]            - kill job
j                 - list all jobs
s [shell]         - start local shell
r [node [shell]]  - start remote shell
q                 - quit erlang
? | h             - this message

这些JCL命令具有以下含义:

c [nn]

连接到工作号码<nn>或当前工作。标准外壳被恢复。当前作业使用标准I/O的操作与用户对shell的输入交错。

i [nn]

停止作业号nn或当前作业的当前评估程序进程,但不会终止进程。因此,任何变量绑定和进程字典都会被保留,并且作业可以重新连接。这个命令可以用来中断无限循环。

k [nn]

杀死工作号码nn或当前工作。如果作业中所有派生的进程都没有评估group_leader/1BIF并且位于本地计算机上,则会终止进程。在远程节点上产生的进程不会被终止。

j

列出所有工作。打印所有已知作业的列表。当前作业名称前缀为'*'。

s

开始一项新工作。这被分配了新的索引[nn],可以在引用中使用。

s [shell]

开始一项新工作。这被分配了新的索引[nn],可以在引用中使用。如果shell指定了可选参数,则假定它是一个实现替代shell的模块。

r [node]

启动远程作业node。这在分布式Erlang中使用,以允许在一个节点上运行的shell来控制在节点网络上运行的许多应用程序。如果shell指定了可选参数,则假定它是一个实现替代shell的模块。

q

退出Erlang。注意,如果Erlang以忽略break,,+Bisystem标志启动(这可能很有用,例如在运行受限shell时,请参阅下一节),此选项将被禁用。

?

显示上面的帮助消息。

shell转义的行为可以通过STDLIB应用程序变量进行更改shell_esc。变量的值可以是jclerl -stdlib shell_esc jcl)或aborterl -stdlib shell_esc abort)。第一个选项设置^G为激活JCL模式(这也是默认行为)。后者设置^G为终止当前shell并启动一个新shell。JCL模式在shell_esc设置为时不能被调用abort

如果您希望Erlang节点从开始(而不是默认的本地作业)开始启用远程作业,请使用标志启动Erlang -remsh,例如,erl -sname this_node -remsh other_node@other_host

限制Shell

该shell可以在受限模式下启动。在这种模式下,shell只在允许的情况下评估函数调用。例如,该功能可以防止用户意外地从提示中调用可能损害正在运行的系统的功能(与系统标志结合使用+Bi)。

当受限shell评估表达式并遇到函数调用或运算符应用程序时,它会调用回调函数(有关正在讨论的函数调用的信息)。这个回调函数返回true,让shell继续进行评估,或者false放弃。有两种可供用户执行的回调函数:

  • local_allowed(Func, ArgList, State) -> {boolean(),NewState} 这用于确定是否允许Func使用参数调用本地函数ArgList
  • non_local_allowed(FuncSpec, ArgList, State) -> {boolean(),NewState} | {{redirect,NewFuncSpec,NewArgList},NewState}

这用于确定是否允许调用带有参数的非本地函数FuncSpec{Module,Func}或乐趣)ArgList。返回值{redirect,NewFuncSpec,NewArgList}可用于让shell评估除FuncSpecand 指定的函数以外的其他函数ArgList

这些回调函数从本地和非本地评估函数处理程序中调用,如erl_eval手册页中所述。(参数in ArgList在调用回调函数之前进行评估。)

参数State是一个元组{ShellState,ExprState}。返回值NewState具有相同的形式。这可以用于在调用回调函数之间传递状态。数据ShellState通过整个shell会话保存在生命中。ExprState只有通过评估当前表达式才能将数据保存在生命中。

启动受限shell会话的方法有两种:

  • 使用STDLIB应用程序变量restricted_shell并指定回调模块的名称作为其值。实施例(与中实现回调函数callback_mod.erl): $ erl -stdlib restricted_shell callback_mod
  • 从一个正常的shell会话中调用函数start_restricted/1。这退出当前评估者并以限制模式启动一个新评估者。

注:

  • 当激活或取消激活限制外壳模式时,分别在受限制或正常模式下运行的节点上启动新作业。
  • 如果在特定节点上启用了限制模式,则连接到此节点的远程外壳也以限制模式运行。
  • 回调函数不能用于允许或不允许执行从编译代码调用的函数(只有在shell提示符处输入的表达式调用的函数)。加载回调模块时的错误以不同方式处理,具体取决于受限shell的激活方式:
  • 如果通过在模拟器启动过程中设置STDLIB变量来激活受限shell,并且无法加载回调模块,那么默认的受限Shell只允许使用这些命令q()init:stop()用作回退。
  • 如果使用受限shell start_restricted/1并且无法加载回调模块,则将错误报告发送到错误记录器,并且调用返回{error,Reason}

催促

默认的shell提示符功能显示节点的名称(如果节点可以是分布式系统的一部分)和当前命令编号。用户可以通过调用prompt_func/1或通过设置shell_prompt_funcSTDLIB应用程序的应用程序配置参数来自定义提示功能。

一个定制的提示函数被声明为一个元组{Mod, Func}。该函数被称为Mod:Func(L),其中L是由shell创建的键值对列表。目前只有一对:{history, N}N当前命令号在哪里。该函数是返回一个字符或一个原子的列表。这个约束是由于Erlang I / O协议。列表和原子中允许代码点255以外的Unicode字符。请注意,在限制模式下,Mod:Func(L)必须允许调用或调用默认的shell提示符函数。

出口

catch_exception(Bool) -> boolean()

类型

设置评估程序进程的异常处理。之前的异常处理被返回。default(false)将在发生异常时终止评估程序进程,这会导致shell创建新的评估程序进程。当异常处理设置true为时,评估程序仍处于运行状态,这意味着例如端口和ETS表以及链接到评估程序的进程在异常情况下仍然存在。

history(N) -> integer() >= 0

类型

设置要保留在历史列表中的先前命令的数量N。之前的号码被返回。默认为20。

prompt_func(PromptFunc) -> PromptFunc2

类型

将shell提示符函数设置为PromptFunc。之前的提示功能被返回。

results(N) -> integer() >= 0

类型

设置要保留在历史列表中的以前命令的结果数N。之前的号码被返回。默认为20。

start_restricted(Module) -> {error, Reason}

类型

退出一个正常的shell并启动一个受限制的shell。Module指定的功能的回调模块local_allowed/3non_local_allowed/3。该函数旨在从shell中调用。

如果回调模块无法加载,则返回错误元组。该Reason错误元组是试图加载回调模块的代码时的代码加载器返回的一个。

stop_restricted() -> no_return()

退出受限制的shell并启动一个正常的shell。该函数旨在从shell中调用。

strings(Strings) -> Strings2

类型

设置漂亮的列表打印Strings。该标志的前一个值被返回。

该标志也可以由STDLIB应用程序变量设置shell_strings。默认为true,这意味着尽可能使用字符串语法打印整数列表。值false意味着没有列表使用字符串语法打印。

扫码关注腾讯云开发者

领取腾讯云代金券