zoukankan      html  css  js  c++  java
  • 第十三章 对文件编程

    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"]
    
  • 相关阅读:
    解惑如何保证数组元素的可见性(yet)
    为什么内存锁在有事务切面的情况下会形同虚设 隔离级别与事务
    小事故合集
    第4种打整包插件,urlfactory already set
    三目运算符与字节码阅读
    servlet application/x-www-form-urlencoded 坑
    多实例重复发邮件
    当动态代理遇到ioc (五)使用cglib切面与自定义类加载器构建独有环境aop日志
    mac笔记本如何重制secureCRT的体验期|试用版本
    npm install出现的错误
  • 原文地址:https://www.cnblogs.com/KylinBlog/p/13536677.html
Copyright © 2011-2022 走看看