第十三章 对文件编程

13.1 库的组织结构

四个操作文件的模块

  • file模块
    包含了用于文件打开、关闭、读取、写入和目录列表等功能的函数。
  • filename模块
    以平台独立的方式提供了一套操作文件名的函数。
  • filelib模块
    是file模块的扩展, 提供了一套辅助函数用于生成文件列表、检验文件类型等操作。
  • io模块
    提供了一系列对已打开的文件进行操作的函数。

13.2 读取文件的不同方法

测试数据 data1.dat

{person, "joe", "armstrong",
    [{occupation, programmer},
     {favoriteLanguage, erlang}]}.
{cat, {name, "zorro"},
      {owner, "joe"}}.

file模块API:

函数 描述
change_group 修改一个文件的群组
change_owner 修改一个文件的所有者
change_time 修改一个文件的最近访问或者最近更新时间
close 关闭一个文件
consult 从一个文件中读取Erlang项
copy 复制文件内容
del_dir 删除一个目录
delete 删除一个文件
eval 在文件中对一个Erlang表达式求值
format_error 返回一个描述错误原因的字符串
get_cwd 获取当前工作目录
list_dir 获取一个目录中的文件列表
make_dir 创建一个目录
make_link 为一个文件创建一个硬链接
make_symlink 为一个文件或目录创建符号链接
open 打开一个文件
position 设置一个文件的访问位置
pread 在一个特定的文件访问位置读取文件
pwrite 在一个特定的文件访问位置写入文件
read 从文件中读取内容
read_file 读取整个文件
read_file_info 获取一个文件的信息
read_link 查看一个文件的链接指向
read_link_info 获得一个文件或者链接的信息
rename 重命名一个文件
script 对一个文件中的Erlang表达式求值并返回结果
set_cwd 设定当前的工作目录
sync 把一个文件的内存状态同步到该文件的物理存储上
truncate 截断一个文件
write 向一个文件写入数据
write_file 写入整个文件
write_file_info 修改一个文件的信息

13.2.1 从文件中读取所有Erlang数据项

# 读取成功返回{ok, [Term]}, 否则返回{error, Reason}
1> file:consult("data1.dat").
{ok,[{person,"joe","armstrong",
             [{occupation,programmer},{favoriteLanguage,erlang}]},
     {cat,{name,"zorro"},{owner,"joe"}}]}

13.2.2 从文件的数据项中一次读取一项

# 以只读方式打开文件, 成功则返回{ok, IoDevice}, 否则返回{error, Reason} 
1> {ok, S} = file:open("data1.dat", read).
{ok,<0.35.0>}

# 读取数据, 返回{ok, Term} | {error, Why} | eof 
2> io:read(S, '').
{ok,{person,"joe","armstrong",
            [{occupation,programmer},{favoriteLanguage,erlang}]}}
3> io:read(S, '').
{ok,{cat,{name,"zorro"},{owner,"joe"}}}
4> io:read(S, '').
eof

# 关闭IoDevice, 返回 ok | {error, Why} 
5> file:close(S).
ok

利用以上函数实现file模块中的consult函数

consult(File) ->
    case file:open(File, read) of
        {ok, S} ->
            %% 成功打开文件后将IoDevice交由consult1函数处理
            Val = consult1(S),
            file:close(S),
            {ok, Val};
        {error, Why} ->
            {error, Why}
    end.

consult1(S) ->
    case io:read(S, '') of
        %% 循环使用io:read逐项读取IoDevice中的数据并将结果拼接成列表
        {ok, Term} ->[Term|consult1(S)];
        eof        ->[];
        Error      ->Error
    end.

如果想查看标准库中的实现, 可以使用

1> code:which(file).
"/usr/local/Cellar/erlang/R15B01/lib/erlang/lib/kernel-2.15.1/ebin/file.beam"code:which(file)
#从而找到源码文件 /usr/local/Cellar/erlang/R15B01/lib/erlang/lib/kernel-2.15.1/src/file.erl 

13.2.3 从文件中一次读取一行数据

1> {ok, S} = file:open("data1.dat", read).
{ok,<0.49.0>}
2> io:get_line(S, '').
"{person, "joe", "armstrong",
"
3> io:get_line(S, '').
"    [{occupation, programmer},
"
4> io:get_line(S, '').
"     {favoriteLanguage, erlang}]}.
"
5> io:get_line(S, '').
"{cat, {name, "zorro"},
"
6> io:get_line(S, '').
"      {owner, "joe"}}.
"
7> io:get_line(S, '').
eof
8> file:close(S).
ok

