erlang程序设计 第六章 编译和运行

莘绍元
2023-12-01
编译和运行
译者: gashero


目录

? 1 启动和停止Erlang shell
? 2 修改开发环境
? 2.1 设置装载代码的搜索路径
? 2.2 在系统启动时执行一系列命令
? 3 运行程序的其他方式
? 3.1 在Erlang shell中编译和运行
? 3.2 在命令行编译和运行
? 3.3 以Escript运行
? 3.4 程序的命令行参数
? 4 通过makefile自动编译
? 4.1 一个makefile模板
? 4.2 实际修改makefile模板
? 5 Erlang shell中的命令编辑
? 6 解决错误
? 7 当确实出错时
? 7.1 未定义(丢失)的代码
? 7.2 我的makefile无法make
? 7.3 shell没有响应了
? 8 获取帮助
? 9 定制环境
? 10 crash dump
上一章并没有详细的说明如何编译和运行程序,而只是使用了Erlang shell。这对小例子是很好的,但是会让你的程序更复杂,你将会需要一个自动处理过程来简化它。我们一般使用Makefile。

事实上有三种不同的方式可以运行程序。本章将会结合特定场合来讲解三种运行方式。

有时候也会遇到问题:Makefile失败、环境变量错误或者搜索路径不对。我们在遇到这些问题时也会帮你处理这些问题(issue)。


1 启动和停止Erlang shell
在Unix系统(包括Mac OS X),可以从命令行启动Erlang shell:

$ erl
Erlang (BEAM) emulator version 5.5.1 [source] [async-threads:0] [hipe]

Eshell V5.5.1 (abort with ^G)
1>而在Windows系统,则可以点击erl的图标。

最简单的退出方法是按下 Ctrl+C (对Windows系统则是 Ctrl+Break ),随后按下 A 。如下:

BREAK: (a)bort (c)ontinue (p)roc info (i)nfo (l)oaded
(v)ersion (k)ill (D)b-tables (d)istribution
a
$另外,你也可以对 erlang:halt() 求值以退出。

erlang:halt() 是一个BIF,可以立即停止系统,也是我最常用的方法。不过这种方法也有个问题。如果正在运行一个大型数据库应用,那么系统在下次启动时就会进入错误回复过程,所以你应该以可控的方式关闭系统。

可控的关闭系统就是如果shell可用时就输入如下:

1> q().
ok
$这会将所有打开的文件写入磁盘,停止数据库(如果在运行),并且按照顺序关闭所有OTP应用。 q() 其实只是shell上 init:stop() 的别名。

如果这些方法都不工作,可以查阅6.6节。


2 修改开发环境
当你启动程序时,一般都是把所有模块和文件都放在同一个目录当中,并且在这个目录启动Erlang。如果这样做当然没问题。不过如果你的应用变得更加复杂时,你可能需要把它们分成便于管理的块,把代码放入不同的目录。而且你从其他项目导入代码时,扩展代码也有其自己的目录结构。


2.1 设置装载代码的搜索路径
Erlang运行时系统包含了代码自动重新载入的功能。想要让这个功能正常工作,你必须设置一些搜索路径以便找到正确版本的代码。

代码装载功能是内置在Erlang中的,我们将在E.4节详细讨论。代码的装载仅在需要时才执行。

当系统尝试调用一个模块中的函数,而这个模块还没有装载时,就会发生异常,系统就会尝试这个模块的代码文件。如果需要的模块叫做 myMissingModule ,那么代码装载器将会在当前目录的所有目录中搜索一个叫做 myMissingModule.beam 的文件。寻找到的第一个匹配会停止搜索,然后就会把这个文件中的对象代码载入系统。

你可以在Erlang shell中看看当前装载路径,使用如下命令 code:get_path() 。如下是例子:

code:get_path()
[".",
"/usr/local/lib/erlang/lib/kernel-2.11.3/ebin",
"/usr/local/lib/erlang/lib/stdlib-1.14.3/ebin",
"/usr/local/lib/erlang/lib/xmerl-1.1/ebin",
"/usr/local/lib/erlang/lib/webtool-0.8.3/ebin",
"/usr/local/lib/erlang/lib/typer-0.1.0/ebin",
"/usr/local/lib/erlang/lib/tv-2.1.3/ebin",
"/usr/local/lib/erlang/lib/tools-2.5.3/ebin",
"/usr/local/lib/erlang/lib/toolbar-1.3/ebin",
"/usr/local/lib/erlang/lib/syntax_tools-1.5.2/ebin",
...]管理装载路径两个最常用的函数如下:

