IDLメモ 入出力

ファイルの読み書き

ファイルを変数に結合する (ランダムアクセス) (assoc)

assoc関数を使うと、ファイルを変数に結合し、変数としてファイルを読み書きすることができる。この結果、ファイルのランダムアクセスが簡単に行える。また、メモリーに収まりきらない巨大なファイルを扱うときにも便利。assoc関数に与える配列のサイズは、2のべきにしたほうが高速になる(512, 1024, 2048バイトなど)。compressキーワード利用時は、ファイルからの読み出しのみ行える。shmmapプロシージャでも似たようなことができる。

IDL> openr,lun,'filename',/GET_LUN
IDL> a = assoc(lun, bytarr(1024,/nozero)) ; 以後、a[0]が最初の1024バイト、a[1]が次の1024バイトというように参照できる
IDL> help,a
A BYTE = File<filename> Array[1024]
IDL> help,a[10] ; ファイル先頭から 1024 * 10バイト目から1024バイト
<Expression> BYTE = Array[1024]
IDL> help,a[0,0] ; ファイル先頭のバイト
<Expression> BYTE = 31
IDL> help,a[32,4] ; ファイル先頭から 1024 * 32 + 4 バイト目
<Expression> BYTE = 182
IDL> b = assoc(lun, bytarr(256,256)) ; 一つのファイルを同時に複数の変数に結合することもできる
IDL> tvscl,b[6] ; ファイル先頭から 256 * 256 * 6 バイト目から 256 * 256 バイトを 256 * 256 ピクセルのイメージとして画面に表示する
IDL> free_lun,lun

gzipで圧縮されたファイルを読み書きする (compressキーワード)

openr/openw/openuプロシージャでファイルを開くとき、compressキーワードをつけると、gzipで圧縮されたファイルを読み書きできる。マニュアルにあるとおり、追記モード(append)は併用不可。また、マニュアルには記述がないが、Fortran書式なし入出力(アンフォーマット)データ(r77_unformatted)の読み書きやassocでの書き出しも利用不可。point_lunプロシージャの利用にも制限がある。

Fortran書式なし入出力(アンフォーマット)データを読み書きする (f77_unformattedキーワード)

openr/openw/openuプロシージャでファイルを開くとき、f77_unformattedキーワードをつけると、Fortran書式なし入出力(form="unformatted")データを読み書きできる。開いた後は、readu/writeuプロシージャで読み書きする。

スーパーコンピュータやSPARCワークステーション等、別のアーキテクチャの計算機で出力したデータを扱うときはエンディアンに注意する。ファイルが壊れていないはずなのに "Corrupted f77 unformatted file detected." というエラーが出るときはエンディアンの不一致が原因の可能性もある。open時にswap_endianキーワード (状況によってはswap_if_little_endian, swap_if_big_endian キーワード)を指定すると、エンディアンを変換してくれる。

2GBを超える可変長レコードを取り扱う場合、ファイル中のレコードマーカーを8バイトで出力するようにしておけば、IDLで自動判別して読むことができる。例えば GFortran ならコンパイル時に -frecord-marker=8 をつけることで、IBM XL Fortran では実行時オプションに uwidth=64 をつけることでレコードマーカーが8バイトになる。

レコードマーカーが4バイトの処理系で2GB超の (正確には2,147,483,639 ( = 231-9)バイトを超える)レコードをUnformattedで書き出すと、レコードは複数の2,147,483,639バイト以下のサブレコードに分割されて記録される。IDLはサブレコードの読み込みには対応しておらず、読み込もうとすると "Corrupted f77 unformatted file detected." というエラーメッセージが出る。【以下補足】サブレコードに分割されている場合、長さフィールドはサブレコード長の2の補数表現(符号ビット1)となる。ただし、最初のサブレコードの終端長さフィールド・最後のサブレコードの先頭長さフィールドだけはサブレコード長をそのまま記載する(符号ビット0)。つまり、先頭長さフィールドのバイト列が、0x09 0x00 0x00 0x80 (little endianの場合)、0x80 0x00 0x00 0x09 (big endianの場合)、あるいはこれ以外の値でも符号ビットが1になっていたら、このレコードはサブレコードに分割されていることを示す。

IDL でアンフォーマット形式で書き出すときはレコードマーカーは常に4バイトになり、サブレコード分割にも対応していないので、2GB以上のレコードを取り扱うことができない。2GB以上のレコードでも一見エラー無しで書けてしまうが、レコード長が正しく記録されていないので後でアンフォーマット形式として読めなくなる。

Fortran 2003で追加されたストリーム出入力 (access="stream",form="unformatted") で出力されたファイルをIDLで開くときには、f77_unformattedキーワード は不要。この場合、2GB以上のレコードでも気にせず読める。ただし、配列の長さを間違えて読み込むなどをしても、ファイル終端に達しない限りエラーにならないので注意。

テキストファイルの行数を取得する (file_lines)

