Erlang文件编程(下篇)

三:写入文件的各种方式

写入文件所涉及的操作和读取文件基本相同。

1.把数据列表写入文件

假设想要创建一个能用file:consult读取的文件。标准库里实际上并没有这样的函数,所以我们将自己编写它。不妨把这个函数称为unconsult:

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

可以调用下面方法来检查能否使用:

1> lib misc:unconsult("testl.dat",
                     [{cats,["zorrow","daisy"]},
                      {weather,snowing}]).
ok

2> file:consult("test1.dat").
{ok,[{cats,["zorrow","daisy"]},{weather,snowing}]}

unconsult以write模式打开文件,然后调用io:format(S, "~p.~n", [X])来把数据类型写入文件,io:format承担了创建格式化输出的重任。要生成格式化输出,我们会做以下调用。

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

其中ioDevice是一个I/O对象(必须以write模式打开),Format是一个包含格式代码的字符串,Args是待输出的项目列表。Args里的每一项都必须对应格式字符串里的某个格式命令。格式命令以一个波浪字符(~)开头。

2.把各行写入文件

下面这些命令和之前的例子很像,我们只是使用了不同的格式命令。

1> {ok,S} = file:open("test2.dat",write).
{ok,<0.62.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

这样就创建出了一个名为test2.dat的文件,它的内容如下:

Hello readers
123
that's it

3.一次性写入整个文件

编写一个名为urls2htmlFile(L, File)的简单函数,它会接受一个URL列表L并生成HTML文件,里面的URL都显示为可点击链接。这样就可以采用单次I/O操作写入整个文件的技巧。我们将在scavenge_urls模块里编写程序。首先是程序的头部信息:

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

这个程序有两个入口点。urls2htmlFile(Urls, File)接受一个URL列表并创建HTML文件,在文件里为每个URL各创建一个可点击的链接。bin2urls(Bin)遍历一个二进制型,然后返回一个包含该二进制型内所有URL的列表。urls2htmlFile的代码如下:

%% scavenge_urls.erl
urls2htmlFile(Urls,File) ->
    file:write_file(File,urls2html(Urls)).

bin2urls(Bin) -> gather_urls(binary_to_list(Bin), []).
 
urls2html(Urls) -> [h1("Urls"), make_list(Urls)].

hl(Title) -> ["<hl>", Title, "</hl>In"].

make_list(L) ->
    ["<ul>ln",
     map(fun(I) -> ["<li>", I, "</li>In"] end, L),
     "</ul>1n"].

这段代码返回一个由字符组成的I/O列表。请注意我们没有尝试扁平化这个列表(这么做效率很低)。 我们生成一个由字符组成的深层列表 ,然后把它直接扔给输出方法 。 用 file:write_file把这个I/O列表写入文件时,I/O系统会自动扁平化列表(也就是说,它只会输出列表里的字符,忽略列表括号)。最后,从二进制型里提取URL的代码如下:

%% scavenge_urls.erl
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([],_) -> {[], []}.

要运行它,需要有一些数据来解析。输入数据(一个二进制型)是HTML网页的内容,所以需要找一张HTML网页来操作。我们将通过socket_examples:nano_get_url来实现。

我们将在shell里分几步完成它。

1>  B = socket_examples:nano_get_url("www.erlang.org"),
    L = scavenge_urls:bin2urls(B),
    scavenge_urls:urls2htmlFile(L,"gathered.html").
ok

这样就生成了gathered.html文件。

<h1>Ur1s</h1>
<ul>
<li><a href="old news.html">older news.....</a></li>
<li><a href="http://www.erlang-consulting.com/training_fs.html">here</a></li>
<li><a href="project/megaco/">Megaco home</a></li>
<li><a href="EPLICENSE">Erlang Public License (EPL)</a></li>
<li><a href="user.html#smtp_client-1.0">smtp_client-1.0</a></li>
<li><a href="download-stats/">download statistics graphs</a></li>
<li><a href="project/test_server">Erlang/OTP Test
Server</a></li>
<li><a href="http://www.erlang.se/euc/06/">proceedings</a></li>
<li><a href="/doc/doc-5.5.2/doc/highlights.html">
Read more in the release highlights.
</a></li>
<li><a href="index.html"><img src="images/erlang.gif"
border="0"alt="Home"></a></li>
</ul>

4.写入随机访问文件:

在随机访问模式下写入某个文件和读取它很相似。首先,必须用write模式打开这个文件。接下来,用file:pwrite(IoDev, Position, Bin)写入文件。这有一个例子:

1> {ok, S} = file:open("some_filename_here", [raw,write,binary]).
{ok, ...}

2> file:pwrite(S,10, <<"new">>).
ok

3> file:close(S).
oK

它从文件内偏移量为10的位置开始写入字符串new,覆盖了原有内容。

四:目录和文件操作

file里有三个操作目录的函数。list_dir(Dir)用来生成一个Dir里的文件列表,make_dir(Dir)创建一个新目录,del_dir(Dir)删除一个目录。

1.查找文件信息

要查找文件F的信息,我们会调用file:read_file_info(F)。如果F是一个合法的文件或目录名,它就会返回{ok, Info}。Info是一个#file_info类型的记录,此类型的定义如下:

-record(file_info,
{size,               % Size of file in bytes.
type,                % Atom:device,directory,regular,
                     % or other.
access,              % Atom:read,write,read write,or none.
atime,               % The local time the file was last read:
                     % {{Year, Mon, Day],{Hour, Min, Sec]}.
mtime,               % The local time the file was last written.
ctime,               % The interpretation of this time field
                     % is dependent on operating system.
                     % On Unix it is the last time the file or
                     % or the inode was changed.On Windows,
                     % it is the creation time.
mode,                % Integer:File permissions.On Windows,
                     % the owner permissions will be duplicated
                     % for group and user.
links,               % Number of links to the file (1 if the
                     % filesystem doesn't support links).
...
}).

注:mode和access字段有重叠。可以用mode一次性设置多个文件的属性,也可以用更简单的 access操作。

要查找某个文件的大小和类型,就会调用read_file_info,就像下面这个例子(请注意, 我们必须包含file.hrl,因为它内含#file_info记录的定义):

%% lib_misc.erl
-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.

现在可以增强list_file返回的目录清单了,方法是在ls()函数里添加文件信息,就像下面这样:

%% lib_misc.erl
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(".").
[{"Makefile",{regular,1244}},
{"README",{regular,1583}},
{"abc.erl",{regular,105}},
{"alloc_test.erl",{regular,303}},
...
{"socket_dist",{directory,4096}},
...

为了方便起见,filelib模块导出了一些小方法,比如file_size(File)和is_dir(X)。它们只不过是file:read_file_info的接口。如果只想获得文件大小,更方便的做法是调用filelib:file_size,而不是调用file:read_file_info然后解包#file_info记录里的元素。

2.复制和删除文件

file:copy(Source, Destination)会把文件Source复制到Destination里。 file:delete(File)会删除File。

五:一个查找工具函数

在最后一个例子里,我们将用file:list_dir和file:read_file_info来编写一个通用型 “查找”工具。 这个模块的主要入口点如下:

lib_find:files(Dir, RegExp, Recursive, Fun, Acc0)

(1) Dir
    这个目录名是文件搜索的起点。

(2) RegExp
    这是一个shell风格的正则表达式,用于测试我们找到的文件。如果遇到的文件匹配这个正
则表达式,就会调用Fun(File, Acc),其中File是匹配正则表达式的文件名。

(3) Recursive = true | false
    这个标记决定了搜索是否应该层层深入当前搜索目录的子目录。

(4) Fun(File, AccIn) -> AccOut
    如果regExp匹配File,这个函数就会被应用到File上。Acc是一个初始值为Acc0的归集
器。Fun在每次调用后必须返回一个新的归集值,这个值会在下次调用Fun时传递给它。
归集器的最终值就是lib_find:files/5的返回值。

可以向lib_find:files/5传递任何我们喜欢的函数。举个例子,可以用下面这个函数创建一个文件列表,并给它传递一个空列表作为初始值:

fun(File, Acc) -> [File | Acc] end

模块入口点lib_find:files(Dir, ShellRegExp, Flag)为此程序的最常用功能提供了个简化的入口点。这里的ShellRegExp是一个shell风格的通配符模式,比完整形式的正则表达式更容易编写。作为这种简化调用形式的一个例子,下面的调用:

%% lib_find.erl
-module(lib find).
-export([files/3,files/5]).
-import(lists,[reverse/1]).
-include_lib("kernel/include/file.hrl").

files(Dir,Re,Flag) ->
    Rel = xmerl_regexp:sh_to_awk(Re),
    reverse = (files(Dir, Rel, Flag, fun(File,Acc) -> [File|Acc] end,[])).

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 ->
            case re:run(FullName, Reg, [{capture,none}]) of
                match ->
                    Acc = Fun(FullName, Acc0),
                    find_files(T,Dir, Reg, Recursive, Fun, Acc);
                nomatch ->
                    find_files(T,Dir, eg, Recursive, Fun, Acc0)
            end;
        directory ->
            case Recursive of
                true ->
                    Acc1 = files(FullName, Reg, Recursive, Fun, Acc0),
                    find_files(T, Dir, Reg, Recursive, Fun, Accl);
                false ->
                    find_files(T, Dir, Reg, Recursive, Fun, AccO)
            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.

这就是查找文件的方法。你可能注意到这个程序都是顺序的。要加速它,可以用多个并行进程来实现并行处理。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

明明如皓

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值