@spec code:add_patha(Dir) => true | {error,bad_directory}

添加新目录Dir到装载路径的开头

@spec code:add_pathz(Dir) => true | {error,bad_directory}

添加新目录Dir到装载路径的末尾

通常并不需要自己来关注。只要注意两个函数产生的结果是不同的。如果你怀疑装载了错误的模块,你可以调用 code:all_loaded() (返回所有已经装载的模块列表),或者 code:clash() 来帮助你研究错误所在。

code 模块中还有其他一些程序可以用于管理路径,但是你可能根本没机会用到它们,除非你正在做一些系统编程。

按照惯例,经常把这些命令放入一个叫做 .erlang 的文件到你的HOME目录。另外,也可以在启动Erlang时的命令行中指定:

> erl -pa Dir1 -pa Dir2 ... -pz DirK1 -pz DirK2其中 -pa 标志将目录加到搜索路径的开头,而 -pz 则把目录加到搜索路径的末尾。


2.2 在系统启动时执行一系列命令
我们刚才把载入路径放到了HOME目录的 .erlang 文件中。事实上,你可以把任意的Erlang代码放入这个文件,在你启动Erlang时,它就会读取和求值这个文件中的所有命令。

假设我的 .erlang 文件如下:

io:format("Running Erlang~n").
code.add_patha(".")
code.add_pathz("/home/joe/2005/erl/lib/supported").
code.add_pathz("/home/joe/bin").当我启动系统时,我就可以看到如下输出:

$ erl
Erlang (BEAM) emulator version 5.5.1 [source] [async-threads:0] [hipe]

Running Erlang
Eshell V5.5.1 (abort with ^G)
1>如果当前目录也有个 .erlang 文件,则会在优先于HOME目录的。这样就可以在不同启动位置定制Erlang的行为,这对特定应用非常有用。在这种情况下,推荐加入一些打印语句到启动文件;否则你可能忘记了本地的启动文件,这可能会很混乱。

某些系统很难确定HOME目录的位置,或者根本就不是你以为的位置。可以看看Erlang认为的HOME目录的位置,通过如下的:

1> init:get_argument(home).
{ok,[["/home/joe"]]}通过这里,我们可以推断出Erlang认为的HOME目录就是 /home/joe 。


3 运行程序的其他方式
Erlang程序存储在模块中。一旦写好了程序,运行前需要先编译。不过,也可以以脚本的方式直接运行程序,叫做 escript 。

下一节会展示如何用多种方式编译和运行一对程序。这两个程序很不同,启动和停止的方式也不同。

第一个程序 hello.erl 只是打印 “Hello world” ,这不是启动和停止系统的可靠方式,而且他也不需要存取任何命令行参数。与之对比的第二个程序则需要存取命令行参数。

这里是一个简单的程序。它输出 “Hello world” 然后输出换行。 “~n” 在Erlang的io和io_lib模块中解释为换行。

-module(hello).
-export([start/0]).

start() ->
io:format("Hello world~n").让我们以3种方式编译和运行它。


3.1 在Erlang shell中编译和运行
$ erl
...
1> c(hello).
{ok,hello}
2> hello:start().
Hello world
ok
3.2 在命令行编译和运行
$ erlc hello.erl
$ erl -noshell -s hello start -s init stop
Hello World
$
Note

快速脚本:

有时我们需要在命令行执行一个函数。可以使用 -eval 参数来快速方便的实现。这里是例子:

erl -eval 'io:format("Memory: ~p~n", [erlang:memory(total)]).'
-noshell -s init stopWindows用户:想要让如上工作,你需要把Erlang可执行文件目录加入到环境变量中。否则就要以引号中的全路径来启动,如下:

"C:Program Fileserl5.5.3binerlc.exe" hello.erl第一行 erlc hello.erl 会编译文件 hello.erl ,生成叫做 hello.beam 的代码文件。第一个命令拥有三个选项:

