ファイルテスト演算子

ファイルへの書き込み処理などは予定どおり完了するとは限らない。 実際には書き込み権限がない場合などがあるため, 事前にファイルに関連する条件を調査することが重要である。 ファイルがあるかないか,読めるか,書けるか,空か,など 指定したファイルの状態を調べるための組み込み関数 test を利用してこれらの条件判定を行なう。。

testの用法

test は,

test(cmd, file1 [, file2])

の書式で用い,指定したファイル file1cmd の要件を満たすかどうかを返す。2つのファイルの 関係を調べるための cmd には第3引数 file2 を与える。

cmd働き
?r現在の実行uidで読めるか
?w現在の実行uidで書き込めるか
?x現在の実行uidで実行できるか
?o現在の実行uidが所有するファイルか
?G現在の実行gidと同じグループ所有か
?R実 uid で読めるか
?W実 uid で書き込めるか
?X実 uid で実行できるか
?O実 uid が所有するファイルか
?e存在するか
?s0バイトでないか(サイズが返る)
?f普通のファイルか
?dディレクトリか
?lシンボリックリンクか
?p名前付きパイプ(FIFO)か
?bブロックデバイスファイルか
?cキャラクタデバイスファイルか
?usetuidビットがセットされているか
?gsetgidビットがセットされているか
?kstickyビットがセットされているか
?M最終更新時刻を返す
?A最終アクセス時刻を返す
?Cinode時刻を返す
以下は第2,第3引数間の比較
?-両ファイルが同一か
?=両ファイルの最終更新時刻が等しいか
?>file1の 最終更新時刻がfile2より大きい(新しい)
?<file1の 最終更新時刻がfile2より小さい(古い)

testの利用例

実行結果を決まった名前のファイルに保存するようなコマンドを考える。 ここでは単純なものとしてたとえば, おみくじの結果をカレントディレクトリの fortune.txt に保存するプログラムを考える。

ft0.rb

#!/usr/bin/env ruby
# -*- coding: utf-8 -*-
#Foretune teller - version 0
ftune	= %w,大吉 吉 中吉 小吉 半吉 末吉 小凶 凶,
outfile	= "fortune.txt"
srand
result = ftune[rand(ftune.length)]
open(outfile, "w") do |f|
  f.printf("今日の運勢は %s\n", result)
end
STDERR.puts "#{outfile}を見よ。"

起動して,foretune.txt ファイルを確認してみる。

./ft0.rb
fortune.txtを見よ。
ls -l fortune.txt
-rw-r--r--  1 yuuji  wheel  26 Mar 14 07:58 fortune.txt
cat fortune.txt
今日の運勢は 中吉

もう一度実行すると上書きされる。

./ft0.rb
fortune.txtを見よ。
cat fortune.txt
今日の運勢は 大吉

せっかく大吉が出たので上書きされないよう chmod で書き込み禁止にする。

chmod -w fortune.txt
ls -l fortune.txt
-r--r--r--  1 yuuji  wheel  26 Mar 14 07:58 fortune.txt

この状態で実行するとプログラムはエラー終了する。

./ft0.rb
ft0.rb:8:in `initialize': Permission denied @ rb_sysopen - fortune.txt (Errno::EACCES)
        from ft0.rb:8:in `open'
        from ft0.rb:8:in `<main>'

以上の様子から,次のような問題があることが分かる。

これらの問題を解決するなら以下のような改良を施すことになるだろう。

上記2点の改良を施してみよ。

すべてのファイルに対しての処理

あるディレクトリ以下にあるすべてのファイルに対して特定の処理を 行なうことはしばしば必要となる。そのための手順の流れは 以下のようになる。

  1. あるディレクトリを開きすべてのファイルについて以下を繰り返す
  2. それがファイルなら特定の処理を行なう
  3. それがディレクトリならそのディレクトリに対してこの処理全体を行なう

特定の処理をメソッド化し,処理対象がディレクトリなら再帰処理を行なう。 以下のプログラムは,指定したディレクトリ (省略時はカレントディレクトリ)以下すべてのファイルに対して, それが通常ファイルならバックアップファイルを作成する。。

mkbak-r.rb

#!/usr/bin/env ruby
# -*- coding: utf-8 -*-
dir = ARGV.shift || "."

def cp(from, to, bufsize=8192)
  open(from, "r") do |src|
    open(to, "w") do |dest|
      while s = src.read(bufsize)
        dest.write s
      end
    end
  end
end

def bakdir(dir)
  ext = ".bak"
  Dir.foreach(dir) do |f|
    file = File.expand_path(f, dir)
    case f
    when ".", ".."              # カレントディレクトリと親ディレクトリ
      next                      # は飛ばす
    when %r,#{ext}$,		# それが既にバックアップファイルなら
      next  			# やはり飛ばす
    else
      if test(?d, file)         # ディレクトリなら
        bakdir(file)		# 再帰的に自分を呼ぶ
      elsif test(?f, file)      # 通常ファイルなら
        # バックアップファイル作成
        bak = File.expand_path(f+".bak", dir)
        File.unlink(bak) if test(?e, bak)
        cp(file, bak)
      end
    end
  end  
end

bakdir(dir)

本日の目次

yuuji@e.koeki-u.ac.jp