例外処理

予期せぬ結果が生じたときには 例外 が発生される。

例外発生

文法的にはエラーのない以下のプログラムを動かしてみよ。

openerr.rb

#!/usr/bin/env ruby
# coding: euc-jp

file = ARGV.shift || "nonexistent.txt"
open(file, "r") do |f|
  while line = f.gets
    print line
  end
end

実行すると例外が発生する。

./openerr.rb:4:in `initialize': No such file or directory - nonexistent.txt (Errno::ENOENT)

begin〜rescue〜end

おみくじプログラムの例に戻る。 改良すべき2つの点,

にはそれぞれ落し穴がある。

バックアップファイルへの移動ができるかどうかを確かめようとして バックアップファイルの存在性と書き込み可能性を確かめたくなるが, 実際にはこれは「バックアップファイルを作れるかどうか」には 関係ない。以下の実験で確認できる。

mkdir dirtest
cd dirtest
touch foo foo.bak
chmod -w foo.bak          (書き込み禁止にする)
echo hoge > foo.bak
zsh: permission denied: foo.bak
(エラーになる)
mv foo foo.bak
override r--r--r--  yuuji/wheel for foo.bak? n
(警告が出る。ここではnと答える)
irb
irb> File.rename("foo", "foo.bak")
=> 0
irb> exit
ls -lF
total 0
-rw-r--r--  1 yuuji  wheel  0 Jun  8 13:12 foo.bak

ファイルをrenameする場合は, 移動先ファイルを修正するのではないので移動先ファイルのパーミッションは 関係ない。元のファイルを消してそのファイルを作る権利があるかが 意味を持つので,ディレクトリに書き込み権があるかが意味を持つ。

touch foo foo.bak
chmod -w .             (ディレクトリを書き込み禁止にする)
irb
irb> File.rename("foo", "foo.bak")
Errno::EACCES: Permission denied - foo or foo.bak
        from (irb):1:in `rename'
        from (irb):1

これは,ファイルを新規作成するときも同様の要件となる。

以上のことから,おみくじの例での改良2点,

は,ディレクトリへの書き込み権限を検査する必要があることが分かる。

ft1.rb

#!/usr/bin/env ruby
# coding: euc-jp
#Foretune teller - version 1
ftune	= %w,大吉 吉 中吉 小吉 半吉 末吉 小凶 凶,
outfile	= "fortune.txt"
srand
result = ftune[rand(ftune.length)]

if test(?w, ".") then
  if test(?s, outfile) then
    ext = File.extname(outfile)
    bak = File.basename(outfile, ext)+".bak"
    File.rename(outfile, bak)
  end
  open(outfile, "w") do |f|
    f.printf("今日の運勢は %s\n", result)
  end
  STDERR.puts "#{outfile}を見よ。"
end

しかしこれでも不十分で,ファイルシステムが溢れるなど, 書き込みに失敗する要因は他にいくらでもあり,それらに対する 事前予想を if で書き連ねてもプログラムの本筋が見づらくなる。

このような場合begin〜rescue〜end 構文を用いる。 エラーが起きない理想的な場合の処理のみをまず書き,エラーが起きた 場合の処理をまとめて別箇所に書くことで,処理の流れがはっきりし, エラーが起きた場合の対処ももれなく書くことが容易になる。

ft2.rb

#!/usr/bin/env ruby
# coding: euc-jp
#Foretune teller - version 2
ftune	= %w,大吉 吉 中吉 小吉 半吉 末吉 小凶 凶,
outfile	= "fortune.txt"
srand
result = ftune[rand(ftune.length)]

begin
  if test(?s, outfile) then
    ext = File.extname(outfile)
    bak = File.basename(outfile, ext)+".bak"
   # File.rename(outfile, bak)
  end
  open(outfile, "w") do |f|
    f.printf("今日の運勢は %s\n", result)
  end
  STDERR.puts "#{outfile}を見よ。"
rescue
  abort(<<MSG)
書き込みに失敗しました。ディレクトリへの書き込み権限を確認して下さい。
MSG
end

この場合は例外発生時に復旧せず,異常終了している。 そのためのメソッドが abort である。


本日の目次

yuuji@e.koeki-u.ac.jp