2014年4月7日

erlang - file io

以下內容涵蓋一些常用的 file io 函數,內容包含了(1) 函式庫的組織 (2) 讀取檔案 (3) 寫入檔案 (4) 目錄操作 (5) 取得檔案資訊 (6) 字元編碼

函式庫的組織

檔案處理的函數分散在四個模組

  1. file
    包含開啟、關閉、讀取、寫入檔案、列出目錄的函數
  2. filename
    可以用 platform independent 的方式處理檔名,所以相同的程式碼可在不同作業系統上運作
  3. filelib
    是 file 的擴充,可用來列出檔案,檢查檔案型別。這些函數大多是利用 file 裡面的函數寫出來的。
  4. io
    用來處理開啟的檔案、剖析資料、寫入格式化的資料

file 模組檔案操作的函數

函數 說明
change_group 改變檔案的群組
change_owner 改變檔案的owner
change_time 改變修改時間或上次取用的時間
close 關閉檔案
consult 從檔案讀取 erlang term
copy 複製檔案內容
del_dir 刪除目錄
delete 刪除檔案
eval 估算檔案內的 erlang expressions
format_error 根據錯誤原因,傳回其說明字串
get_cwd 目前的工作目錄
list_dir 取得目錄內的檔案清單
make_dir 建立新目錄
make_link 建立 hard link
make_symlink 建立 symbolic link
open 開啟檔案
position 設定檔案內的位置
pread 讀取檔案的特定位置
pwrite 寫入檔案的特定位置
read 讀取檔案
read_file 讀取整個檔案
read_file_info 取得檔案資訊
read_link 查詢 link 指向何處
read_link_info 取得 link 或檔案的資訊
rename 改檔名
script 估算並傳回檔案內的 erlang expressions 的值
set_cwd 設定工作目錄
sync 以檔案的實體媒介,同步化一個檔案的記憶體內狀態
truncate 截斷一個檔案
write 寫入檔案
write_file 寫入整個檔案
write_file_info 改變檔案的資訊

讀取檔案

以下以 data1.dat 檔案為例,採用不同的方式讀取檔案

data1.dat 內容

{person, "john", "chen",
    [{occupation, programmer}, {favorite, coding}]}.
{cat, {name, "money"}, {owner, "john"}}.

file:consult

讀取檔案內所有 erlang terms

1> file:consult("data1.dat").
{ok,[{person,"john","chen",
             [{occupation,programmer},{favorite,coding}]},
     {cat,{name,"money"},{owner,"john"}}]}

file:open, io:read

先用 file:open 開啟檔案,然後用 io:read 一次讀取一個 erlang term,最後用 file:close 關閉檔案。

4> {ok, S} = file:open("data1.dat", read).
{ok,<0.38.0>}
5> io:read(S, '').
{ok,{person,"john","chen",
            [{occupation,programmer},{favorite,coding}]}}
6> io:read(S, '').
{ok,{cat,{name,"money"},{owner,"john"}}}
7> io:read(S, '').
eof
8> file:close(S).
ok

@spec file:open(File, read) -> {ok, IoDevice} | {error, Why}

@spec io:read(IoDevice, Prompt) -> {ok, Term} | {error, Why} | eof
只有在 io:read 讀取 stdin 的時候,才會用到 Prompt(提示符號)

@spec file:close(IoDevice) -> ok | {error, Why}

使用這些函數,就可以實做出 file:consult

consult(File) ->
    case file:open(File, read) of
        {ok, S} ->
            Val = consult1(S),
            file:close(S),
            {ok, Val};
        {error, Why} ->
            {error, Why}
    end.

consult1(S) ->
    case io:read(S, '') of
        {ok, Term} -> [Term|consult1(S)];
        eof        -> [];
        Error      -> Error
    end.

我們可以用 code:which 找到任何「已載入模組」的目的碼

9> code:which(file).
"c:/PROGRA~1/ERL510~1.4/lib/kernel-2.16.4/ebin/file.beam"

