読者です 読者をやめる 読者になる 読者になる

Minecraftとタートルと僕

PCゲームMinecraftのMOD「ComputerCraft」の情報を集めたニッチなブログです。

FS API を使いこなそう(4): fs.open()でファイルの読み書き

チュートリアル Lua ComputerCraft Minecraft API CC1.6 CC1.58

これまでのお話

前回までにComputerCraftでファイルを扱うための基礎知識を紹介しました。

今回は、FS APIのfs.open()関数の説明を通して実際にファイルを読み書きしてみましょう。

まずは、fs.open()関数の仕様について。

fs.open()関数

仕様

  • fs.open(file_path, mode)
    • 指定されたモード(mode)でファイル(file_path)を開き、ファイルハンドルを返す。今後はそのファイルハンドルを通してファイルを読み書きする。
    • 引数1(文字列): ファイルパス
    • 引数2(文字列): モードの種類については後述。基本的には"r"、"w"、"a"の3つを理解して使いこなせばよいかと(極論)
    • 返値(テーブル): ファイルハンドル。Luaではそのファイルを取り扱うための関数が入ったテーブル。

テキストファイルを読み書きするためのモード

モード 概要
"r" ファイルを読み専用(read)モードで開く。
"w" ファイルを新規書き込み(write)モードで開く。
既存ファイルの中身を消して新規に書き込むことに注意。
"a" ファイルを追加書き込み(add)モードで開く。
既存ファイルの最後に記述を追加していく。
  • 上記3つのモードは、テキストファイル(人間が読めるファイル)を扱うためのモードであり、「テキストモード」と呼ばれます。
  • それに対して画像ファイルや音声ファイルなど、テキストファイル以外のファイルをバイナリファイルと呼び、バイナリファイルを取り扱うモードを「バイナリモード」と呼びます。
    • バイナリファイルを開いて中を覗いても、一般人にとっては意味不明な文字(数値)しか表示されません。
    • 上記3つのモードに"b"をつけた。"rb"、"wb"、"ab"がバイナリモードでファイルを開くための記号です。

どのモードを指定するかによって、ファイルハンドルで用意される関数が変わってきます。

たとえば"r"モードでファイルを開くと、返ってくるファイルハンドル(テーブル)にはファイルを読むための関数が入っており、書きこむための関数が含まれていません。

ファイルハンドルって?

一言でいうならば、そのファイルを手軽に取り扱うためにシステムが用意したインタフェースです。

たとえば車の操作で言うと、道路を右折するためにはタイヤを適切なタイミングで適切な角度に切り替えなければなりませんが、ハンドルというシンプルなインタフェースを使うことによって、運転の素人でも比較的簡単に曲がることができます。

(ESTIMAのハンドル / 柏翰 / ポーハン / POHAN)

ファイルの操作も同様です。実際にファイルを読んだり書いたりする具体的な動作をプログラミングするとなるとかなり大変なことなのですが、ファイルハンドルというインタフェースを使うことによって簡単に操作できるのです。

ComputerCraftのfs.open()関数によって手に入るファイルハンドル、その実体はただのテーブルです。中には開いたファイルを操作するための関数が入っているので、その関数を使うだけで細かいことに囚われることなくやりたいことに集中できます。

各モードでどのようなファイルハンドルができるのか

"r" 読み専用モードで開いたとき

fs.open()で指定したファイルが存在しないときには読み専用モードでファイルを開くことはできません。

ここでは、myfileというテキストファイルがすでに存在しているとします。

使用例

# lua interactive mode (> lua)
lua> fh = fs.open("myfile", "r")
lua> fh
table: 73e2e8da
lua> for k,v in pairs(fh) do print(k) end
readLine
readAll
close

どうやら読み専用("r")モードでは、3つの関数が入っているようです。それぞれ簡単に使い方を説明しましょう。

readLine(): ファイルから1行読み出す

  • readLine()
    • 開いたファイルから1行分の文字列を取り出します。実行するたびに、2行目・3行目と次の行の文字列が返ってきます。
    • なお、最終行まで行ったらnilが返ってきます。
    • 返値(文字列): 1行分の文字列

使い方

# "myfile"の中身を全て画面に表示するプログラム
local fh = fs.open("myfile", "r")

repeat
  local str = fh.readLine()
  print(str)
until not str

readAll(): ファイルから全ての文字を読み出す

  • readAll()
    • 開いたファイルから改行も含めて一気に全ての文字列を取り出します。
    • なお、一度実行して読み込んだ後は、再度実行しても長さ0の文字列しか返って来ません。
    • 返値(文字列): 改行も含めて全文字列

使い方

# "myfile"の中身を全て画面に表示するプログラム
local fh = fs.open("myfile", "r")

print(fh.readAll())

