M.Hiroi's Home Page

お気楽 Erlang プログラミング入門

ファイル入出力


Copyright (C) 2011-2024 Makoto Hiroi
All rights reserved.

はじめに

今回は Erlang のファイル入出力について説明します。Erlang は「ポート (port)」というデータ型を介して外部と通信処理を行います。ファイルの入出力処理もポートを介して行われますが、モジュール io, file などに用意されている関数を使うと、ポートを意識しないで入出力処理を行うことができます。

●標準入出力

通常のファイルは、ファイルに対応するポートを生成しないとアクセスすることはできません。ただし、標準入出力は Erlang を起動した時から簡単に利用することができます。一般に、キーボードからの入力を「標準入力」、画面への出力を「標準出力」といいます。

データの入出力処理は標準入出力を使うと簡単です。モジュール io には標準入出力用の関数が用意されています。たとえば、io:write や io:format はデータを標準出力へ出力する関数でした。関数 io:read/1 は Erlang で取り扱うことができる「項」を入力することができます。

io:read(Prompt) => {ok, Data} | eof | {error, Reason}

引数 Prompt は画面に表示するプロンプトで、文字列かアトムで指定します。省略することはできません。read はファイルの終了を検出すると eof を返します。エラーが発生した場合は {error, Reason} を返します。

簡単な実行例を示しましょう。

> io:read(">> ").
>> 1234.
{ok,1234}
> io:read(">> ").
>> 1.2345.
{ok,1.2345}
> io:read(">> ").
>> foo.
{ok,foo}
> io:read(">> ").
>> [1,2,3,4].
{ok,[1,2,3,4]}
> io:read(">> ").
>> {a, b, c, d}.
{ok,{a,b,c,d}}
> io:read(">> ").
>> "hello, world".
{ok,"hello, world"}

データの終わりにはピリオドを入力してください。read は Erlang の構文規則にしたがって標準入力からデータを読み込み、それを項に変換して返します。

●get_line

標準入力から 1 行ずつ読み込む場合、関数 io:get_line/1 を使うと便利です。

io:get_line(Prompt) => Data | eof | {error, Reason}

引数 Prompt は画面に表示するプロンプトで、文字列かアトムで指定します。省略することはできません。

簡単な実行例を示します。

> io:get_line(">> ").
>> hello, world
"hello, world\n"

hello, world と入力してリターンキーを押すと、get_line は入力データを文字列 (リスト) にして返します。このとき、改行文字もいっしょに文字列に格納されます。また、get_line はファイルの終了を検出すると eof を返します。エラーが発生した場合は {error, Reason} を返します。

それでは簡単な例題として、入力をそのままエコーバックする関数 echo/0 を作ってみましょう。プログラムは次のようになります。

リスト : エコーバック

-module(iotest).
-export([echo/0]).

echo() -> 
    case io:get_line("> ") of
        "\n" -> ok;
        Line -> io:fwrite(Line), echo()
    end.

標準入力から get_line で 1 行読み込み、それを io:fwrite で標準出力へ出力します。fwrite は io:format と同じ働きをする関数です。第 1 引数に文字列を与えると、それをそのまま出力します。それから echo を再帰呼び出します。これで echo は無限ループになります。入力された文字列が "\n" の場合は処理を終了します。

簡単な実行例を示します。

> iotest:echo().
> hello, world
hello, world
> foo bar baz
foo bar baz
> 1234567890
1234567890
>
ok

終了する場合はリターンキーだけを押してください。最後の ok は echo/0 の返り値です。

●ファイルのオープンとクローズ

ファイルにアクセスする場合、次の 3 つの操作が基本になります。

  1. アクセスするファイルをオープンする
  2. 入出力関数を使ってファイルを読み書きする。
  3. ファイルをクローズする。

「ファイルをオープンする」とは、アクセスするファイルを指定して、それと 1 対 1に対応するポートを生成することです。入出力関数はオープンしたポートを経由してファイルにアクセスします。Erlang の場合、ファイルのオープンは関数 file:open/2 で行います。

