文字列
文字列を数値に変換する
list_to_integer("123"). % 123 list_to_integer("-10"). % -10
n進数の文字列を数値に変換する
u は指定した基数で変換、# は文字列が表現している基数で変換します。
io_lib:fread("~16u", "100"). % {ok,[256],[]} io_lib:fread("~2u", "100abc"). % {ok,[4],[abc]} io_lib:fread("~36u", "100%%%"). % {ok,[1296],"%%%"} io_lib:fread("~#", "16#100"). % {ok,[256],[]} io_lib:fread("~#", "2#100abc"). % {ok,[4],[abc]} io_lib:fread("~#", "36#100%%%"). % {ok,[1296],"%%%"}
部分文字列を取得する
Latin-1 の文字なら問題ないのですが、日本語は完全にバイト単位です。 次の例は UTF-8 で実行した場合です。 なにかいいライブラリがあるといいのですが。
string:substr("abcd", 2). % "bcd" string:substr("abcd", 2, 1). % "b" string:substr("あいう", 1, 3). % [227,129,130] string:substr("あいう", 2, 1). % [129]
文字列の前方一致でパターンマッチングする
文字列の前方一致、プレフィックスでパターンマッチングが可能です。
1> fun("Hello" ++ _) -> ok; (_) -> ng end("Hello World!"). ok 2> fun("Hello" ++ _) -> ok; (_) -> ng end("Fello World!"). ng 3> fun([$H, $e, $l, $l, $o | _]) -> ok; (_) -> ng end("Hello World!"). ok"Hello" ++ _ は [$H, $e, $l, $l, $o | _] と同じということです。
なので、後方一致のパターンマッチングはできません。
文字列が文字列の中に含まれているか判定する
string:str/2 は頭から探し、string:rstr/2 は後から探します。 返り値は何文字目で見つかったかで、見つからない場合は 0 が返ります。 Erlang は 1 オリジンです。
1> string:str("Hello element", "el"). 2 2> string:rstr("Hello element", "el"). 7 3> string:rstr("Hello element", "xel"). 0
数値
数値を16進数で表記
16#ff. % 255 16#A1. % 161
数値をn進数で表記
2進数から36進数まで表記可能です。
2#101. % 5 3#211. % 22 36#zz. % 1295
数値を文字列に変換する
integer_to_list(123). % "123" integer_to_list(-123). % "-123" integer_to_list(16#10). % "16" integer_to_list(36#0z). % "35"
数値をn進数の文字列に変換する
io_lib:format("~.16b", [31]). % ["1f"] io_lib:format("~.36B~.2B", [71, 15]). % ["1Z","1111"]
リスト
リストを連結する
1> [1, 2] ++ [3, 4]. [1,2,3,4]
リストから要素を取り除く
1> [1, 2, 3, 1, 2, 3] -- [3, 2, 1, 2]. [1,3] 2> [X || X <- [1, 2, 3, 1, 2, 3], X /= 3]. [1,2,1,2] 3> [X || X <- [1, 2, 3, 1, 2, 3], X /= 3, X /= 1]. [2,2]
リストの要素1つずつに対して処理を行う
lists:foreach/2 はリストの要素1つずつに処理を行い ok を返します。 lists:map/2 はリストの要素1つずつに処理を行った結果をリストにして返します。
2> lists:foreach(fun(N) -> io:format("~p~n", [N]) end, [1,2,3]). 1 2 3 ok 3> lists:map(fun(N) -> N * N end, [1,2,3]). [1,4,9]
ドットリストを作る
リスト [1, 2] は [1|[2|[]]] の構文糖です。 最後が [] になっていますが、それを [] 以外にすると Lisp でいうところのドットリストになります。5> [1|[2|[]]]. [1,2] 6> [1|[2|3]]. [1,2|3] 7> tl(v(6)). [2|3] 8> tl(v(7)). 3
タプル
タプルの要素数を取得する
erlang:size/1 でタプルの要素数を取得できます。
size({a, b}). % 2 size({}). % 0 size({{}, {1, {1, 2}}}). % 2
タプルの要素を取得する
erlang:element/2 でタプルの要素を取得できます。 どの要素を取得するかは第1引数の1から始まるインデックスで指定します。 範囲外のインデックスを指定した場合は badarg ランタイムエラーが発生します。
element(1, {a, b, c}). % a element(2, {a, b, c}). % b
タプルに要素を追加する
4> erlang:append_element({a, b}, c). {a,b,c}
タプルに値を設定する
5> setelement(2, {a,b,c}, two).
{a,two,c}
ディクショナリ
dict モジュール
Erlang には組み込みではディクショナリやハッシュといったデータ型はありませんが、 STDLIB に dict というモジュールがあります。 Erlang は単一代入なので、値を入れるたびに変数をかえてやる必要があります。
D1 = dict:new(). % 作成 D2 = D1:store(key1, value1). % キーと値のペアを入れる D3 = D2:store({key2, key2}, [value2, value2]). % キーも値も任意の方を使える D4 = D3:append({key2, key2}, value3). % append で値を追加 D4:find(key1). % {ok,value1} D4:find(xxxx). % error が返る D4:fetch({key2, key2}). % [value2,value2,value3] D4:fetch({key2, xxxx}). % ランタイムエラー(function_clause)が発生 D4:to_list(). % リストに変換([{key1,value1},{{key2,key2},[value2,value2,value3]}])
バイナリ
文字列リテラルをバイナリで表記する
任意の文字列リテラルをバイナリとして表記できます。
1> <<"Hello Bin.">>. <<"Hello Bin.">> 2> <<"Hello あいう.">>. <<"Hello \343\201\202\343\201\204\343\201\206.">>. <<72,101,108,108,111,32,227,129,130,227,129,132,227,129,134,46>>
バイナリのパターンマッチング
リストやタプルと同様にバイナリでもパターンマッチングが可能です。
IPTuple = {192, 168, 1, 1}, {A,B,C,D} = IPTuple, <<IPDecimal:32>> = <<A:8,B:8,C:8,D:8>>.
関数
"fun モジュール名:関数名/引数の数" で関数を参照
"fun モジュール名:関数名/引数の数" で関数を参照できます。
1> fun erlang:tuple_to_list/1. #Fun<erlang.tuple_to_list.1> 2> fun erlang:tuple_to_list/1({a, b}). [a,b] 3> lists:map(fun erlang:tuple_to_list/1, [{a, b}, {a}, {}]). [[a,b],[a],[]]
日付と時刻
日付と時刻の取得する
日付と時刻の取得に関しては次のような BIF があります。 また、この他に calendar モジュールがあります。
1> date(). % ローカル日付 {年, 月, 日} {2007,5,28} 2> erlang:localtime(). % ローカル日時 {{年, 月, 日}, {時, 分, 秒}} {{2007,5,28},{6,46,28}} 3> erlang:localtime_to_universaltime(v(-1)). % ローカル日時を UCS に変換 {{2007,5,27},{21,46,28}} 4> now(). % {MegaSecs, Secs, MicroSecs} 1970/1/1からの経過 {1180,302477,518328} 5> time(). % ローカル時刻 {時, 分, 秒} {6,50,30} 6> erlang:universaltime(). % UTC 日時 {{年, 月, 日}, {時, 分, 秒}} {{2007,5,27},{21,51,6}} 7> erlang:universaltime_to_localtime(v(-1)). % UTC 日時をローカルに変換 {{2007,5,28},{6,51,6}}
曜日を取得する
calendar:day_of_the_week の返り値は 1 が月曜、7 が日曜日です。
2> calendar:day_of_the_week({2007,6,1}). 5 3> calendar:day_of_the_week(2007,6,3). 7
エラー(例外)処理
エラー処理を行う
Erlang にも try catch があります。 catch は 例外クラス:例外パターン となり、 例外クラスには次の3つがあります。
- error
- ランタイムエラー。erlang:error/1,2 または erlang:fault/1,2によって発生する。
- exit
- exit/1 が呼ばれた場合に発生する。
- throw
- throw/1 が呼ばれた場合に発生する。
8> try (1 + a) catch _:Reason -> io:format("1 + a is failed, reason: ~p~n", [Reason]) end. 1 + a is failed, reason: badarith ok 9> try (1 + a) catch error:Reason -> io:format("1 + a is failed, reason: ~p~n", [Reason]) end. 1 + a is failed, reason: badarith ok 10> try (1 + a) catch throw:Reason -> io:format("1 + a is failed, reason: ~p~n", [Reason]) end. =ERROR REPORT==== 30-Apr-2007::07:49:40 === Error in process <0.222.0> on node 'emacs@localhost' with exit value: {badarith,[{erl_eval,expr,3}]} ** exited: {badarith,[{erl_eval,expr,3}]} **
ファイル
ファイル全体を文字列で取得する。
ファイルの内容を一括でバイナリとして取得します。
get_file_contents(File) -> {ok, Binary} = file:read_file(File), Binary.
ファイルの内容を一括で文字列として取得します。
get_file_contents(File) -> {ok, Binary} = file:read_file(File), binary_to_list(Binary).
一行ずつ読み込む
print_with_line(File) -> {ok, IoDevice} = file:open(File, read), print_with_line(IoDevice, 1), file:close(IoDevice). print_with_line(IoDevice, LineNumber) -> case io:get_line(IoDevice, "") of eof -> ok; Line -> io:format("~b ~s", [LineNumber, Line]), print_with_line(IoDevice, LineNumber + 1) end.
ファイル情報を取得する
file:read_file_info(FileName) で file_info レコードを取得できます。
% file_info レコードをインクルード -include_lib("kernel/include/file.hrl"). % ファイル情報取得 {ok, FileInfo} = file:read_file_info(FileName). FileInfo#file_info.size. % ファイルサイズ FileInfo#file_info.type. % device | directory | regular | other FileInfo#file_info.access. % read | write | read_write | none FileInfo#file_info.atime. % 最終アクセス時間 FileInfo#file_info.mtime. % 最終更新時間 FileInfo#file_info.ctime. % 作成日時 FileInfo#file_info.mode. % 8#00400 のようなファイルパーミッション FileInfo#file_info.links. % このファイルへのリンク数。 FileInfo#file_info.major_device. % 0: Aドライブ, 1: Bドライブ FileInfo#file_info.minor_device. % Unix のキャラクタデバイスでのみ有効 FileInfo#file_info.inode. % inode 番号 FileInfo#file_info.uid. % 所有者のユーザID FileInfo#file_info.gid. % 所有者のグループID
リンクかどうか判定する
あるファイルがリンクかどうか判定するには file:read_link(Name) または file:read_link_info(Name) を使用します。
1> file:read_link("/cdrom"). {ok,"media/cdrom"} 2> file:read_link("/usr"). {error,einval} 3> file:read_link_info("/cdrom"). {ok,{file_info,11, symlink, read, {{2007,4,6},{5,38,13}}, {{2005,1,9},{21,20,27}}, {{2005,1,9},{21,20,27}}, 41471, 1, 2051, 0, 12, 0, 0}} 4> file:read_link_info("/usr"). {ok,{file_info,4096, directory, read, {{2007,4,5},{6,4,23}}, {{2005,11,9},{21,16,44}}, {{2005,11,9},{21,16,44}}, 16877, 14, 2051, 0, 12500993, 0, 0}}
パスがファイルかディレクトリか判定する
filelib:is_dir でディレクトリなら true。 filelib:is_file でディレクトリまたはファイルなら true。 filelib:is_regular でファイルなら true。
4> filelib:is_dir("/etc/yaws"). true 5> filelib:is_dir("/etc/yaws/yaws.conf"). false 6> filelib:is_file("/etc/yaws"). true 7> filelib:is_file("/etc/yaws/yaws.conf"). true 8> filelib:is_regular("/etc/yaws"). false 9> filelib:is_regular("/etc/yaws/yaws.conf"). true
ファイルをコピーする
1> file:list_dir("/tmp/bano"). {ok,["a.txt"]} 2> file:copy("/tmp/bano/a.txt", "/tmp/bano/b.txt"). {ok,1} 3> file:list_dir("/tmp/bano"). {ok,["a.txt","b.txt"]}
ファイルのリネーム
1> file:list_dir("/tmp/bino"). {ok,["a.txt"]} 2> file:rename("/tmp/bino/a.txt", "/tmp/bino/b.txt"). ok 3> file:list_dir("/tmp/bino"). {ok,["b.txt"]}
ファイルの改行コードを変更する
/tmp/a.txt の改行コード \n を、\r\n に変更して /tmp/aa.txt に出力します。
{ok, Bin} = file:read_file("/tmp/a.txt"), Str = binary_to_list(Bin), {ok, NewStr, RepCount} = regexp:gsub(Str, "\n", "\r\n"), file:write_file("/tmp/aa.txt", list_to_binary(NewStr)). %% 一行で書いてみると、こうなります。 %% 変数を使わない分、element でタプルから目的の値を取り出す必要があります。 file:write_file("/tmp/aa.txt", list_to_binary(element(2, regexp:gsub(binary_to_list(element(2, file:read_file("/tmp/a.txt"))), "\n", "\r\n")))).
現在のディレクトリ(current working directory)を取得・設定する
file:get_cwd/0, file:set_cwd/1 で取得・設定します。 Windows の場合は file:get_cwd("C:") のようにドライブごとのワーキングディレクトリを取得することもできます。
7> file:get_cwd(). {ok,"/home/ancient/public_html/erlang"} 8> file:set_cwd("/tmp"). ok 9> file:get_cwd(). {ok,"/tmp"}
絶対パスを取得する
filename:absname/1 は現在のディレクトリを基準に絶対パスを返します。 filename:absname/2 は基準となるディレクトリを第2引数に指定します。
1> file:get_cwd(). {ok,"/tmp"} 2> filename:absname("a.txt"). "/tmp/a.txt" 3> filename:absname("a.txt", "/var"). "/var/a.txt"
標準入出力
io モジュールでは、IoDevice 引数を省略、または standard_io を指定することで標準入出力を使用できます。
1> io:get_line("? "). "? "hello. "hello.\n" 2> io:get_line(standard_io, '? '). ? worlde. "worlde.\n" 3> io:fwrite("hello\n"). hello ok 4> io:fwrite(standard_io, "hello\n", []). hello ok
file モジュールでは erlang:group_leader/0 の返り値を IoDevice に指定することで標準入出力を使用できます。
全ての IO はグループリーダー経由で行われるため、標準入出力 = グループリーダーとなるのです。
#!/usr/bin/env escript main(_) -> loop(file:read(group_leader(), 1)). loop(eof) -> ok; loop({ok, Data}) -> file:write(group_leader(), Data), loop(file:read(group_leader(), 1)).
実行例。C-d で終了します。
~% cd ~/public_html/erlang/sample ~/public_html/erlang/sample% chmod +x standard_io.erl ~/public_html/erlang/sample% ./standard_io.erl Hello? Hello? あいう あいう
ディレクトリ内のファイルを(再帰的に)処理する
filelib:fold_files/5 という関数があります。 1番目の引数がディレクトリ。 2番目が処理対象か否かを判定するための正規表現。 3番目がサブディレクトリも再帰的に処理するか否か。 4番目が各ファイルに適用する関数。 5番目が初期値。 4番目の引数である関数は引数を2つとります。最初の引数は処理対象のファイル。次は最初は5番目に指定した引数で、それ以降はこの関数の返り値です。
サフィックスが erl のファイルをリストにして取得するには次のように書きます。
>1 filelib:fold_files("/home/ancient/letter/erlang", "\\.erl$", true, fun(X, Acc) -> [X|Acc] end, []). ["/home/ancient/letter/erlang/a/fib.erl", "/home/ancient/letter/erlang/chat/_darcs/pristine/chat.erl", "/home/ancient/letter/erlang/chat/_darcs/pristine/test_chat.erl", "/home/ancient/letter/erlang/chat/chat.erl", [...]|...]
ワイルドカード指定でファイルリストを取得する
filelib:wildcard/1 を使います。?(任意の1文字)、*(任意の文字列)、{Item,..}(いずれかに一致)をワイルドカードとして使用できます。
1> filelib:wildcard("/us?/*/lib{GL,Xa*}.so"). ["/usr/lib/libGL.so", "/usr/lib/libXau.so", "/usr/lib/libXaw.so", "/usr/lib/libXaw3d.so", "/usr/lib/libXaw7.so"]
Web
Web ページを取得する
まずはじめに inets:start() を呼び出しておく必要があります。
1> inets:start(). 2> {ok, {Status, Header, Body}} = http:request("http://www.google.co.jp").
HTML エスケープを行う
Yaws の API があります。
1> yaws_api:htmlize("<br>&"). "<br>&"
ネットワーク
サービス名からポート番号を取得する
inet:getservbyname/2 を使用します。サービス名はアトムで指定する必要があります。文字列で指定した場合はエラーになってしまいます。
25> inet:getservbyname(telnet, tcp). {ok,23} 26> inet:getservbyname(echo, udp). {ok,7}
FTP でファイルをアップロードする
make から手軽に呼べるので escript で作成します。 <div class="file-name">upload.es</div>
#!/usr/bin/env escript main(_) -> inets:start(), {ok, Pid} = inets:start(ftpc, [{host, "ftp.example.com"}]), ftp:user(Pid, "user", "password"), ftp:cd(Pid, "/public_html/erlang"), lists:foreach(fun(File) -> ftp:send(Pid, File) end, ["cookbook.html", "cookbook.css", "index.html"]), inets:stop(ftpc, Pid).
コンカレントプログラミング
プロセスを生成する
spawn でプロセスを生成します。返り値は pid() です。
spawn(Fun). spawn(Node, Fun). spawn(Module, Function, Args). spawn(Node, Module, Function, Args).
メッセージを送信する
Pid(プロセスID)または登録名に ! を使用してメッセージを送信します。
Pid ! Message. registered_name ! Message.
メッセージを受信する
receive でメッセージを受信します。
receive Message -> Message end.
タイムアウトを指定してメッセージを受信する
receive で after を使います。タイムアウトはミリ秒で指定します。 タイムアウトに0を指定した場合は、メッセージがなければ即時タイムアウトします。
receive Message -> Message after 1000 -> no_message end.
プロセスを登録する
プロセスを登録することにより、Pid を知らなくても、登録時の名前を指定し てそのプロセスにメッセージを送信することができます。
register(foo, spawn(fun() -> receive {Client, Message} -> Client ! Message end end)). foo ! {self(), "Hello foo."}. receive M -> M end.
プロセスをリンクする
あるプロセスでエラーが発生した場合、 リンクされている全てのプロセスが終了します。
リンクされていないプロセスは無関係に処理を継続します。
spawn_link でリンクしたプロセスを開始できます。
次の例では start_without_link はリンクをしていないため、 エラーを発生させても spawn したプロセスは処理を継続します。 start_with_link はリンクをしているため、 エラーを発生させると spawn_link したプロセスも停止します。
-module(sample.link). -compile(export_all). start_without_link() -> %% リンクしない spawn(?MODULE, loop, [10]), .timer:sleep(3000), .io:format("Let's stop!~n"), .erlang:error(stop). start_with_link() -> %% リンクする spawn_link(?MODULE, loop, [10]), .timer:sleep(3000), .io:format("Let's stop!~n"), .erlang:error(stop). loop(0) -> ok; loop(N) -> .io:format("hello~n"), .timer:sleep(1000), loop(N - 1).
プロセスをグルーピングする
pg2 モジュールでプロセスをグルーピング化できます。 プロセスが終了した場合、そのプロセスは自動的にグループから削除されます。
-module(process_group). -compile(export_all). start() -> pg2:create(group), % グループ作成 erlang:display(pg2:which_groups()), % グループリストを取得 Pid1 = spawn(fun p/0), pg2:join(group, Pid1), % グループ参加 Pid2 = spawn(fun p/0), pg2:join(group, Pid2), % グループに参加 Pid3 = spawn(fun p/0), pg2:join(group, Pid3), % グループに参加 erlang:display(pg2:get_members(group)), % グループメンバを取得 pg2:leave(group, Pid1), % グループから抜ける erlang:display(pg2:get_members(group)), % グループメンバを取得 Pid2 ! error, % エラー終了 timer:sleep(100), erlang:display(pg2:get_members(group)), % グループメンバを取得 Pid3 ! finish, % 普通に終了 timer:sleep(100), erlang:display(pg2:get_members(group)), % グループメンバを取得 pg2:delete(group), % グループ削除 erlang:display(pg2:which_groups()). % グループリストを取得 p() -> receive error -> 1 + a; finish -> ok end.
実行結果
6> process_group:start(). [group] [<0.65.0>,<0.64.0>,<0.63.0>] [<0.65.0>,<0.64.0>] =ERROR REPORT==== 5-May-2007::16:58:31 === Error in process <0.64.0> on node 'emacs@localhost' with exit value: {badarith,[{process_group,p,0}]} [<0.65.0>] [] [] true
ローカルノードのプロセスだけ返す pg2:get_local_members/1 や、ローカルノードのプロセスがあればそれ、なければリモートノードのプロセスを返す pg2:get_closest_pid/1 等もあります。
プロセスを監視する
Erlang では通信相手のプロセスが停止していても ! と receive は失敗しません(登録名で receive を行う場合で、 登録名のプロセスが存在しない場合はエラーになります)。 通信相手のプロセスが停止していないかどうか検知するには erlang:monitor/2 を使います。
1番目の引数は process 固定です。 2番目の引数はプロセスID、登録名、{登録名, ノード名} のいずれかです。 返り値はリファレンスです。 2番目の引数で指定したプロセスが停止していた場合、次のメッセージを受信します。
{'DOWN', monitorの返り値と同一のリファレンス, process, monitorの2番目の引数, プロセスダウン理由}
2番目の要素に monitor の返り値と同一のリファレンスが設定されるのど、 通常それを receive のパターンマッチングに使用します。
-module(monitor_sample). -compile(export_all). start() -> Pid = spawn(fun loop/0), Ref = erlang:monitor(process, Pid), % 監視を開始する RevFun = fun(Ref) -> receive {'DOWN',Ref,process,Pid,Reason} -> io:format("process is down, reason: ~p.~n", [Reason]); Any -> io:format("~p~n", [Any]) after 1000 -> io:format("timeout.~n") end end, Pid ! {self(), "Noko"}, RevFun(Ref), % ここでは Down していない Pid ! {self(), "Quit"}, % Pid が終了 RevFun(Ref), % ここでは Down している Pid ! {self(), "Hitsuji"}, RevFun(Ref), % ここではタイムアウト Pid ! {self(), "Hitsuji"}, Ref2 = erlang:monitor(process, Pid), % 再度監視を開始する RevFun(Ref2). % ここでも Down している loop() -> % サーバプロセスループ receive {_Pid, "Quit"} -> ok; {Pid, Any} -> Pid ! "Hi, " ++ Any, loop() end.
実行例
3> monitor_sample:start().
"Hi, Noko"
process is down, reason: normal.
timeout.
process is down, reason: noproc.
ok
ポートスキャン
私の環境では50プロセスぐらいがちょうどいいみたいです。 1プロセスだと約19秒。 2プロセスだと約13秒。 50プロセスだと約8秒。 1000プロセスだと約10秒。 10000プロセスだと約60秒。
-module(port_scan). -compile(export_all). m(ProcCount) -> timer:tc(?MODULE, main, [ProcCount]). main(ProcCount) -> lists:foreach(fun(_) -> spawn(?MODULE, client, [self()]) end, lists:seq(1, ProcCount)), init(). init() -> loop(lists:seq(1, 16#ffff)). loop([H|T]) -> receive {get, C} -> C ! H, loop(T) end; loop([]) -> io:format("finish!~n"). client(Server) -> Server ! {get, self()}, receive Port -> scan(Port), client(Server) after 1000 -> ok end. scan(Port) -> case gen_tcp:connect("localhost", Port, []) of {ok, Socket} -> gen_tcp:close(Socket), io:format("~b~n", [Port]); _ -> ok end.
分散プログラミング
分散ノードを開始する
分散ノードを開始するには -name か -sname オプションでノード名を指定して erl を実行します。 または、net_kernel:start を使用します。
ancient@vmubu:~% erl -name ubu@172.22.10.22 Erlang (BEAM) emulator version 5.5.4 [source] [async-threads:0] [hipe] [kernel-poll:false] Eshell V5.5.4 (abort with ^G) (ubu@172.22.10.22)1>
1> net_kernel:start(['master@172.22.10.15', longnames]).
{ok,<0.32.0>}
(master@172.22.10.15)2>
分散ノードを停止する
net_kernel:stop().
分散ノードへの ping
net_adm:ping で分散ノードの接続確認ができます。 接続できたときは pong、接続失敗時は pang が返ってきます。
net_adm:ping('ubu@172.22.10.22').
RPC
rpc:call で分散ノードの任意の関数を実行することができます。 引数は ノード, モジュール, 関数, 引数 です。
(master@172.22.10.15)78> rpc:call('ubu@172.22.10.22', file, list_dir, ["/usr"]). {ok,["src","share","sbin","local","lib","include","games","bin","X11R6"]}
リモートノードへのモジュールロード
リモートノードへローカルでロードしたモジュールをロードさせることができます。 ローカルでモジュールのオブジェクトコードを取得し、それを RPC でリモートノードにロードします。
(cho@Macintosh)37> {Module, Binary, Filename} = code:get_object_code(todo). {todo,<<70,79,82,49,0,0,16,144,66,69,65,77,65,116,111,109,0,0,3,102,0,0,0,82,4, 116,111,...>>, "/Users/ancient/letter/tex/erlang/sample/mnesia/todo.beam"} (cho@Macintosh)38> rpc:call('babi@localhost', code, load_binary, [Module, Filename, Binary]). {module,todo}
Yaws
動的なページを作成する
ファイル名の拡張子を yaws にします。 <erl>タグの間に out(Arg) 関数を定義します。 f とい関数を io_lib:format のかわり使用できます。
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>最初のページ</title> </head> <body> <h1>最初のページ</h1> <erl> out(Arg) -> {{Y, M, D}, {H, Mi, S}} = erlang:localtime(), {html, f("今は~b年~b月~b日~b時~b分~b秒です。", [Y, M, D, H, Mi, S])}. </erl> </body> </html>
セッションデータを使う
yaws_api:find_cookie_val/2 でクッキーセッションを取得し、 yaws_api:cookieval_to_opaque/1 でセッションに保存したデータを取得します。 セッションデータを書きかえるには yaws_api:replace_cookie_session/2 を使用します。
クッキーセッションの新規作成は yaws_api:new_cookie_session/1 で引数はセッションデータです。 クッキーセッションを新規作成した場合は、yaws_api:setcookie/3 でクッキーを設定する HTTP ヘッダの作成し、それをレスポンスの html と一緒にリストにして返す必要があります。
-define(myopaque, {aaa, bbb}). out(Arg) -> {Myopaque, NewHeader} = get_session_data(Arg), case NewHeader of undefined -> {html, "contents"}; _ -> [{html, "contents"}, NewHeader] end. %% 受信プロセスIDを取得します。 get_session_data(Arg) -> H = Arg#arg.headers, C = H#headers.cookie, case yaws_api:find_cookie_val("COOKIE_KEY", C) of [] -> % クッキーセッションなし Myopaque = #myopaque{aaa=init, bbb=init}, Cookie = yaws_api:new_cookie_session(Myopaque), NewHeader = yaws_api:setcookie("COOKIE_KEY", Cookie, "/"), {Myopaque, NewHeader}; Cookie -> % クッキーセッションあり case yaws_api:cookieval_to_opaque(Cookie) of {ok, Myopaque} -> {Myopaque, undefined} end end.
外界とのつながり
OS(シェル)コマンドを実行する
os:cmd/1 を使います。引数は文字列か、アトムです。
1> io:format("~s", [os:cmd(pwd)]). /home/abc/public_html/erlang ok 2> io:format("~s", [os:cmd("dir")]). ドライブ C のボリューム ラベルは IBM_PRELOAD です ボリューム シリアル番号は 68F0-7B18 です c:\home\abc\public_html\erlang のディレクトリ 2007/05/18 17:25 <DIR> . 2007/05/18 17:25 <DIR> .. 2007/05/18 17:34 33,564 cookbook.html 2007/05/18 17:17 33,281 cookbook.html~ 2007/05/18 17:08 441 erlang.css 2007/05/18 17:08 130 index.html 2007/05/18 17:08 34 Makefile 2007/05/18 17:08 <DIR> sample 2007/05/18 17:08 309 upload.es 2007/05/18 17:08 <DIR> _darcs 6 個のファイル 67,759 バイト 4 個のディレクトリ 4,458,496,000 バイトの空き領域 ok
環境変数を取得する
引数がない場合全ての環境変数を返します。
6> os:getenv("SHELL"). "zsh" 7> os:getenv(). ["windir=C:\\WINDOWS", "VS80COMNTOOLS=C:\\Program Files\\Microsoft Visual Studio 8\\Common7\\Tools\\", [...]|...]
環境変数を設定する
8> os:getenv("ENVVV"). false 9> os:putenv("ENVVV", "HELLO"). true 10> os:getenv("ENVVV"). "HELLO"
メタ
Beam ファイルのパスを取得する
code:which/1 にモジュール(atom)を渡せばそのモジュールの Beam ファイルのパスを取得できます。ただし、プレロードされているモジュールは preloaded、カバレジコンパイルされているモジュールは cover_compiled が返されます。存在しないモジュールの場合は non_existing が返されます。
1> code:which(io). "/usr/lib/erlang/lib/stdlib-1.14.4/ebin/io.beam" 2> code:which(erlang). preloaded 3> code:which(no_such_module). non_existing 4> c("/tmp/a", [{outdir, "/tmp/"}]). {ok,a} 5> code:which(a). "/tmp/a.beam" 6> cover:compile("/tmp/a.erl"). {ok,a} 7> code:which(a). cover_compiled
Beam ファイルからソースコードを取得(再構築)する
1> {_Module, Beam, _File} = code:get_object_code(base64), 1> {ok,{_,[{abstract_code,{_,AC}}]}} = beam_lib:chunks(Beam, [abstract_code]), 1> io:fwrite("~s~n", [erl_prettypr:format(erl_syntax:form_list(AC))]). -file("./base64.erl", 1). -module(base64). -export([encode/1, decode/1, mime_decode/1, encode_to_string/1, decode_to_string/1, mime_decode_to_string/1]). encode_to_string(Bin) when is_binary(Bin) -> encode_to_string(binary_to_list(Bin)); encode_to_string(List) when is_list(List) -> encode_l(List). (以下略)
文字列をコンパイルする
文字列で作ったモジュール(ソース)をコンパイルする方法です。
- erl_scan:tokens で1文ずつスキャン。
- erl_parse:parse_form で1文ずつパース。
- compile:forms でモジュール単位でコンパイル。
- code:load_binary でコンパイルしたものをロード。
改行コードは \n にしておかないとだめです。
-module(compile_string). -export([compile/1, test/0]). compile(SrcString) -> Forms = mk_forms(lists:flatten(SrcString)), %% コンパイルする {ok, M, B} = compile:forms(Forms), %% コンパイルしたものをロードする {module, M} = code:load_binary(M, atom_to_list(M), B), M. mk_forms(String) -> mk_forms(String, []). mk_forms([], Acc) -> lists:reverse(Acc); mk_forms(S, Acc) -> %% 1文(ピリオドまで)をスキャン {done, {ok, Tokens, _Line}, Rest} = erl_scan:tokens([], S, 0), %% パースする {ok, Parsed} = erl_parse:parse_form(Tokens), mk_forms(Rest, [Parsed|Acc]). %% テスト関数 test() -> %% ソース S = "-module(fib). -export([fib/1]). fib(1) -> 1; fib(2) -> 1; fib(N) -> fib(N-1) + fib(N-2). ", %% 文字列をコンパイル compile(S), %% コンパイルしたモジュールの関数を呼び出す fib:fib(10).
関数情報を取得する
erlang:fun_info/1 で関数の情報を取得できます。
1> erlang:fun_info(fun erlang:display/1). [{module,erlang},{name,display},{arity,1},{env,[]},{type,external}] 2> fun() -> X = 1, Y = fun(Y) -> X + Y end, erlang:fun_info(Y) end(). [{pid,<0.59.0>}, {module,erl_eval}, {new_index,2}, {new_uniq,<<146,128,248,136,99,188,48,7,216,172,210,56,139,244,145,225>>}, {index,6}, {uniq,72228031}, {name,'-expr/5-fun-2-'}, {arity,1}, {env,[[{'X',1}], none, {eval,#Fun<shell.21.66499203>}, [{clause,1,[{var,1,'Y'}],[],[{op,1,'+',{var,1,'X'},{var,1,'Y'}}]}]]}, {type,local}]
取得できる情報は次のとおりです。
- pid
- 関数を作成したプロセスID。ローカル関数のみ。
- module
- モジュール名。
- new_index
- モジュールの関数テーブルのインデックス。ローカル関数のみ。
- new_uniq
- 関数のユニーク値(バイナリ)。ローカル関数のみ。
- index
- モジュールの関数テーブルのインデックス。ローカル関数のみ。
- uniq
- 関数のユニーク値(整数)。ローカル関数のみ。
- name
- 関数名。
- arity
- 引数の個数
- env
- 関数の環境。ローカル関数のみ。
- type
- ローカル関数の場合は local、外部関数の場合は external。
DB
ODBC 接続
多くの場合オプションに {scrollable_cursors, off} を指定しないとエラーになります。 connect する都度 odbcserver.exe が起動され disconnect しないとそのプロセスが残ります。
{ok, Ora} = odbc:connect("DSN=bino;UID=hr;PWD=password;", [{scrollable_cursors, off}]), {selected, _, [{Aiu}]} = odbc:sql_query(Ora, "select 'あいう' from dual"), io:format("~s~n", [Aiu]), odbc:disconnect(Ora).
色々
関数の実行時間を計測する
シェルの time コマンドのように実行時間を計測するには timer:tc を使用します。 返り値は {実行時間(マイクロ秒), 関数の返り値} です。
5> timer:tc(fib, fib, [1]). {3,1} 6> timer:tc(fib, fib, [40]). {8957808,102334155}