而 file.beam 的原始程式就在 C:\Program Files\erl5.10.4\lib\kernel-2.16.4\src 目錄裡面。

io:get_line

以 io:get_line 一次讀取一行資料

1> {ok, S} = file:open("data1.dat", read).
{ok,<0.33.0>}
2> io:get_line(S, '').
"{person, \"john\", \"chen\",\n"
3> io:get_line(S, '').
"\t[{occupation, programmer}, {favorite, coding}]}.\n"
4> io:get_line(S, '').
"{cat, {name, \"money\"}, {owner, \"john\"}}."
5> io:get_line(S, '').
eof
6> io:get_line(S, '').
eof
7> file:close(S).
ok

file:read_file(File)

讀取整個檔案,作為一個 binary 資料

8> {ok, Bin} = file:read_file("data1.dat").
{ok,<<"{person, \"john\", \"chen\",\r\n\t[{occupation, programmer}, {favorite, coding}]}.\r\n{cat, {name, \"money\"}, {owner, "...>>}

file:pread

以 raw 模式開啟檔案,然後用 file:pread 讀取任意位置的檔案資料,檔案內第一個位元組,位置為 1

1> {ok, S} = file:open("data1.dat", [read, binary, raw]).
{ok,{file_descriptor,prim_file,{#Port<0.505>,564}}}
2> file:pread(S, 22, 46).
{ok,<<"\",\r\n\t[{occupation, programmer}, {favorite, cod">>}
3> file:pread(S, 1, 10).
{ok,<<"person, \"j">>}
4> file:close(S).
ok

@spec file:pread(IoDevice, Start, Len) -> {ok, Bin} | {error, Why}

尋找檔案

這是用 file:list_dir 與 file:read_file_info 搭配 re:run 做出來的。

%% -*- coding: utf-8 -*-
-module(lib_find).
-export([files/3, files/5]).
-import(lists, [reverse/1]).

-include_lib("kernel/include/file.hrl").

files(Dir, Re, Flag) ->
    %% 這裡原本是用來簡化 regular expression 的輸入,例如可輸入 *.mp3
    %% 但實際上測試結果,卻發生問題,因此就直接把這個轉換拿掉
    %% Re1 = xmerl_regexp:sh_to_awk(Re),
    reverse(files(Dir, Re, Flag, fun(File, Acc) ->[File|Acc] end, [])).

files(Dir, Reg, Recursive, Fun, Acc) ->
    case file:list_dir(Dir) of
        %% 以 file:list_dir 列出所有檔案 list,然後放入 find_files
        {ok, Files} -> find_files(Files, Dir, Reg, Recursive, Fun, Acc);
        {error, _}  -> Acc
    end.

%% 以 File|T 一次處理一個檔案
%% 由該檔案的類型,加上 Recursive 參數決定要不要搜尋目錄下的檔案
%% 所以這是 depth-first-search 的方法
find_files([File|T], Dir, Reg, Recursive, Fun, Acc0) ->
    FullName = filename:join([Dir,File]),
    %% io:format("FullName: ~p ~p~n", [FullName, Reg]),
    case file_type(FullName) of
        regular ->
            %% re:run 的參數裡面,必須加上 unicode
            %% 且設定為 {caprure, none} 時,回傳值只有 match 或 nomatch
            case re:run(FullName, Reg, [{capture, none}, unicode]) of
                %{match, _, _}  ->
                match ->
                    Acc = Fun(FullName, Acc0),
                    %% io:format("Acc = ~p~n", [Acc]),
                    find_files(T, Dir, Reg, Recursive, Fun, Acc);
                _ ->
                    %% io:format("notmatch = ~n"),
                    find_files(T, Dir, Reg, Recursive, Fun, Acc0)
            end;
        directory ->
            %% 遇到目錄時,由 Resursive 參數決定要不要繼續尋找目錄下的檔案
            case Recursive of
                true ->
                    Acc1 = files(FullName, Reg, Recursive, Fun, Acc0),
                    find_files(T, Dir, Reg, Recursive, Fun, Acc1);
                false ->
                    find_files(T, Dir, Reg, Recursive, Fun, Acc0)
            end;
        error -> 
            find_files(T, Dir, Reg, Recursive, Fun, Acc0)
    end;
find_files([], _, _, _, _, A) ->
    A.

file_type(File) ->
    case file:read_file_info(File) of
        {ok, Facts} ->
            case Facts#file_info.type of
                regular   -> regular;
                directory -> directory;
                _         -> error
            end;
        _ ->
            error
    end.

remove xmerl_regexp:sh_to_awk

在尋找檔案的程式中,有使用到 xmerl_regexp:sh_to_awk 這個轉換 regular expression 的 module,實際上測試時,卻發覺使用了反而讓程式出現問題,所以就直接把這個部份移除。

re:run

另外就是 re:run 的使用方式,第三個參數可加上 {capture, none} ,但在測試時,最好把這個參數移除,或是改為 {capture, [1]},這樣才能確定是不是有 match 到正確的位置。像以下範例的狀況,實際上 pattern 符合的位置,不是預期的副檔名的位置,而是在前面目錄的位置。

這時候就把 pattern 由 .mp3 改成 .mp3$,這樣就確定位置沒錯了。

3> re:run("d:/mp3/misc/測試test.mp3", ".mp3", [{capture, none}, unicode]).
match
4> re:run("d:/mp3/misc/測試test.mp3", ".mp3", [unicode]).
{match,[{2,4}]}
5> re:run("d:/mp3/misc/測試test.mp3", ".mp3$", [unicode]).
{match,[{24,4}]}

file:list_dir

在測試時,我們發現目錄裡面有中文字的時候,就會發生找不到檔案的問題。這個問題是 erl 預設在處理 standard_io 使用的 encoding 為 latin1,因此就造成了 file:list_dir 發生問題。

10> lib_find:files("d:/mp3/disc/測試", ".mp3$", true).
[]
11> lib_find:files("d:/mp3/misc/hot1", ".mp3$", true).
[[100,58,47,109,112,51,47,109,105,115,99,47,104,111,116,49,
 [100,58,47,109,112,51,47|...]]

根據文件 unicode usage 的說明,windows 跟 macos 預設的 filename encoding 為 utf8,而 linux 是 latin1。

在 windows 的結果

1> file:native_name_encoding().
utf8

在 linux 的結果

1> file:native_name_encoding().
latin1

使用 file:list_dir 在 erl 與 werl 測試的結果不同,那是因為 werl 能支援 Unicode input and output,如果把測試程式碼寫到 module file 裡面,則都能運作。

在 windows 的 erl 測試,

1> file:list_dir("d:/mp3/disc/測試").
{error,enoent}
2> file:list_dir("d:/mp3/misc/hot1").
{ok,["00playall.m3u",
     [48,49,46,32,22283,22659,20043,21335,40,33539,36920,33251,
      41,46,109,112,51],
     [...]|...]}
3> lists:keyfind(encoding, 1, io:getopts()).
{encoding,latin1}

然而在 windows 的 werl 測試

3> file:list_dir("d:/mp3/disc/冰與火之歌").
{ok,["00playall.m3u","01playall.m3u","02playall.m3u",
     "03playall.m3u","readme.txt","S1","S2","S3"]}
4> file:list_dir("d:/mp3/misc/hot1").
{ok,["00playall.m3u",
     [48,49,46,32,22283,22659,20043,21335,40,33539,36920,33251,
      41,46,109,112,51],
     [...]|...]}
5> lists:keyfind(encoding, 1, io:getopts()).
{encoding,unicode}

Erlang Unicode 兩三事 有一些測試,我想在處理檔名時,除了作業系統,還要注意 erl, werl 跟 module 的行為,可能都不一樣。

unicode in erlang

erlang 在 R17 才會正式完整支援 unicode,尤其是 erl source file 的部份,預設也是 UTF-8。

根據 Using Unicode in Erlang 文件的說明,因為 R16B 預設coding 為 bytewise (or latin1) encoding,如果在 module 的程式碼裡面要寫上中文字,而檔案要存成 utf-8 時,erl module source file 的第一行就必須要寫上

%% -*- coding: utf-8 -*-

以目前的狀況,當程式裡面遇到 re:run 與 file:list_dir ,跟 file io 有關的程式時,都必須要注意

  1. 以 英文目錄名 "d:/mp3/misc" 測試
  2. 以 中文目錄名 "d:/mp3/misc/測試" 測試
  3. 以 英文檔名 "d:/mp3/misc/test.mp3" 測試
  4. 以 中文檔名 "d:/mp3/misc/測試test.mp3" 測試
  5. 在 windows 與 linux 環境測試

事實上在 Java 處理中文檔名,也常常遇到很多問題,最後通常我們都會把實際的檔名紀錄在 DB 中,而 disk 裡面都只會存英文或數字的檔案或目錄名稱,避免程式在 windows/linux 上運作,會發生不同結果的意外狀況。

讀取 mp3 的 ID3 tag

mp3 的檔案資訊 metadata 儲存在 ID3 標籤區塊中,在這裡指處理兩種最簡單的 ID3 格式:ID3v1 與 ID3v1.1

ID3v1是在檔案最後 128 byts 包含固定長度的標籤

長度 內容
3 TAG 三個字元
30 Title
30 Artist
30 Album
4 Year
30 Comment
1 Genre

ID3v1.1 加入了歌曲編號,就是把30 bytes 的 comment 改成下面的格式

長度 內容
28 Comment
1 0
1 歌曲編號

以下為掃描目錄內所有的 mp3,並將 ID3 資料寫入檔案中的程式。

%% -*- coding: utf-8 -*-
%% ---
%%  Excerpted from "Programming Erlang",
%%  published by The Pragmatic Bookshelf.
%%  Copyrights apply to this code. It may not be used to create training material, 
%%  courses, books, articles, and the like. Contact us if you are in doubt.
%%  We make no guarantees that this code is fit for any purpose. 
%%  Visit http://www.pragmaticprogrammer.com/titles/jaerlang for more book information.
%%---
-module(id3_v1).
-import(lists, [filter/2, map/2, reverse/1]).
-export([test/0, test2/0, dir/1, read_id3_tag/1]).

test() -> dir("d:/mp3/disc/測試").
test2() -> dir("d:/mp3/misc/hot1").

dir(Dir) ->
    %% 取得目錄裡面所有副檔名為 mp3 的檔案
    %%Files = lib_find:files(Dir, "\x{2e}\x{6d}\x{70}\x{33}", true),
    Files = lib_find:files(Dir, ".mp3$", true),
    %% 對每一個檔案以 read_id3_tag 檢查是否有 ID3
    L1 = map(fun(I) -> 
                     {I, (catch read_id3_tag(I))}
             end,  Files),
    %% L1 = [{File, Parse}] where Parse = error | [{Tag,Val}]
    %% we now have to remove all the entries from L where
    %% Parse = error. We can do this with a filter operation
    %% 以 filter 去掉結果為 error 的元素
    L2 = filter(fun({_,error}) -> false;
                   (_) -> true
                end, L1),
    %% 將 L2 dump 到檔案 mp3data 裡面
    lib_misc:dump("mp3data", L2).

read_id3_tag(File) ->
    case file:open(File, [read,binary,raw]) of
        {ok, S} ->
            Size = filelib:file_size(File),
            %% 讀取檔案最後 128 bytes
            {ok, B2} = file:pread(S, Size-128, 128),
            %% 判斷是否為 ID3 v1 or v1.1
            Result = parse_v1_tag(B2),
            file:close(S),
            Result;
        _Error ->
            error
    end.

%% 判斷是否為 ID3 v1.1
parse_v1_tag(<<$T,$A,$G,
               Title:30/binary, Artist:30/binary,
               Album:30/binary, _Year:4/binary,
               _Comment:28/binary, 0:8,Track:8,_Genre:8>>) ->
    {"ID3v1.1", 
     [{track,Track}, {title,trim(Title)},
      {artist,trim(Artist)}, {album, trim(Album)}]};
%% 判斷是否為 ID3 v1
parse_v1_tag(<<$T,$A,$G,
               Title:30/binary, Artist:30/binary,
               Album:30/binary, _Year:4/binary,
               _Comment:30/binary,_Genre:8>>) ->
    {"ID3v1", 
     [{title,trim(Title)}, 
      {artist,trim(Artist)}, {album, trim(Album)}]};
%% 其他狀況則傳回 error
parse_v1_tag(_) ->
    error.

%% 去掉 binary 資料裡面的尾端 0 與 空白字元
%% 先將 Bin 轉成 list,然後 trim_blanks,再轉回 binary
trim(Bin) -> 
    list_to_binary(trim_blanks(binary_to_list(Bin))).

%% 將 list X 反轉後,去掉尾端 0 與 空白,然後再反轉回來
trim_blanks(X) -> reverse(skip_blanks_and_zero(reverse(X))).

skip_blanks_and_zero([$\s|T]) -> skip_blanks_and_zero(T);
skip_blanks_and_zero([0|T])   -> skip_blanks_and_zero(T);
skip_blanks_and_zero(X)       -> X.

測試時,一樣會遇到 erl 無法處理中文目錄名稱的問題,可以把測試程式寫到 module 裡面,或是用 werl 測試。

寫入檔案

寫入 list 到檔案中

file:consult 可讀取檔案內容,我們可寫一個 unconsult 寫入資料

unconsult(File, L) ->
    {ok, S} = file:open(File, write),
    lists:foreach(fun(X) -> io:format(S, "~p.~n",[X]) end, L),
    file:close(S).

測試

2> lib_misc:unconsult("test1.dat", [{cats, run}, {weather, raining}]).
ok

會得到一個檔案 test1.dat,內容為

{cats,run}.
{weather,raining}.

如果用 file:consult 讀取檔案,會得到兩個 erlang terms

3> file:consult("test1.dat").
{ok,[{cats,run},{weather,raining}]}

io:format

@spec io:format(IoDevice, Format, Args) -> ok

  1. IoDevice 必須要以寫入模式開啟
  2. 對 Args 來說,要對應 Format 格式
    ~n 是換行,這是跨平台的,在widows 會有 CR LF,在 linux 則是 LF
    ~p pretty-print 此參數
    ~s 字串
    ~w 以標準語法寫入資料,用來輸出 erlang term

    ~s 還有一些其他的寫法,~10s是以10個字元的位置輸出字串,~-10S是靠左,~10.3.+s是在右邊留3個字元列印資料,其他的放 +,如果是 ~10.2.+s ,則結果就會變成 |++++++++ab|

test() ->
    io:format("|~10s|~n", ["abc"]),
    io:format("|~-10s|~n", ["abc"]),
    io:format("|~10.3.+s|~n", ["abc"]),
    io:format("|~10.10.+s|~n", ["abc"]),
    io:format("|~10.7.+s|~n", ["abc"]),
    io:format("~s~n", ["Hello Readers"]),
    io:format("~w~n", [123]),
    io:format("~s~n", ["that's it"]).

測試

20> lib_misc:test().
|       abc|
|abc       |
|+++++++abc|
|+++++++abc|
|+++abc++++|
Hello Readers
123
that's it
ok

file:write_file(File, IO)

IO list 是一個 list,其元素是 IO list、binary、0-255 整數,當 IO list 輸出到 File 時,會自動 flattened,清單的 { } 會被移除

scavenge_urls.erl 可以parsing html 網頁內容的資料,將所有

<a href ... </a>

資料取出來變成 list,然後再用這個 list 以 file:write_file(File, IO) 輸出到檔案。

% scavenge_urls.erl
-module(scavenge_urls).
-export([urls2htmlFile/2, bin2urls/1]).
-import(lists, [reverse/1, reverse/2, map/2]).

% 以 Urls list 加上 html ul href tag,然後存放到 File 裡面
urls2htmlFile(Urls, File) ->
    file:write_file(File, urls2html(Urls)).

% 將 html binary 資料裡面的 URLs,轉換成 list 回傳
bin2urls(Bin) ->  gather_urls(binary_to_list(Bin), []).

% 以 list 兩個元素製作 html 內容
urls2html(Urls) -> [h1("Urls"),make_list(Urls)].
% 第一行是 h1 Title
h1(Title) -> ["<h1>", Title, "</h1>\n"].
% 後面是 ul li 清單
make_list(L) ->
    ["<ul>\n",
     map(fun(I) -> ["<li>",I,"</li>\n"] end, L),
     "</ul>\n"]. 

% Bin 會先比對是不是 <a href 開頭
% 如果不是 就會 match [_|T] ,然後就去掉第一個字元,再運算一次 gather_urls
% 如果吻合 "<a href" ++ T ,接下來就利用 collect_url_body 取得 </a> 前面的內容
gather_urls("<a href" ++ T, L) ->
    % T1 是 collect_url_body 處理後,剩下來的後面的資料
    {Url, T1} = collect_url_body(T, reverse("<a href")),
    gather_urls(T1, [Url|L]);
gather_urls([_|T], L) ->
    gather_urls(T, L);
gather_urls([], L) ->
    L.

% 跟 gather_urls 一樣的方法,L 一開始就是 reverse("<a href")
% 先比對 前面 是不是 </a> 開頭
% 如果不是 就會 match [H|T] ,然後就將第一個字元存到後面的 L,再運算一次 collect_url_body
% 如果吻合 "</a>" ++ T ,因為 L 的字元是相反的,就先 reverse,然後再加上 </a>,並將剩下的 T 回傳回去
collect_url_body("</a>" ++ T, L) -> {reverse(L, "</a>"), T};
collect_url_body([H|T], L)       -> collect_url_body(T, [H|L]);
collect_url_body([], _)          -> {[],[]}.

測試

1> S = socket_examples:nano_get_url("www.erlang.org").
<<"HTTP/1.0 200 OK\r\nServer: inets/5.7.1\r\nDate: Mon, 10 Feb 2014 06:51:11 GMT\r\nSet-Cookie: eptic_cookie=erlangorg@hades-"...>>
2> L = scavenge_urls:bin2urls(S).
["<a href=\"https://github.com/esl/erlang-web\">Erlang Web</a>", ...]
3> scavenge_urls:urls2htmlFile(L, "gathered.html").
ok

gathered.html 的內容就會是

    <h1>Urls</h1>
    <ul>
    <li><a href="https://github.com/esl/erlang-web">Erlang Web</a></li>
    ...
    </ul>

寫入一個隨機存取檔案

將檔案以隨機存取的方式寫入資料,作法跟讀取類似,先以 write mode 打開檔案,然後使用 file:pwrite(Position, Bin) 寫入資料。

1> {ok, S} = file:open("test3.dat", [raw, write, binary]).
{ok,{file_descriptor,prim_file,{#Port<0.500>,568}}}
2> file:pwrite(S, 10, <<"new">>).
ok
3> file:close(S).
ok

test3.dat 為以下的內容,但前面 10 個字元都是 16#00

          new

目錄與檔案操作

file:list_dir(Dir) 可取得目錄 Dir 裡面的檔案清單(包含子目錄)
file:makr_dir(Dir) 會建立一個新的目錄
file:del_dir(Dir) 會刪除一個目錄

file:copy(Source, Destination) 將 Source 複製到 Destination
file:delete(File) 刪除檔案

取得檔案的資訊 file:read_file_info(F)

如果檔案 F 是有效的檔案或目錄名稱,就會傳回 {ok, Info} ,Info 是型別為 #file_info 的 record,定義如下

-record( file_info,
    { size,    % size of file in bytes
    type,    % atom: device, directory, regular, symlink, other
    access,    % atom: read, write, read_write, none
    atime,    % {{Year,Mon,Day},{Hour,Min,Sec}} 檔案上次被讀取的時間
    mtime,    % {{Year,Mon,Day},{Hour,Min,Sec}} 檔案上次被寫入的時間
    ctime,    % 在 linux 是檔案上次被修改的時間,在 windows 是檔案被建立的時間
    mode,    % Integer: File Permissions, windows 裡面 owner permission會複製到group與user
    links,    % number of links to the file,如果不支援 link 就會永遠是 1
    major_device, % Integer: Identifies the file system(linux) 或是 device number in windows ( A:=0, B:=1 )
    minor_device,
    inode,
    uid,
    gid
    })

可以取得檔案的大小及type,並擴充 file:list_dir 增加檔案的資訊

-include_lib("kernel/include/file.hrl").
file_size_and_type(File) ->
    case file:read_file_info(File) of
    {ok, Facts} ->
        {Facts#file_info.type, Facts#file_info.size};
    _ ->
        error
    end.

ls(Dir) ->
    {ok, L} = file:list_dir(Dir),
    map(fun(I) -> {I, file_size_and_type(I)} end, sort(L)).

測試

1> lib_misc:ls(".").
[{"code",{directory,0}},
 {"gathered.html",{regular,1768}},
 {"lib_misc.beam",{regular,1616}},
 {"lib_misc.erl",{regular,882}},
 {"scavenge_urls.beam",{regular,1540}},
 {"scavenge_urls.erl",{regular,1746}},
 {"socket_examples.beam",{regular,2784}},
 {"socket_examples.erl",{regular,2651}},
 {"test.md",{regular,6059}},
 {"test1.dat",{regular,31}},
 {"test3.dat",{regular,13}}]

filelib 模組有一些小的函數,使用上比 #file_info 方便,例如 file_size(File) 與 is_dir(X)

其他功能

file:open 提供的模式有很多種,例如 compressed 是用來讀寫 gzip 壓縮檔

{error, enoent} 錯誤碼,是代表檔案不存在的意思

filename 模組有很多拆解檔名的函數,可找出副檔名,或組合檔名

filelib 模組有少量有用的函數,例如 filelib:ensure_dir(Name) 可確保父目錄存在,如果不存在就會建立目錄

character transcoding

任何其他的字元字串,經過 java 讀取後,就會變成 utf16 Big Endian encoding(參閱 Encoding Gossip: Java 的字串),如果需要特殊編碼的字串,就要加上編碼的參數。在Java中遇到亂碼問題的時候,第一步就是要識別字串的編碼是什麼,假設是 Big5,我們就要以 getBytes 告訴 JVM 要以 Big5 的方式取得字串的 byte array,然後再由byte array轉換為 utf8 的編碼。

byte[] binary = "測試".getBytes("Big5");
String b= new String(binary, "UTF-8");

erlang 一直到 R13 之後,才開始慢慢重視 unicode 的問題,mbcs 就是用來解決編碼轉換的問題。

要使用 mbcs,就先將 project checkout 下來,然後放到 C:\Program Files\erl5.10.4\lib\mbcs 路徑下,編譯後的 beam 放到 C:\Program Files\erl5.10.4\lib\mbcs\ebin 裡面,然後就可以用 werl 測試。

不能使用 erl 測試的原因是因為 erl 預設的編碼為 latin1,要使用 werl 才能正確處理 unicode 資料輸入。

Eshell V5.10.4  (abort with ^G)
1> l(mbcs).
{module,mbcs}
2> mbcs:start().
ok
3> mbcs:encode("世界,你好", gbk).
<<"ÊÀ½ç£¬ÄãºÃ">>
4> mbcs:encode("世界,你好", big5).
<<"¥@¬É¡A§A¦n">>
5>

參考

Erlang and OTP in Action
Programming Erlang: Software for a Concurrent World