Tcl – ファイルの入出力(6) ファイル操作中のエラーを捕捉する

ファイルのエラー処理

これまでファイルの入出力(x)で紹介したファイルを開く操作はファイルオープンする際のエラーを考慮していませんでした。

ファイルを開く際に発生するエラー(ファイルの有無など)をcatchコマンドまたはfile existsコマンド使って捕捉し、エラー処理を行う方法を2つ紹介します。

※ファイルは前回の、str.txt を使用します。

[str.txt]

Hello! Hello!
Good morning!
Bye bye!
Have a nice day!
こんにちわ
おはようございます。
バイバイ
よい1日を!

ファイルの存在を確認する – file exists

ファイルを書き込みモードで開く方法として、これまで紹介したやり方は、openコマンドの引数に w または a を指定するものでした。

この指定方法では、存在しないファイルを指定すると新しいファイルを作成して処理を続行します。

ファイルが存在しない場合は処理を中止したり、存在しない為、新規で作成する旨の通知を行いたいこともあるかと思います。

また、ファイルを読み込む場合も、指定したファイルが存在しない場合には、メッセージを表示したいこともあると思います。

そういう時は、fileコマンドのexistsオプションでファイルの有無を確認できます。

[書式]

file exists name

ファイル名 name が存在し、現在のユーザーがそれに続くディレクトリに対する検索権限を持っている場合は1を返し、それ以外の場合は0を返します。

例えば、ファイルの入出力(3)で使用した、nl.tcl で、存在しないファイルを指定した場合、以下のエラーメッセージを画面に出力します。

$ ./nl.tcl aaa.txt
couldn't open "aaa.txt": no such file or directory
    while executing
"open $fname r"
    invoked from within
"set fid [open $fname r]"
    (file "./nl.tcl" line 10)

出力されたメッセージの内容を見るとエラーが発生したプログラムコードの箇所が表示されています。

Tclが出力するこのようなエラーメッセージを使用者には見せたくないという事もあると思います。

このエラーメッセージを画面に表示させなくするには次のサンプルのようにします。

以下のサンプルは file exists を使用して上記のエラーメッセージを表示させずに、「aaa.txt はありません」と表示するようにしています。

[サンプル] nl2.tcl

#!/bin/sh
# the next line restarts using tclsh \
exec tclsh "$0" "$@"

# 各行の先頭に行番号を付加して画面に出力する。

set fname [lindex $argv 0]    ;# ファイル名の取得

# ファイルの有無を確認後に読み込む。

if {[file exists $fname] == 1} {
    set fid [open $fname r]
    while {[gets $fid line] >= 0} {
        lappend strlst $line
    }
    close $fid

} else {
    puts "$fname はありません"
    exit  ;# プログラムの終了
}

# 各行の先頭に行番号を付ける。

set n 1    ;# 行番号
foreach tmp $strlst {
    puts [format "%03d : %s" $n $tmp]
    incr n
}

[実行例]

$ ./nl2.tcl aaa.txt
aaa.txt はありません

$ ./nl2.tcl str.txt
001 : Hello! Hello!
002 : Good morning!
003 : Bye bye!
004 : Have a nice day!
005 : こんにちわ
006 : おはようございます。
007 : バイバイ
008 : よい1日を!

エラーを捕捉する – catch

catchコマンドは、引数の数が違っていたり、Tclコマンドやプロシージャを実行した際に発生するエラーを捕捉する為に使います。

エラーを捕捉する事で、プログラムの異常終了や中断するのを防ぎます。

[書式]

catch script ?resultVarName? ?optionsVarName?

第1引数のscriptは、Tclコマンドやプロシージャを指定します。
第2引数のresultVarNamescriptの実行結果を保存する変数名を指定します。

scriptでエラーが発生すると、catchコマンドは、例外的なリターンコードを示す0以外の整数値を返します。正常時は、0 (TCL_OK)を返します。

例外的なリターンコードとして以下が定義されています。

  1. TCL_ERROR
  2. TCL_RETURN
  3. TCL_BREAK
  4. TCL_CONTINUE

resultVarNameが指定されている場合、scriptでエラーが発生すると、resultVarNameには、エラーメッセージが格納されます。正常時は、scriptから返された値が格納されます。

※optionsVarNameの使い方については、catch, return のマニュアルを参照してください。

catchコマンドを使用してエラーを捕捉するには以下のようにします。

[サンプル]] nl3.tcl

#!/bin/sh
# the next line restarts using tclsh \
exec tclsh "$0" "$@"

# 各行の先頭に行番号を付加して画面に出力する。

set fname [lindex $argv 0]    ;# ファイル名の取得

# ファイルの読み込み。

if {[catch {open $fname r} fid]} {
    puts stderr "$fname がオープンできません"
    exit  ;# プログラムの終了
} else {
    while {[gets $fid line] >= 0} {
        lappend strlst $line
    }
    close $fid
}

# 各行の先頭に行番号を付ける。

set n 1    ;# 行番号
foreach tmp $strlst {
    puts [format "%03d : %s" $n $tmp]
    incr n
}

[実行例]

$ ./nl3.tcl aaa.txt
aaa.txt がオープンできません

$ ./nl3.tcl str.txt
001 : Hello! Hello!
002 : Good morning!
003 : Bye bye!
004 : Have a nice day!
005 : こんにちわ
006 : おはようございます。
007 : バイバイ
008 : よい1日を!

上のサンプルはTclのエラーを出力させずに「aaa.txt がオープンできません」と表示するようにしています。

catch {open $fname r} fid

エラー発生時、第2引数の fid には、エラーメッセージが格納されますが、正常時は、ファイル識別子が格納されます。

上のサンプルでは、正常時のみ変数fidを使用しています。

puts stderr "$fname がオープンできません"

標準出力ではなく標準エラーに出力するようにしています。

これには理由がありまして、例えば、ファイルの内容や処理結果を標準出力へ出力するようなプログラムがあるとします。これをリダイレクションの機能を使って別のファイルへコピーしたとします。

例.

$ ./xxxxx.tcl > tmp.txt

エラーメッセージを標準出力へ出力するようにしていると、コピー中にエラーが発生した場合、そのメッセージも一緒にコピーされてしまいます。

エラーメッセージを標準エラーに出力するようにしていると、エラーメッセージは画面に出力されるので、一緒にコピーされることを防ぐことができます。

catchコマンド及びTclで捕捉したエラーについて

catchコマンドで捕捉したエラーは第2引数に格納されます。Tclで捕捉したエラーはTclのグロ-バル変数のerrorInfoerrorCodeに格納されています。

メッセージの違いを確認する為に、nl3.tcl のメッセージを出力する部分を以下のように書き換えて実行してみます。

puts stderr "$fname がオープンできません"
puts stderr "fid  : $fid"
puts stderr "info : $errorInfo"
puts stderr "code : $errorCode"

[実行例]

$ ./nl3.tcl aaa.txt
aaa.txt がオープンできません
fid  : couldn't open "aaa.txt": no such file or directory
info : couldn't open "aaa.txt": no such file or directory
    while executing
"open $fname r"
code : POSIX ENOENT {no such file or directory}

「aaa.txt がオープンできません」だけでは分からないので以下のように出力するようにしてもいいかも知れません。

puts stderr "$fname がオープンできません\n$fid"

[実行例]

$ ./nl3.tcl aaa.txt
aaa.txt がオープンできません
couldn't open "aaa.txt": no such file or directory

コメント

タイトルとURLをコピーしました