file:open(Filename, Mode) => {ok, IoDevice} | {error, Reason}

open/2 は引数にファイル名 Filename とアクセスモード Mode を指定して、Filename で指定されたファイルに対応する IoDevice を生成して返します。Erlang ではファイルの入出力処理をプロセスで行っていて、open はそのファイルの入出力を行うプロセスの識別子 (Pid) を返します。このほかに「ファイルディスクプリタ」という IoDevice もありますが、本稿では触れません。

アクセスモード Mode はリストで、要素はアクセスモードを表すアトムになります。要素がひとつしかない場合、リストではなくアトムをそのまま指定してもかまいません。下表に主なアクセスモードを示します。

表 : アクセスモード
モード動作
read 読み込み (read) モード
write 書き出し (write) モード
append 追加 (append) モード
binary バイナリの指定

読み込みモードの場合、ファイルが存在しないとエラーになります。書き出しモードの場合、ファイルが存在すれば、そのファイルを大きさ 0 に切り詰めてからオープンします。追加モードの場合、ファイルの最後尾にデータを追加します。binary は取り扱うデータをバイナリデータに設定します。

なお、Windows では「テキストモード」と「バイナリモード」の区別がありますが、Erlang の基本はバイナリモードです。Erlang の場合、行単位で入出力を行う関数において改行の変換が行われます。

オープンしたファイルは必ずクローズしてください。この操作を行う関数が file:close/1 です。

file:close(IoDevice) => ok | {error, Reason}

●ファイルの読み込み

ファイルの読み込みはモジュール file に定義されている関数のほかに、モジュール io の関数でも第 1 引数に IoDevice を指定すると利用することができます。主な入力関数を以下に示します。

io:read(IoDevice, Prompt) => Result
io:get_line(IoDevice, Prompt) => Data | eof | {error, Reason}
file:read_line(IoDevice) => {ok, Data} | eof | {error, Reason}
file:read(IoDevice, Number) => {ok, Data} | eof | {error, Reason}
file:read_file(Filename) => {ok, Binary} | {error, Reason}

read_line/1 は IoDevice から1 行読み込みます。Windows の場合、改行コードの変換が行われます。file:read/2 は Number で指定した個数だけデータを読み込みます。返り値の Data は、open の Mode で binary を指定するとバイナリに、そうでなければリストになります。read_file/1 は Filename で指定したファイルを全部読み込み、データをバイナリに格納して返します。

簡単な例を示します。ファイル test.dat からデータを読み込みます。

foo
bar
baz
1234
5678

図 : test.dat の内容
> file:read_file("test.dat").
{ok,<<"foo\r\nbar\r\nbaz\r\n1234\r\n5678\r\n">>}
> {ok, F} = file:open("test.dat", read).
{ok,<...>}
> file:read_line(F).
{ok,"foo\n"}
> file:read_line(F).
{ok,"bar\n"}
> file:read_line(F).
{ok,"baz\n"}
> file:read_line(F).
{ok,"1234\n"}
> file:read_line(F).
{ok,"5678\n"}
> file:read_line(F).
eof
> file:close(F).
ok


read_file/1 を使って test.dat を読み込むと、データを格納したバイナリが返されます。M.Hiroi の実行環境は Windows なので、改行が \r\n で表されていることがわかります。次に、read_line/1 で 1 行ずつ読み込みます。open のモードで binary を指定していないので、データはリストに格納されて返されます。このとき、改行の変換が行われていることに注意してください。

次は open のモードで binary を指定してみましょう。

> {ok, F1} = file:open("test.dat", [read, binary]).
{ok,<...>}
> file:read_line(F1).
{ok,<<"foo\n">>}
> file:read_line(F1).
{ok,<<"bar\n">>}
> file:read_line(F1).
{ok,<<"baz\n">>}
> file:read_line(F1).
{ok,<<"1234\n">>}
> file:read_line(F1).
{ok,<<"5678\n">>}
> file:read_line(F1).
eof
> file:close(F1).
ok