-noshell :启动Erlang而没有交互式shell,此时不会得到Erlang的启动信息来提示欢迎

-s hello start :运行函数 hello:start() ,注意使用 -s Mod … 选项时,相关的模块Mod必须已经编译完成了。

-s init stop :当我们调用 apply(hello,start,[]) 结束时,系统就会对函数 init:stop() 求值。

命令 erl -noshell … 可以放入shell脚本,所以我们可以写一个shell脚本负责设置路径(使用-pa参数)和启动程序。

在我们的例子中,我们使用了两个 -s 选项,我们可以在一行拥有多个函数。每个 -s 都会使用 apply 语句来求职,而且,在一个执行完成后才会执行下一个。

如下是启动hello.erl的例子:

#! /bin/sh
erl -noshell -pa /home/joe/2006/book/JAERANG/Book/code
-s hello start -s init stop
Note

这个脚本需要使用绝对路径指向包含 hello.beam 。不过这个脚本是运行在我的电脑上,你使用时应该修改。

运行这个shell脚本,我们需要改变文件属性(chmod),然后运行脚本:

$ chmod u+x hello.sh
$ ./hello.sh
Hello world
$
Note

在Windows上, #! 不会起效。在Windows环境下,可以创建.bat批处理文件,并且使用全路径的Erlang来启动(假如尚未设置PATH环境变量)。

一个典型的Windows批处理文件如下:

"C:Program Fileserl5.5.3binerl.exe" -noshell -s hello start -s init stop
3.3 以Escript运行
使用escript,你可以直接运行你的程序,而不需要先编译。


Warning

escript包含在Erlang R11B-4或以后的版本,如果你的Erlang实在太老了,你需要升级到最新版本。

想要以escript方式运行hello,我们需要创建如下文件:

#! /usr/bin/env escript

main(_) ->
io:format("Hello worldn").
Note

开发阶段的导出函数

如果你正在开发代码,可能会非常痛苦于需要不断在导出函数列表增删函数。

一个声明 -compile(export_all) ,告知编译器导出所有函数。使用这个可以让你的开发工作简化一点。

当你完成开发工作时,你需要抓实掉这一行,并添加适当的导出列表。首先,重要的函数需要导出,而其他的都要隐藏起来。隐藏方式可以按照个人喜好,提供接口也是一样的效果。第二,编译器会对需要导出的函数生成更好的代码。

在Unix系统中,我们可以立即按照如下方式运行:

$ chmod u+x hello
$ ./hello
Hello world
$
Note

这里的文件模式在Unix系统中表示可执行,这个通过chmod修改文件属性的步骤只需要执行一次,而不是每次运行程序时。

在Windows系统中可以如下方式运行:

C:> escript hello
Hello world
C:>
Note

在以escript方式运行时,执行速度会明显的比编译方式慢上一个数量级。


3.4 程序的命令行参数
“Hello world”没有参数。让我们重新来做一个计算阶乘的程序,可以接受一个参数。

首先是代码:

-module(fac).
-export([fac/1]).

fac(0) -> 1;
fac(N) -> N*fac(N-1).我们可以编译 fac.erl 并且在Erlang中运行:

$ erl
1> c(fac).
{ok,fac}
2> fac:fac(25).
15511210043330985984000000如果我们想要在命令行运行这个程序,我们需要修改一下他的命令行参数:

-module(fac1).
-export([main/1]).

main([A]) ->
I=list_to_integer(atom_to_list(A)).
F=fac(I),
io:format("factorial ~w= ~w~n",[I,F]),
init:stop().

fac(0) -> 1;
fac(N) -> N*fac(N-1).让我们编译和运行:

$ erlc fac1.erl
$ erl -noshell -s fac1 main 25
factorial 25 = 15511210043330985984000000
Note

事实上这里的main()函数并没有特殊意义,你可以把它改成任何名字。重要的一点是命令行参数中要使用函数名。

最终,我们可以把它直接作为escript来运行:

#! /usr/bin/env escript

main([A]) ->
I=list_to_integer(A),
F=fac(I),
io:format("factorial ~w = ~w~n",[I,F]).

fac(0) -> 1;
fac(N) -> N*fac(N-1).运行时无需编译,可以直接运行:

