ファイルとディレクトリ

データの処理を行なうことはコンピュータに求められる重要な仕事だが, そのデータの入れ物であるファイルの処理も同様に重要である。 たとえばゲームを作る場合にハイスコアを記録したい場合を考える。 データの入出力の方法も大事だが,保存するファイル名をどう決めるか, 書き込みできる場所をどうやって探すか,などについても知る必要がある。

ここではファイルやディレクトリに関する操作をプログラムから行なうための メソッドや,そのときに注意すべきシステム上の事項について説明を進める。

ファイル

ファイル名操作やファイルそのものの属性を操作したりするメソッドは, File クラスに集まっている。

クラスメソッド

以下のものはクラスメソッドであり,オブジェクトの 確保なしにどのタイミングでも使える。

ファイル入出力時の特殊なメソッド

主なメソッド

単一ファイルを何度も読み書きする場合は,その都度閉じたり開いたりを 繰り返すのではなく,対象ファイルのうち次に読み書きする位置(ファイルポインタ) を移動したり,書き込んだ内容を確実にディスクに書き出す処理が必要になる。 以下のメソッドは IOクラスの非クラスメソッドである。既に open しているIOオブジェクトから利用する。

出力のバッファリング

プログラムから出力を行なう場合,print などの書き込み操作をしても すぐに実際に対象デバイスにデータが書き込まれるとは限らない。 効率を上げるため一定量を溜めてからまとめて書き出す処理が行なわれる。 このため,目の前の短期的な応答性が求められるプログラムでは, 出力先のバッファリングを制御する必要がある。

バッファリングの有無による違いを確認するために, Ruby1.8での挙動を用いた例を示す。 システムの標準的な設定として, 標準出力はデフォルトでバッファリングされ, 標準エラー出力はデフォルトでバッファリングされない。 他の言語処理系を使う場合にもこのような注意が必要な点は同じである。

Ruby1.8用の以下のプログラムは, 標準出力から "O" を,標準エラー出力から "E" を1字ずつ交互に出力する。

oeoe.rb

#!/usr/bin/env ruby18

3.times do		# 全体を3回繰り返す
  12.times do		# STDOUT/STDERR 交互出力を12回繰り返す
    STDOUT.print "O"
    STDERR.print "E"
  end
  STDOUT.puts ""	# 12回終わったらSTDOUTに改行出力
end
STDOUT.puts "Done."

このプログラムでは,交互出力を12回行なったあと標準出力に改行文字を 出力している。NetBSD6/amd64で実行すると以下のようになった。

oeoe.rb
EEEEEEEEEEEEOOOOOOOOOOOO
EEEEEEEEEEEEOOOOOOOOOOOO
EEEEEEEEEEEEOOOOOOOOOOOO
Done.

この例から,標準出力は改行文字までがまとめて出力されていることが分かる。 プロセスと通信を行なう場合などで,出力先にデータを即時に送りたい場合は flush メソッドで書き出しバッファを一掃するか,常に即時送りをした場合は sync=true で出力同期モードに変える。flush を用いるように変えたプログラムと実行例を示す。

oeflush.rb

#!/usr/bin/env ruby18

3.times do		# 全体を3回繰り返す
  12.times do		# STDOUT/STDERR 交互出力を12回繰り返す
    STDOUT.print "O"
    STDERR.print "E"
    STDOUT.flush
  end
  STDOUT.puts ""	# 12回終わったらSTDOUTに改行出力
end
STDOUT.puts "Done."
oeflush.rb
EOEOEOEOEOEOEOEOEOEOEOEO
EOEOEOEOEOEOEOEOEOEOEOEO
EOEOEOEOEOEOEOEOEOEOEOEO
Done.

ただし,即時書き込みはプログラムの動作性能を落とす可能性があることや, 実際に書き出されるかどうかは受け取り側の状況によることに注意する必要がある。

ディレクトリ

ファイルはディレクトリに格納する。 保存ファイルを書き込める場所を探したり, 既存のファイルを特定のディレクトリから探したりするためには ディレクトリを操作するためのメソッドの集まった Dir クラスを利用する。

ファイルの消えるタイミング

ファイルはリンクカウント1のときの名前が消えても,そのファイルへの アクセスがある限りファイルシステムの領域を占め続ける。 プログラム中で利用する一時ファイルを作成後,すぐに unlink しても close するまではアクセスし続けられる。

unlinktest.rb

#!/usr/bin/env ruby
# -*- coding: utf-8 -*-
tmpfile = ARGV.shift || "00TEMPFILE"
tmpdir  = File.dirname(tmpfile)

STDERR.puts "最初のこのディレクトリの使用量:"
system "du -sk #{tmpdir}"
open(tmpfile, "w+") do |tf|
  1.upto(10) do |lineno|
    tf.printf("これは%02d行目\n", lineno)
  end
  tf.puts "-"*1024*1024         # 1MBのダミーデータ
  tf.flush                      # バッファを書き込み
  tf.rewind                     # ファイルポインタを先頭に
  system "ls -lF #{tmpfile}"
  STDERR.puts "ファイル作成直後のこのディレクトリの使用量:"
  system "du -sk #{tmpdir}"
  STDERR.puts "改行を押すとこのファイルを消します。"
  STDIN.gets
  File.unlink(tmpfile)
  system "ls -lF #{tmpfile}"
  STDERR.puts "closeする前のこのディレクトリの使用量:"
  system "du -sk #{tmpdir}"
  STDERR.puts "1行目から読みます。"
  STDERR.puts "別の端末でlsして#{tmpfile}がないことを確認しましょう。"
  10.times do |l|
    print tf.gets
    sleep 1
  end
end
STDERR.puts "closeされたあとのこのディレクトリの使用量:"
system "du -sk #{tmpdir}"

このプログラムを実行すると以下の結果が得られる。

./unlinktest.rb
最初のこのディレクトリの使用量:
116     .
-rw-r--r--  1 yuuji  wheel  1048757 Mar 13 16:00 00TEMPFILE
ファイル作成直後のこのディレクトリの使用量:
1156    .
改行を押すとこのファイルを消します。

ls: 00TEMPFILE: No such file or directory
closeする前のこのディレクトリの使用量:
1156    .
1行目から読みます。
別の端末でlsして00TEMPFILEがないことを確認しましょう。
これは01行目
これは02行目
これは03行目
これは04行目
これは05行目
これは06行目
これは07行目
これは08行目
これは09行目
これは10行目
closeされたあとのこのディレクトリの使用量:
116     .

4つの数字が出されているタイミングは,

  1. データファイル作成前 (116)
  2. データファイル作成直後 (1156)
  3. データファイル削除後でopenしている間 (1156)
  4. データファイル作成後でcloseしたあと (116)

のとおりで,3のときにはディレクトリエントリからデータファイルの 名前がなくなっているにも関らず,ディスク上に確保された領域が残っている 状態であることが分かる

ユーザに見せる必要がなく, プログラム実行終了に不要となるファイルはopen直後に unlink するとよい。


本日の目次

yuuji@e.koeki-u.ac.jp