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 & 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 & 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"]