ace007
ace007

Reputation: 577

File IO and list processing

I asked a similar question, not sure what wasnt clear about it but I'll try again. I have a file. File name is file.txt, I read file.txt in to a list. Now I can print this to the console and it will show:

blah
blah
blah
blah

That is fine. Perfect :) Now how would I forward that to a new file? so that the new file contains:

blah
blah
blah
blah

Nothing more and nothing less. Here is the code I am using to read a file in to a list: {ok, Device} = file:open("file.txt", [read]), Li = readdata(Device, []).

readdata(Device, Accum) ->
case io:get_line(Device, "") of
    eof  -> file:close(Device), Accum;
    Line -> readdata(Device, Accum ++ [Line])
end.

So again, the new file with display EXACTLY what the file I read displays, no extra characters, not all on 1 line..etc.. just the same :)

Upvotes: 1

Views: 959

Answers (2)

dsmith
dsmith

Reputation: 1978

So this is what I came up with. I modified your readdata/2 slightly to optimize the append and remove the newline. The write/2 function uses lists:foreach/2 and io:fwrite/3 to write to the file.

-module(rwlist).
-export([read/1,write/2]).

read(FileName) ->
   case file:open(FileName, [read]) of
      {ok, Device} ->
         readdata(Device, [])
   end.

readdata(Device, Accum) ->
   case io:get_line(Device, "") of
       eof  -> file:close(Device), lists:reverse(Accum);
       Line -> readdata(Device, [(Line--"\n")|Accum])
   end.

write(FileName, List) ->
   case file:open(FileName, [write]) of
      {ok, Device} ->
         lists:foreach(fun(Line) -> writeline(Device, Line) end, List),
         file:close(Device)
   end.

writeline(Device, Line) -> writeline(Device, Line, os:type()).

writeline(Device, Line, {win32,_}) -> io:fwrite(Device, "~s\r\n", [Line]);
writeline(Device, Line, _) -> io:fwrite(Device, "~s\n", [Line]).

Here's the test...

57> List=rwlist:read("list").  
["item 1","item 2","item 3","item 4"]
58> rwlist:write("list2", List).
ok
59> List2=rwlist:read("list2"). 
["item 1","item 2","item 3","item 4"]

Of course if you are just copying a file Dmitry's answer is better.

Upvotes: 0

Dmitry Belyaev
Dmitry Belyaev

Reputation: 2593

Well, the easy way is:

ok = file:write_file("output.txt", Li).

As you may see in http://www.erlang.org/doc/man/file.html , there are plenty of useful functions like file:read_file/1 that may shorten your program and at the same time make it a little quicker.

You see, the way you combine read data with accumulator is not perfect because it requires copying of Accum values, so the complexity of your readdata/2 function is N^2. Appending to the head of the list is the best way but of course you'd have to store lines as values of Acc and reverse it in the end.

And what about the length of the file? If it is huge and doesn't fit into memory, you'll have problems using even working with accumulator properly. The standard way in this case is to open both files, read some chunk of data and immediately write it to the output.

copy_file() ->
    {ok, In} = file:open("input", [read]),
    {ok, Out} = file:open("output", [write]),
    copy_file(In, Out),
    file:close(In),
    file:close(Out).

copy_file(In, Out) ->
    case file:read(In, 1024 * 64) of
        {ok, Data} ->
            ok = file:write(Out, Data),
            copy_file(In, Out);
        _ ->
            ok
    end.

I haven't tried the code, it may not compile, I just tried to show the basic idea.

Upvotes: 2

Related Questions