close(): 開いたファイルを閉じる

  • close()
    • 開いたファイルを閉じる。「作業が終わったら閉じる」というのはファイル取り扱いのマナーです。
    • ファイルを読む"r"モードではさほど問題になりませんが、書き込む"w","a"モードで閉じ忘れると面倒なことに!? 忘れないように。

ドアは開いたら閉じますよね? それと同じです。

(Door Handle / Sean MacEntee)

local fh = fs.open("myfile", "r")

# いろいろとファイルを取り扱ったあと
・・・

# 終了! マナーですよ!!
fh.close()

"w" 新規書き込みモードで開いたとき

fs.open()で指定したファイルがすでに存在しているとき、そのファイルの中身を消して新しく上書きしてしまうことに注意してください。

# このミスをすると痛い、痛すぎるのですよ・・・。

# lua interaction mode (> lua)
lua> fh = fs.open("myfile", "w")
lua> fh
table: 2d4e490d
lua> for k,v in pairs(fh) do print(k) end
write
writeLine
flush
close

どうやら新規書き込み("w")モードでは、4つの関数が入っているようです。それぞれ簡単に使い方を説明しましょう。

write(): ファイルに文字列を書き込む

  • write(str)
    • ファイルに文字列を書き込む
    • 引数(文字列): 書き込む文字列
    • 返値なし

writeLine(): ファイルに文字列を書き込み、すぐに改行する

  • writeLine(str)
    • ファイルに文字列を書き込み、すぐその後に改行する(改行コードを埋め込む)
    • 引数(文字列): 書き込む文字列
    • 返値なし

ここで注意しなくてはならないのは、上記の関数で文字列を書き込んでも、実際には以下の関数を使わないとファイルに反映されないということです*1

flush(): 書き込んだ内容を実際のファイルに反映させる(実際に書き込む)

  • flush()
    • write()やwriteLine()で書き込んだ内容を実際のファイルに書き込む。逆に言うと、この関数を実行するまで実際には書き込まれない。

close(): 開いたファイルを閉じる

  • close()
    • 開いたら閉じる。マナーですよ! なお、まだファイルに反映させていない書き込みが残っていたら書き込んでくれます。つまりflush()忘れていてもclose()さえしておけば(ある程度は)大丈夫!

使用例

local fh = fs.open("newfile", "w")

fh.writeLine("Hello World!")

fh.flush() -- ここまでの内容を一旦書き込み

fh.write("H")
fh.write("e")
fh.write("l")
fh.write("l")
fh.write("o")

fh.close() -- 書き込み終了!

新しく書き込まれたファイル「newfile」の内容は次の通りです。

Hello World!
Hello

"a" 追加書き込みモードで開いたとき

fs.open()で指定したファイルがすでに存在しているとき、そのファイルの末尾に続けて書き込まれます。

指定したファイルが存在しなければ新しくファイルを作ってそこに書き込みます。

# lua interaction mode (> lua)
lua> fh = fs.open("newfile", "a")
lua> fh
table: 6a2cbdcd
lua> for k,v in pairs(fh) do print(k) end
write
writeLine
flush
close

用意される関数は、新規書き込みモード"w"と同じです。そのため説明を省いて使用例だけ。

-- ファイル「newfile」はさきほどの"w"モードで作ったもの
local fh = fs.open("newfile", "a")
fh.writeLine(" Wrold 2")
fh.write("Hello World 3")
fh.close()

追加書き込みされたファイル「newfile」の内容は次のようになります。

Hello World!
Hello World2
Hello World3

(補足)バイナリモードの場合、"rb"、"wb"、"ab"

バイナリファイルを直接取り扱うことはほとんどないと思いますが、念のため補足として。

バイナリファイルなので、改行コードなんて特別扱いはしません。すべては1バイトずつのデータ!

"rb" バイナリファイル読み専用モード

  • read()
    • バイナリファイルから1バイト分のデータを読み込みます。実行するたびに次々と1バイトずつデータを読み込みます。
    • 返値(数値):

"wb", "ab" バイナリファイル新規書き込み(追加書き込み)モード

  • write(num)
    • 引数(数値): ファイルに書き込みたい1バイト(byte)の数値
    • 返値なし

おわりに

次回は、これまで紹介したFS APIの関数を使って便利なプログラムを紹介しましょう。

とりあえず今のところ案として持っているのは次あたり。他にもリクエストがあればお知らせください。

  • 依存するライブラリ(APIファイル)をpastebinから自動的にダウンロード&インストールしてくれるプログラム
  • タートルが何か動作を行うたびに、動作のログをとっておくプログラム

*1:これは、ファイルを読むことより書き込むことのほうが作業的な負荷が高いためです。ちまちまファイルに書き込むなんてまどろっこしいことしないで、ある程度(バッファに)貯めておいてから一気に書き込むという効率を重視した仕様です。