file_linesプロシージャはテキストファイルの行数を取得する。バイナリファイルに対しては利用不可。file_linesプロシージャは実際にファイルを開いて改行文字の検索を行っているので、巨大なテキストファイルに対して実行するとそれなりに時間がかかる。compressキーワードをつけることで、gzipで圧縮されたファイルの行数も取得可能。

; 利用例: 文字列の配列dataの各要素に、ファイルの内容が1行づつ入る
data=strarr(file_lines('filename'))
openr,lun,'filename',/GET_LUN
readf,lun,data
free_lun,lun

Windows での共有違反

IDLが開いているファイルを、同時に他のプログラムから不用意に開くと共有違反が発生する場合がある。状況を確認するため、IDLがファイルを開く時に発行するCreateFileのパラメータを調べてみた。(テスト環境: Windows XP SP3, IDL 7.1)

IDLのメソッド Desired Access Disposition Options ShareMode
OPENW Generic Read/Write OverwriteIf Synchronous IO Non-Alert, Non-Directory File Read, Write
OPENW,/APPEND Generic Read/Write OpenIf Synchronous IO Non-Alert, Non-Directory File Read, Write
OPENW,/DELETE Generic Read/Write, Delete OverwriteIf Synchronous IO Non-Alert, Non-Directory File, Delete On Close Read, Write
OPENR Generic Read Open Synchronous IO Non-Alert, Non-Directory File Read, Write
OPENR,/DELETE Generic Read, Delete Open Synchronous IO Non-Alert, Non-Directory File, Delete On Close Read, Write
FILE_MOVE Read Attributes, Delete, Synchronize Open Synchronous IO Non-Alert, Open Reparse Point Read, Write, Delete
FILE_DELETE Read Attributes, Delete Open Non-Directory File, Open Reparse Point Read, Write, Delete

リモートディスクに対する読み書きの場合にも調べてみた。(テスト環境: サーバー・クライアントともにWindows XP SP3, IDL 7.1)

コンソールに表示

非印字文字(制御文字)を利用する

コマンドライン版のIDLでは、非印字文字をコンソールに出力することで、特殊な効果を得ることができる。一般的に、結果は環境依存である。IDLDE環境では何もおこらないか、ゴミが表示される。

IDL> print,string(7b) ; ビープ音を鳴らす (Bell)

IDL> print,'123',string(8b),'456' ; 一文字戻る (Backspace)
12456

ANSIエスケープシーケンスを使うと、カーソルを任意の位置に移動、画面を消去、文字色の変更などが行える。当然のことながら、ANSIエスケープシーケンスをサポートしたターミナル上でないと効果がない。

print,string(27b)+'[2J'+string(27b)+'[0;0H'+'Hello' ; 画面を消去し、カーソルを左上に移動し、Helloと表示する
print,string(27b)+'[20;30H'+'Hello' ; 20行目30桁目にカーソルを移動し、Helloと表示する
print,string(27b)+'[1m' ; 高輝度(またはbold)で表示
print,string(27b)+'[4m' ; アンダーラインをつける
print,string(27b)+'[7m' ; 反転
print,string(27b)+'[0m' ; 元に戻す
print,string(27b)+'[31m' ; 前景色(文字色)を赤に
print,string(27b)+'[34m' ; 前景色を青に
print,string(27b)+'[39m' ; 前景色をデフォルトの色に戻す
print,string(27b)+'[47m' ; 背景色を灰色に
print,string(27b)+'[49m' ; 背景色をデフォルトの色に戻す

; 利用例: 時間のかかる処理で進捗率を表示
n=10
print ; 表示スペースの確保
for i=0,n-1 do begin
print, format='(a,i2," / ",i2)', string(27b) + '[1A', i, n-1
wait,1 ; 何か時間のかかる処理
endfor

長い文字列を表示するとき一画面ごとに停止させる (moreキーワード)

コマンドライン版のIDLでは、制御端末を書き込み用に開くことができるが、このときmoreキーワードを指定すると、一画面ごとに停止してくれる。しかし、出力の最中に端末のサイズを変更したりするとうまくいかないことがある。IDLDE環境では制御端末を開くのに失敗する。制御端末を開けるか調べるには、(fstat(-1)).isattyを調べる(1の時開ける、0のとき開けない)。filepath(/terminal) は制御端末の名前 (多くは /dev/tty )を返す。

if ((fstat(-1)).isatty) then $
openw,lun,filepath(/terminal),/get_lun,/more $
else $
lun = -1
for i=0,1000 do printf,lun,i ; 何か適当な内容を出力する
if (lun ne -1) then free_lun,lun

上のサンプルでは、制御端末を開けないときには、通常通り標準出力に出力するようにしている。なお、標準エラー出力を用いるときは装置番号として -1 ではなく -2 を用いる。

IDL7.1から、Windowsでもコマンドライン版のIDLが使えるようになったが、con を開けないのでこの方法は使えない。

西田圭佑 (NISHIDA Keisuke)
nishida at kwasan.kyoto-u.ac.jp