$ ./factorial 25
factorial 25 = 15511210043330985984000000
$
4 通过makefile自动编译
当编写一个大型程序时,我希望只在需要的时候自动编译。这里有两个原因。首先,节省那些一遍遍相同的打字。第二,因为经常需要同时进行多个项目,当再次回到这个项目时,我已经忘了该如何编译这些代码。而 make 可以帮助我们解决这个问题。

make 是一个自动工具,用以编译和发布Erlang代码。大多数我的makefile都非常简单,并且我有个模板可以解决大多数问题。

我不想在这里解释makefile的意义。而我会展示如何将makefile用于Erlang程序。推荐看看本书附带的makefile,然后你就会明白他们并且构建你自己的makefile了。


4.1 一个makefile模板
如下的模板是很常用的,很多时候可以基于他们来做实际的事情:

#让这一行就这么放着
.SUFFIXES: .erl .beam .yrl

.erl.beam:
erlc -W $<

.yrl.erl:
erlc -W $<

ERL=erl -boot start_clean

#如下是需要编译的模块列表
#如果一行写不下可以用反斜线 来折行

#修改如下行
MODS=module1 module2
module3 ....

#makefile中的第一个目标是缺省目标
#也就是键入make时默认的目标
all: compile

compile: ${MODS:%=%.beam} subdirs

#指定编译需求,并且添加到这里
special1.beam: special1.erl
${ERL} -Dflag1 -W0 special1.erl

#从makefile运行一个应用
application1: compile
${ERL} -pa Dir1 -s application1 start Arg1 Arg2

#在子目录编译子目录的目标
subdirs:
cd dir1; make
cd dir2; make
...

