Programming Erlang 第6章笔记 编译和运行
编译和运行
译者: | gashero |
---|
上一章并没有详细的说明如何编译和运行程序,而只是使用了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 stop
Windows用户:想要让如上工作,你需要把Erlang可执行文件目录加入到环境变量中。否则就要以引号中的全路径来启动,如下:
"C:\Program Files\erl5.5.3\bin\erlc.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 Files\erl5.5.3\bin\erl.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 world\n").
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 程序的命令行参数
@page 125