13.2.4 将整个文件的内容读入到一个二进制数据中

# 读取成功返回 {ok, Bin}, 否则返回 {error, Why} 
1> file:read_file("data1.dat").
{ok,<<"{person, "joe", "armstrong",
    [{occupation, programmer},
     {favoriteLanguage, erlang}]}.
{cat, {name, "...>>}}}

13.2.5 随机读取一个文件

1> {ok, S} = file:open("data1.dat", [read, binary, raw]).
{ok,{file_descriptor,prim_file,{#Port<0.582>,11}}}

# file:pread(IoDevice, Start, Len)
# 从第N个字节开始, 读取长度为Len字节的数据
2> file:pread(S, 22, 46).
{ok,<<"rong",
    [{occupation, programmer},
     {fa">>}
3> file:pread(S, 1, 10). 
{ok,<<"person, "j">>}

13.2.6 读取ID3标记

MP3文件本身不存储有关音乐属性的信息, 程序员Eric Kemp发明了ID3标记, 将文件内容信息存储在MP3文件的一个标记格式区块中。
ID3v1标记的结构:

长度 内容
3 TAG
30 标题
30 艺术家
30 专辑
4 年度
30 注释
1 其它

ID3v1.1添加了音轨号, 将上面存储注释的30字节分为如下结构:

长度 内容
28 注释
1 0
1 音轨号

读取ID3v1标记的程序实现

-module(id3_v1).
-import(lists, [filter/2, map/2, reverse/1]).
-export([test/0, dir/1, read_id3_tag/1]).

test() ->dir("/Users/matrix/Music/Enigma/").

dir(Dir) ->
    %% 递归搜索MP3文件
    Files = lib_find:files(Dir, "*.mp3", true),
    L1 = map(fun(I) ->
                     %% 解析每个MP3文件
                     {I, (catch read_id3_tag(I))}
             end, Files),
    L2 = filter(fun({_, error}) ->false;
                   (_) ->true
                end, L1),
    lib_misc:dump("mp3data", L2).

read_id3_tag(File) ->
    case file:open(File, [read, binary, raw]) of
        {ok, S} ->
            Size = filelib:file_size(File),
            {ok, B2} = file:pread(S, Size-128, 128),
            Result = parse_v1_tag(B2),
            file:close(S),
            Result;
        Error ->
            {File, Error}
    end.

%% 按照ID3v1的两个版本进行解析
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)}]};

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)}]};

parse_v1_tag(_) ->error.

trim(Bin) ->
    list_to_binary(trim_blanks(binary_to_list(Bin))).

trim_blanks(X) ->reverse(skip_blanks_and_zero(reverse(X))).

%% 进行位匹配, 删除空格和0 
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.

13.3 写入文件的不同方法

13.3.1 向一个文件中写入一串Erlang数据项

unconsult(File, L) ->
    {ok, S} = file:open(File, write),

    %% io:format(IoDevice, Format, Args)
    %% Format是一个包含了格式化代码的字符串
    %% ~p 完整打印参数
    %% ~s 字符串参数
    %% ~w 标准语法写入数据
    %% ~n 换行符
    %% Args为数据项
    lists:foreach(fun(X) ->io:format(S, "~p.~n", [X]) end, L),
    file:close(S).

运行结果:

$ cat test1.dat 
{cats,["zorrow","daisy"]}.
{weather,snowing}.

13.3.2 向文件中写入一行

1> {ok, S} = file:open("test2.dat", write).
{ok,<0.33.0>}
2> io:format(S, "~s~n", ["Hello readers"]).
ok
3> io:format(S, "~w~n", [123]).
ok
4> io:format(S, "~s~n", ["that's it"]).
ok
5> file:close(S).
ok

查看结果:

$ cat test2.dat 
Hello readers
123
that's it

13.3.3 一步操作写入整个文件

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

%% 将列表写入到文件
urls2htmlFile(Urls, File) ->
    file:write_file(File, urls2html(Urls)).

%% 将二进制数据转成列表
bin2urls(Bin) ->gather_urls(binary_to_list(Bin), []).

%% 根据列表中的数据构建html代码
urls2html(Urls) ->[h1("Urls"), make_list(Urls)].
h1(Title) ->["<h1>", Title, "</h1>
"].
make_list(L) ->
    ["<ul>/n",
     map(fun(I) ->["<li>", I, "</li>
"] end, L),
     "</ul>
"].

%% 匹配链接, 拼接成列表
gather_urls("<a href" ++T, L) ->
    {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.

%% 匹配链接结束符以获取链接实体
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> B = socket_examples:nano_get_url("www.erlang.org").
<<"HTTP/1.0 200 OK
Server: inets/5.7.1
Date: Sun, 03 Nov 2013 03:10:14 GMT
Set-Cookie: eptic_cookie=erlangorg@hades-"...>>
2> L = scavenge_urls:bin2urls(B).
["<a href="https://github.com/esl/erlang-web">Erlang Web</a>",
 "<a href="http://www.twitter.com/erlang_org"><img src="/icons/twitter.png" width="32"/></a>",
 "<a href="http://www.github.com/erlang/otp"><img src="/images/GitHub-Mark-32px.png"/></a>",
 "<a href="/download.html" title="DOWNLOAD"><img src="/icons/download.png"/></a>",
 "<a href="/event">More events...</a>",
 "<a href="/mailman/listinfo/erlang-questions">
		Listinfo &amp; subscription...
	    </a>",
 "<a href="/pipermail/erlang-questions/2013-November/075894.html" target="_blank">Arch Linux patches?
</a>",
 "<a href="/pipermail/erlang-questions/2013-November/075903.html" target="_blank">ETS-TRANSFER
</a>",
 "<a href="/pipermail/erlang-questions/2013-November/075904.html" target="_blank">emysql
</a>",
 "<a href="/pipermail/erlang-questions/2013-November/075905.html" target="_blank">Mysterious gen_server timeouts in MIX
</a>",
 "<a href="/pipermail/erlang-questions/2013-November/075910.html" target="_blank">On Pull Requests Comments
</a>",
 "<a href="/news/59">Erlang/OTP R16B02 has been released! </a>",
 "<a href="/news/60">Erlang talks at Code Mesh 3-5 December 2013: the Alternative Programming Conference</a>",
 "<a href="/news/61">Toronto Erlang Factory Lite 23 November 2013</a>",
 "<a href="http://www.github.com/erlang/otp"><img src="/images/GitHub-Mark-32px.png" width="35"/></a>",
 "<a href="/download.html" class="btn btn-success">Download Erlang/OTP</a>"]
3> scavenge_urls:urls2htmlFile(L, "gathered.html").
ok

$ cat gathered.html 
<h1>Urls</h1>
<ul>/n<li><a href="https://github.com/esl/erlang-web">Erlang Web</a></li>
<li><a href="http://www.twitter.com/erlang_org"><img src="/icons/twitter.png" width="32"/></a></li>
<li><a href="http://www.github.com/erlang/otp"><img src="http://images.cnblogs.com/GitHub-Mark-32px.png"/></a></li>
<li><a href="/download.html" title="DOWNLOAD"><img src="/icons/download.png"/></a></li>
<li><a href="/event">More events...</a></li>
<li><a href="/mailman/listinfo/erlang-questions">
        Listinfo &amp; subscription...
        </a></li>
<li><a href="/pipermail/erlang-questions/2013-November/075894.html" target="_blank">Arch Linux patches?
</a></li>
<li><a href="/pipermail/erlang-questions/2013-November/075903.html" target="_blank">ETS-TRANSFER
</a></li>
<li><a href="/pipermail/erlang-questions/2013-November/075904.html" target="_blank">emysql
</a></li>
<li><a href="/pipermail/erlang-questions/2013-November/075905.html" target="_blank">Mysterious gen_server timeouts in MIX
</a></li>
<li><a href="/pipermail/erlang-questions/2013-November/075910.html" target="_blank">On Pull Requests Comments
</a></li>
<li><a href="/news/59">Erlang/OTP R16B02 has been released! </a></li>
<li><a href="/news/60">Erlang talks at Code Mesh 3-5 December 2013: the Alternative Programming Conference</a></li>
<li><a href="/news/61">Toronto Erlang Factory Lite 23 November 2013</a></li>
<li><a href="http://www.github.com/erlang/otp"><img src="http://images.cnblogs.com/GitHub-Mark-32px.png" width="35"/></a></li>
<li><a href="/download.html" class="btn btn-success">Download Erlang/OTP</a></li>
</ul>$ 

13.3.4 在随机访问模式下写入文件

# 写入前
$ cat test2.dat 
Hello readers
123
that's it

# 执行写入操作 
1> {ok, S} = file:open("test2.dat", [raw, write, binary]).
{ok,{file_descriptor,prim_file,{#Port<0.582>,11}}}

# file:pwrite(IoDevice, Position, Bin)
2> file:pwrite(S, 10, <<"new data
">>).
ok
3> file:close(S).
ok

# 写入后
$ cat test2.dat 
new data 

13.4 目录操作

  • list_dir(Dir)
    用于生成Dir目录下的文件列表
  • make_dir(Dir)
    用于创建一个新的目录
  • del_dir(Dir)
    用于删除目录

13.5 查询文件属性

使用read_file_info(File)来查询文件的属性, 查询成功则返回{ok, Info}, 其中Info为#file_info类型。
file_info的结构:

属性 含义
size 文件大小(字节)
type 文件类型(设备, 目录, 常规文件, 其它)
access 读, 写, 读写, 其它
atime 最后一次读时间{{Year, Mon, Day}, {Hour, Min, Sec}}
ctime 最后修改时间(Unix); 创建时间(Windows)
mode 数字表示的文件权限
links 指向当前文件的链接数量
major_device 表示文件系统编号

利用它实现输出更多信息的ls函数

%% file_info结构的定义在这个文件中
-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),
    lists:map(fun(I) ->{I, file_size_and_type(I)} end, lists:sort(L)).

运行结果:

1> lib_misc:ls(".").
[{"data1.dat",{regular,141}},
 {"gathered.html",{regular,1551}},
 {"id3_v1.beam",{regular,1968}},
 {"id3_v1.erl",{regular,1763}},
 {"lib_find.beam",{regular,1460}},
 {"lib_find.erl",{regular,1744}},
 {"lib_misc.beam",{regular,1540}},
 {"lib_misc.erl",{regular,1092}},
 {"scavenge_urls.beam",{regular,1444}},
 {"scavenge_urls.erl",{regular,998}},
 {"socket_examples.beam",{regular,2692}},
 {"socket_examples.erl",{regular,2651}},
 {"test1.dat",{regular,46}},
 {"test2.dat",{regular,18}}]

13.6 复制和删除文件

  • copy(Source, Destination)
    复制Source到Destination
  • delete(File)
    删除File

13.7 小知识

  • 文件模式
    open(File)可以使用多种模式
  • 修改文件属性
    可以使用file模块中的功能函数实现修改文件的时间, 群组, 系统链接
  • 错误代码
    所有错误都是形如{error, Why}的元组
  • filename模块
    从路径中提取文件名, 扩展名, 具有平台无关性
  • filelib模块
    ensure_dir(Name)判断目录(及其父目录)是否存在, 不存在则创建

13.8 一个搜索小程序

-module(lib_find_my).
-export([files/3, files/5]).
-import(lists, [reverse/1]).

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

files(Dir, Re, Flag) ->
    reverse(files(Dir, Re, Flag, fun(File, Acc) ->[File|Acc] end, [])).

%% Dir        执行搜索的路径名
%% Reg        匹配文件名的正则
%% Recursive  是否对子目录递归搜索
%% Fun        正则匹配后调用的函数
%% Acc        累加器
files(Dir, Reg, Recursive, Fun, Acc) ->
    case file:list_dir(Dir) of
        {ok, Files} ->find_files(Files, Dir, Reg, Recursive, Fun, Acc);
        {error, _}  ->Acc
    end.

find_files([File|T], Dir, Reg, Recursive, Fun, Acc0) ->
    FullName = filename:join([Dir, File]),
    case file_type(FullName) of
        regular ->
            %% 新版本的Erlang中regexp模块由re模块取代
            case re:run(FullName, Reg) of
                {match, _} ->
                    Acc = Fun(FullName, Acc0),
                    find_files(T, Dir, Reg, Recursive, Fun, Acc);
                _  ->
                    find_files(T, Dir, Reg, Recursive, Fun, Acc0)
            end;
        directory ->
            %% 对于目录只有当Recursive设置为true时才递归处理
            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.

运行结果:

1> lib_find_my:files(".", ".*[.](erl).*$", false).
["./socket_examples.erl","./scavenge_urls.erl",
 "./lib_misc.erl","./lib_find.erl","./id3_v1.erl"]
2> lib_find_my:files(".", ".*[.](beam).*$", false).
["./socket_examples.beam","./scavenge_urls.beam",
 "./lib_misc.beam","./lib_find.beam","./id3_v1.beam"]
原文地址:https://www.cnblogs.com/KylinBlog/p/13536677.html