この場合、読み込んだデータはバイナリに格納されて返されます。行単位の入力なので、改行の変換も行われていることに注意してください。

次は file:read/2 を使って 6 バイトずつデータを読み込んでみましょう。

> {ok, F2} = file:open("test.dat", read).
{ok,<...>}
> file:read(F2, 6).
{ok,"foo\r\nb"}
> file:read(F2, 6).
{ok,"ar\r\nba"}
> file:read(F2, 6).
{ok,"z\r\n123"}
> file:read(F2, 6).
{ok,"4\r\n567"}
> file:read(F2, 6).
{ok,"8\r\n"}
> file:read(F2, 6).
eof
> file:close(F2).
ok

open のモードで binary を指定していないので、読み込んだデータはリストに格納されて返されます。file:read/2 では改行の変換が行われないことに注意してください。また、ファイルの最後では、データが足りなくて指定したサイズに満たない場合もあります。

それでは簡単な例題として、ファイルの内容を画面へ出力する関数 cat/1 を作ってみましょう。プログラムは次のようになります。

リスト : ファイルの表示

cat_sub(In) ->
    case file:read_line(In) of
        eof -> ok;
        {ok, Line} -> io:fwrite(Line), cat_sub(In)
    end.

cat(Filename) ->
    case file:open(Filename, read) of
        {error, Reason} -> io:format('file open error ~w~n', [Reason]);
        {ok, In} -> cat_sub(In), file:close(In)
    end.

cat/1 の引数 Filename はファイル名を表す文字列です。ファイル Filename をオープンして IoDevice を変数 In にセットします。ファイルの表示は関数 cat_sub/1 で行います。read_line/1 で 1 行読み込み、それを fwrite/1 で標準出力へ出力します。それから cat_sub/1 を再帰呼び出しします。ファイルの終了を検出したら処理を終了します。あとは cat/1 に戻って、close でファイルを閉じるだけです。

簡単な実行例を示します。

> iotest:cat("test.dat").
foo
bar
baz
1234
5678
ok

●ファイルの書き込み

データをファイルに書き込むには、ファイルを write モードでオープンします。このとき、注意事項が一つあります。既に同じ名前のファイルが存在している場合は、そのファイルの長さを 0 に切り詰めてからデータを書き込みます。既存のファイルは内容が破壊されることに注意してください。

モジュール io と file に定義されている主な出力関数を以下に示します。

io:nl(IoDevice) => ok
io:write(IoDevice, Term) => ok
io:format(IoDevice, Format, Args) => ok
file:write(IoDevice, Data) => ok | {error, Reason}
file:write_file(Filename, Data) => ok | {error, Reason}

Data はリストまたはバイナリで、要素の値はバイナリデータ (0 - 255) になります。なお、リストの要素はバイナリデータを格納したリストまたはバイナリでもかまいません。

簡単な例を示します。

> {ok, Out1} = file:open("test.out", write).
{ok,<...>}
> file:write(Out1, "foo").
ok
> file:write(Out1, "bar").
ok
> file:write(Out1, "baz").
ok
> file:write(Out1, ["1234", "5678"]).
ok
> file:close(Out1).
ok
> {ok, Data} = file:read_file("test.out").
{ok,<<"foobarbaz12345678">>}
> file:write_file("test1.out", Data).
ok
> file:read_file("test1.out").
{ok,<<"foobarbaz12345678">>}

ファイル test.out を write モードでオープンします。write でデータを書き込みますが、このときデータにリストを渡すと、格納された文字列をファイルに書き込みます。test.out をクローズしたあと、read_file/1 で test.out を読み込むと、データが書き込まれていることがわかります。それから、そのデータを write_file/1 でファイル test1.out に書き込みます。read_file/1 で test1.out を読み込むと、正常に書き込まれていることがわかります。

このほかにも、Erlang にはファイルを操作する便利な関数が多数用意されています。詳しい説明は Erlang のリファレンスマニュアル Erlang -- io, Erlang -- file をお読みください。


初出 2011 年 11 月 20 日
改訂 2019 年 1 月 6 日, 2024 年 11 月 1 日