#清除所有编译后的文件
clean:
rm -rf *.beam erl_crash.dump
cd dir1; make clean
cd dir2; make cleanmakefile开始于一些编译Erlang模块的规则,包括扩展名为 .yrl 的文件(是Erlang词法解析器的定义文件)(Erlang的词法解析器生成器为yecc,是yacc的Erlang版本,参考 http://www.erlang.org/contrib/parser_tutorial-1.0.tgz )。

重要的部分开始于这一行:

MODS=module1 module2这是我们需要编译的Erlang模块的列表。

任何在MODS列表中的模块都会被Erlang命令 erlc Mod.erl 来编译。一些模块可能会需要特殊的待遇,比如模板中的special1。所以会有单独的规则用来处理这些。

makefile中有一些目标。一个目标是一个字符串,随后是冒号”:”。在makefile模板中,all,compile和special1.beam都是目标。想要运行makefile,你可以在shell中执行:

$ make [Target]参数Target是可选的。如果Target忽略掉了,则假设被第一个目标。在前面的例子中,目标all就是缺省的。

如果我希望构建软件并运行,我们就会使用 make application1 。如果我希望这个作为缺省行为,也就是在我每次键入 make 时都会执行,我就可以把application1目标作为第一个目标。

目标clean会删除所有编译过的Erlang目标代码和 erl_crash.dump 。crashdump包含了帮助调试应用程序的信息。查看6.10了解更多。


4.2 实际修改makefile模板
我并不热衷于让自己的程序变得混乱,所以我一般以一个不包含无用行的模板开始。所以得到的makefile会更短,而且也更易于阅读。另外,你也可以使用到处都是变量的makefile,以方便定制。

一旦我遍历整个流程,就会得到一个很简单的makefile,有如如下:

.SUFFIXES: .erl .beam

.erl.beam:
erlc -W $<

ERL = erl -boot start_clean

MODS=module1 module2 module3

all: compile
${ERL} -pa '/home/joe/.../this/dir' -s module1 start

compile: ${MODS:%=%.beam}

clean:
rm -rf *.beam erl_crash.dump
5 Erlang shell中的命令编辑
Erlang shell包含了内置的行编辑器。它可以执行emacs的一部分行编辑命令,前一行可以以多种方式来调用。可用命令如下,注意 “^Key” 是指按下 “Ctrl+Key” 。

命令 描述
^A 开始一行
^E 最后一行
^F或右箭头 向前一个字符
^B或左箭头 向后一个字符
^P或上箭头 前一行
^N或下箭头 下一行
^T 调换最后两个字符的顺序
Tab 尝试补全模块名或函数名


6 解决错误
Erlang有时会发生一些问题而退出。下面是可能的错误原因:

1.shell没有响应
2.Ctrl+C处理器被禁用了
3.Erlang以 -detached 标志启动,这时你甚至感觉不到他在运行
4.Erlang以 -heart Cmd 标志启动。这会让OS监控器进程看管Erlang的OS进程。如果Erlang的OS进程死掉,那么就会求值 Cmd 。一般来说 Cmd 只是用于简单的重启Erlang系统。这个是用于生产可容错系统的一个重要技巧,用于结点-如果erlang自己死掉了(基本不可能发生),就会自己重启。这个技巧对Unix类操作系统使用 ps 命令来监控,对Windows使用任务管理器。进行心跳信息检测并且尝试kill掉erlang进程。
5.有且确实很无奈的错误,留下一个erlang僵尸进程。

7 当确实出错时
本节列出了一些常见错误和解决方案。


7.1 未定义(丢失)的代码
如果你尝试调用一个模块,而代码载入器却无法找到时(比如搜索路径出错),你将会遇到 undef 错误信息,如下是例子:

1> glurk:oops(1,23).
** exited: {undef,[{glurk,oops,[1,23]},
{erl_eval,do_apply,5},
{shell,exprs,6},
{shell,eval_loop,3}]}**事实上,这里没有一个叫做glurk,但是这里没有关联问题,你只需要关心错误信息就可以了。错误信息告诉我们系统尝试调用glurk模块的函数oops,参数1是23,这四个事物中的一个出了问题。

1.有可能不存在模块glurk,找不到或者根本不存在。可能是拼写错误。
2.存在模块glurk,但是还没有编译。系统尝试寻找文件 glurk.beam ,但是没有找到。
3.有模块glurk,但是包含 glurk.beam 的目录并没有在模块搜索路径中。想要修复这个问题,你必须改变搜索路径,一会再讲。
4.在载入路径中有多个不同版本的 “glurk” ,而我们选择了错误的。这是个罕见的错误,但是有可能会发生。如果你担心发生,你可以运行 code:clash() 函数,这将会报告代码搜索路径中的重复模块。

Note

有人看见我的分号了么?

如果你忘记了两个子句之间的分号,或者是放了个句点,那你可就麻烦了。

如果你定义了一个函数 foo/2 在模块bar的1234行,并且在该写分号的地方用了句点,那么编译器会提示:

bar.erl:1234 function foo/2 already defined.不要这么做,确保你的子句总是以分号分开。


7.2 我的makefile无法make
你怎么能让makefile出错?当然,尽管这本书不是讲makefile的,但是我还是会给出一些常见错误提示。如下是两个常见的错误:

1.makefile中的空白:makefile是很严格而挑剔的。虽然你看不到他们,但是缩进行必须以一个tab字符开始。如果这里有其他空白,那么make就会出错,而你会看到一些错误。(当然折行,也就是上一行末尾有 “\” 字符的不算)

2.丢失的erlang文件。如果在MODS变量中定义的模块丢失了,你会得到错误信息。举例,假设MODS中包含一个模块叫做glurk,但是却没有glurk.erl文件。这种情况下,make会出错,并给出如下信息:

$ make
make: *** No rule to make target 'glurk.beam',
needed by 'compile'. Stop.另外模块名拼写错误也会导致这个问题。


7.3 shell没有响应了
如果shell对命令不做出响应了,有可能发生一系列的问题。shell进程可能会crash了,或者你可能放任一个命令永远无法终止。你可能忘了输入关闭的括号,或者忘记了 点号回车 。

无论哪种问题,你都可以通过按下 “Ctrl+G” 啦关闭当前shell,并且学着如下的例子:

1> receive foo -> true end.
^G
User switch command
--> h
c [nn] - connect to job
i [nn] - interrupt job
k [nn] - kill job
j - list all jobs
s - start local shell
r [node] - start remote shell
q - quit erlang
? | h - this message
--> j
1* {shell,start,[init]}
--> s
--> j
1 {shell,start,[init]}
2* {shell,start,[]}
--> c 2
Eshell V5.5.1 (abort with ^G)
1> init:stop().
ok
2> $1.第一行告诉shell接收foo消息,当时不会有人发送消息到shell,shell会进入一个无限的等待,所以需要按下 “Ctrl+G” 。
2.“–> h” ,系统进入了 “shell JCL” 模式(Job Control Language),这里我可以不知道任何命令所以键入 h 来获取帮助。
3.“–> j” ,列出所有任务。任务的号码1标记为星号,表示为默认的shell。所有命令选项参数 [nn] 使用缺省的shell除非指定了参数。
4.“–> s” ,键入s以启动一个新的shell,而在其后可以看到已经把2号标记为默认的shell了
5.“–> c 2″ ,让我连接到新启动的2号shell,然后停止了系统。
有如你所见,你可以拥有多个shell,并且通过按下Ctrl+G以后来切换。你可以通过命令r在远程启动一个shell方便调试。


8 获取帮助
在Unix类系统中,如下:

$ erl -man erl
NAME
erl - The Erlang Emulator

DESCRIPTION
...你也可以通过这种方式获取模块的帮助文档:

$ erl -man lists
MODULE
lists - List Processing Functions
...
Note

在Unix系统,man手册页并不是默认安装的,如果命令 erl -man … 没有工作,你需要自己安装man手册页。所有的man手册页都是单独压缩存档的,可以到 http://www.erlang.org/download.html 下载。man手册页可以通过root解压并安装到Erlang的安装目录,通常为 /usr/local/lib/erlang 。

也可以下载HTML文档。在Windows下默认会安装HTML文档,可以通过开始菜单访问到。


9 定制环境
Erlang shell拥有一系列的内置命令。你可以通过 help() 来看到这些命令:

1> help().
** shell internal commands **
b() -- display all variables bindings
e(N) -- repeat the expression in query <N>
f() -- forget all variable bindings
f(X) -- forget the binding of variable X
h() -- history
...所有这些命令都是定义在模块 shell_default 中。

如果你想定义自己的命令,只需要创建一个叫做 user_default 的模块,例如:

-module(user_default).
-compile(export_all).

hello() ->
"Hello joe how are you?".

away(Time) ->
io:format("Joe is away and will be back in ~w minutes~n",[Time]).一旦你编译了它并且放在了你的载入路径,那么你可以调用 user_default 中的任何函数,而不用给出模块名:

1> hello().
"Hello joe how are you?"
2> away(10).
Joe is away and will be back in 10 minutes
ok
10 crash dump
当erlang crash时,他会生成一个文件叫做 erl_crash.dump 。文件内容包含出错的地方的相关信息。想要分析crash dump这里有个基于web的分析器。启动这个分析器,你可以给出如下命令:

1> webtool:start().
WebTool is available at http://localhost:8888/
Or http://127.0.0.1:8888/
{ok,<0.34.0>}然后让浏览器指向 http://localhost:8888/ 。你就可以看到刚才发生的问题了。

现在我们看到的只是问题回溯的原料,具体还需要你仔细的分析,并发的程序不太好调试。从这时开始,你将离开熟悉的环境,不过这时也正是历险的开始。

This entry was posted on Monday, April 14th, 2008 at 9:28 and is filed under 不着边的翻译, Erlang. You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response, or trackback from your own site.

2 Responses to “Programming Erlang 第6章 编译和运行(完整)”
Leo Says:

April 25th, 2008 at 16:37
我有这样一段程序,如下:
-module(tut7).
-export([format_temps/1]).

format_temps(List_of_cities) ->
Converted_List = convert_list_to_c(List_of_cities),
print_temp(Converted_List).

convert_list_to_c([{Name,{f,F}}|Rest]) ->
Convert_City = {Name,{c,(F - 32) * 5 / 9}},
[Convert_City|convert_list_to_c(Rest)];

convert_list_to_c([]) ->
[].

print_temp([{Name,{c,Temp}}|Rest]) ->
io:format(”~-15w ~w c~n”,[Name,Temp]),
print_temp(Rest);
print_temp([]) ->
ok.
.erl文件放在D盘,我是windows系统。
我想请教一下该如何编译运行这个程序,谢谢。

gashero Says:

April 25th, 2008 at 17:56
d:
d:\>erlc a.erl
d:\>dir
a.erl
a.beam
… …
 类似资料: