【入門編】ENDFILE条件のONユニットによる終了処理 – PL/Iの基本構文とデータ制御実践ガイド

皆さん、こんにちは!
IBMメインフレームの世界へようこそ。そして、PL/Iという、古くて新しい、奥深い言語に興味を持ってくださって、本当にありがとうございます。

Javaの`try-catch`やCOBOLの`AT END`に慣れている皆さんにとって、メインフレームのPL/Iは「なんだか昔の呪文みたい…」と感じるかもしれませんね。でも大丈夫!一つずつ紐解いていけば、その堅牢な設計思想と、驚くほどモダンな機能にきっと魅了されるはずです。

今回は、基幹システムのファイル処理において、「ファイルの終わり」をいかに優雅に、そして安全に処理するか、PL/I流の作法を徹底解説していきます。ずばり、`ENDFILE`条件と、それを捕捉する`ONユニット`の魔法についてです。

PL/Iプログラムの基本のキ:「おまじない」の正体

まず、PL/Iプログラムの基本的な骨格から見ていきましょう。Javaのクラス定義やCOBOLのIDENTIFICATION DIVISIONに相当する部分ですね。

SAMPLE: PACKAGE OPTIONS(MAIN); / プログラム全体を定義するパッケージ /
/ OPTIONS(MAIN)は、ここがプログラムの開始点であることを示す /

//
/ グローバルな変数宣言など、必要に応じてここに記述 /
//

SAMPLE_PROC: PROCEDURE; / プログラムの主たる処理を行うプロシージャー /

//
/ ローカルな変数宣言 /
/ 処理ロジック /
//

END SAMPLE_PROC; / プロシージャーの終了 /

END SAMPLE; / パッケージの終了 /

  • `PACKAGE OPTIONS(MAIN)`: Javaでいうところの「`public class Sample { public static void main(String[] args) { … } }`」のようなもの、とイメージしてください。`SAMPLE`という名前のプログラム全体を定義する箱(パッケージ)で、`OPTIONS(MAIN)`は「このパッケージがプログラムの起動点ですよ!」とコンパイラとOSに教えてあげるお約束です。これがないと、プログラムとして単独で実行できません。
  • `PROCEDURE`: COBOLの`PROCEDURE DIVISION`や、Javaのメソッドに近い概念です。実際の処理は、この`PROCEDURE`の中に記述していきます。上記の例では`SAMPLE_PROC`という名前のプロシージャーがプログラムのメインロジックを担います。

この基本構造が、これから学ぶファイル処理の舞台となります。難しく考える必要はありません。「とにかく、この形でプログラムを書き始めるんだな」と覚えておけばOKです。

ファイル処理の基本:メインフレームとの「おしゃべり」

さて、本題のファイル処理です。メインフレームでは、ファイルは非常に重要な存在です。膨大な量のデータを高速に処理するために、PL/Iはファイル処理に特化した強力な機能を持っています。

ファイルの「宣言」と「扉の開閉」

Javaで`FileInputStream`や`FileReader`をnewしたり、COBOLで`SELECT`句を書いたりするのと同じように、PL/Iでもファイルを使う前に「こんなファイルを使いますよ」と宣言する必要があります。

DECLARE SYSIN FILE INPUT RECORD ENVIRONMENT(FB(80 160)); / 入力ファイルを宣言 /
DECLARE SYSPRINT FILE OUTPUT PRINT; / 出力ファイルを宣言 /

  • `DECLARE`: 変数だけでなく、ファイルもこれで宣言します。
  • `SYSIN`、`SYSPRINT`: これらは、メインフレームでよく使われる論理ファイル名(DD名)です。慣例的に、`SYSIN`は標準入力、`SYSPRINT`は標準出力(プリンターや画面)を指すことが多いです。
  • `FILE INPUT RECORD`: このファイルが「入力用」で「レコード単位」の処理をする、という意味です。`STREAM`とすると、文字単位のストリーム処理になりますが、バッチ処理では`RECORD`が主流です。
  • `ENVIRONMENT(FB(80 160))`: これがメインフレーム特有の部分で、ファイルの物理的な属性(レコード形式、レコード長、ブロック長など)をOSに伝えます。`FB`は固定長ブロック化(Fixed Blocked)で、`80`はレコード長、`160`はブロック長(80バイトのレコードが2つで1ブロック)を示します。
  • `FILE OUTPUT PRINT`: このファイルが「出力用」で「印刷」を目的とすることを示します。`PRINT`属性を付けると、改行コードの自動挿入など、印刷に適した処理が行われます。

そして、ファイルを使う前には必ず`OPEN`し、使い終わったら`CLOSE`する、というJavaやCOBOLと同じ作法がPL/Iにもあります。

OPEN FILE(SYSIN), FILE(SYSPRINT); / ファイルを開く /
/ … ファイル処理 … /
CLOSE FILE(SYSIN), FILE(SYSPRINT); / ファイルを閉じる /

`OPEN`を忘れるとファイルアクセスエラー、`CLOSE`を忘れるとリソースリークやデータ不整合につながります。基幹システムでは絶対に避けたい事態ですよね。

データの読み込み:`READ`文

ファイルを宣言し、開いたら、いよいよデータを読み込みます。

DECLARE REC_AREA CHAR(80); / 80バイトのレコード格納領域 /

READ FILE(SYSIN) INTO(REC_AREA); / SYSINファイルからREC_AREAに1レコード読み込む /

  • `READ FILE(ファイル名) INTO(変数名)`: 指定したファイルから1レコードを読み込み、指定した変数に格納します。COBOLの`READ ファイル名 INTO データ名`とそっくりですよね!

「ファイルの終わり」との出会い:`ENDFILE`条件とは?

さて、ここからが本番です。ファイルを読み進めていくと、いつかは必ず「ファイルの終わり(End Of File)」に到達します。Javaでは`EOFException`が飛んできたり、`read()`メソッドが`-1`を返したり、COBOLでは`AT END`句で処理を記述しますよね。

PL/Iでは、この「ファイルの終わり」に到達したときに、`ENDFILE`という「条件」が発生します

この「条件」とは、特定の事象が発生したときに、PL/Iが内部的に発火させるシグナルのようなものです。もし、この`ENDFILE`条件を適切に捕捉しなかったらどうなるでしょう?

そうです、プログラムは延々とファイルを読み込もうとし続け、最終的には「ファイル終端に到達したのにまだREADしようとしているぞ!」とOSに怒られて、異常終了(ABEND)してしまいます。基幹システムでABENDはご法度ですよね。

そこで登場するのが、PL/Iの強力な機能である`ONユニット`です。

`ENDFILE`条件を捕まえる:`ONユニット`の魔法

PL/Iの`ONユニット`は、特定の条件が発生したときに実行される、専用の処理ブロックです。Javaの`try-catch`やCOBOLの`AT END`に最も近い概念と言えるでしょう。

構文は次のようになります。

ON ENDFILE(ファイル名)
BEGIN;
/ ファイル終端に到達したときに実行したい処理 /
END;

  • `ON ENDFILE(ファイル名)`: 「指定したファイルで`ENDFILE`条件が発生したら、この後の処理を実行してください」という意味です。
  • `BEGIN; … END;`: これが`ONユニット`の処理ブロックです。この中に、ファイルの終端に到達したときに実行したい処理を記述します。

まるで、舞台の袖で待機している役者のようです。特定の合図(ENDFILE条件)があったときに、サッと舞台に飛び出して、与えられたセリフ(処理)をこなす。そして、また舞台袖に戻っていく…そんなイメージですね。

安全な終了処理の組み立て方

一般的なファイル読み込みループは、次のような流れになります。

1. 無限ループ(または大きなループ)を開始
2. `ON ENDFILE`ユニットを設定
3. ファイルを1レコード読み込む
4. レコード処理
5. ループの先頭に戻る

このとき、`ON ENDFILE`ユニットの中で、ループを終了させるためのフラグを立てるか、あるいは`GO TO`文を使ってループの外に脱出するのが一般的なPL/Iの作法です。

「え、`GO TO`? 昔の言語じゃないんだから…」と思うかもしれませんね。しかし、PL/IのONユニットでは、特定の条件下で制御を移すために`GO TO`が非常に有効に機能する場合があるのです。もちろん、無闇な`GO TO`はスパゲッティコードの元凶ですが、ここでは目的が明確なので、安全に使うことができます。

実践!`ON ENDFILE`ユニットを使った安全なファイル処理

それでは、具体的なコード例を見ていきましょう。入力ファイル`SYSIN`からレコードを読み込み、出力ファイル`SYSPRINT`に書き出すシンプルなプログラムです。

SAMPLE: PACKAGE OPTIONS(MAIN);

DECLARE SYSIN FILE INPUT RECORD ENVIRONMENT(FB(80 160)); / 入力ファイル /
DECLARE SYSPRINT FILE OUTPUT PRINT; / 出力ファイル /

DECLARE REC_AREA CHAR(80); / レコード格納領域 /
DECLARE EOF_FLAG BIT(1) INIT(‘0’B); / EOFフラグ (‘0’B = false, ‘1’B = true) /

SAMPLE_PROC: PROCEDURE;

/ ファイルを開きます /
OPEN FILE(SYSIN), FILE(SYSPRINT);

/ ★ここがポイント! ON ENDFILEユニットを設定します /
ON ENDFILE(SYSIN) BEGIN;
PUT SKIP LIST(‘SYSINファイルの終端に到達しました。’); / メッセージ出力 /
EOF_FLAG = ‘1’B; / EOFフラグを立てて、ループを終了させます /
END;

/ 最初のレコードを読み込みます /
/ ここでENDFILEが発生する可能性もあるため、ループに入る前に一度READします /
READ FILE(SYSIN) INTO(REC_AREA);

/ EOFフラグが立たない限り、ループを続けます /
DO WHILE(EOF_FLAG = ‘0’B);

/ 読み込んだレコードをSYSPRINTに出力します /
PUT FILE(SYSPRINT) SKIP LIST(REC_AREA);

/ 次のレコードを読み込みます /
/ ここでENDFILEが発生すると、上記ONユニットが実行されます /
READ FILE(SYSIN) INTO(REC_AREA);

END; / DO WHILE の終了 /

PUT SKIP LIST(‘ファイル処理を終了します。’);

/ ファイルを閉じます /
CLOSE FILE(SYSIN), FILE(SYSPRINT);

END SAMPLE_PROC;

END SAMPLE;

コードの解説

1. `DECLARE EOF_FLAG BIT(1) INIT(‘0’B);`:

  • `BIT(1)`は1ビットの論理型変数です。`’0’B`はfalse、`’1’B`はtrueを意味します。Javaの`boolean`型と同じ感覚ですね。
  • このフラグを使って、ファイル読み込みループの終了を制御します。

2. `ON ENDFILE(SYSIN) BEGIN; … END;`:

  • `SYSIN`ファイルの終端に到達したら、このブロックが実行されます。
  • `PUT SKIP LIST(…)`は、Javaの`System.out.println()`やCOBOLの`DISPLAY`に近い出力文です。
  • `EOF_FLAG = ‘1’B;`によってフラグが立てられ、メインの`DO WHILE`ループが終了する準備が整います。

3. `READ FILE(SYSIN) INTO(REC_AREA);` (ループに入る前):

  • 重要ポイント! `ON ENDFILE`ユニットを設定したら、まずループに入る前に一度`READ`を実行するのが定石です。なぜなら、ファイルが空っぽだった場合、最初の`READ`でいきなり`ENDFILE`条件が発生するからです。この時点でフラグが立てば、ループは一度も実行されずに安全に終了します。

4. `DO WHILE(EOF_FLAG = ‘0’B); … END;`:

  • Javaの`while(true)`やCOBOLの`PERFORM … UNTIL`に相当するループです。`EOF_FLAG`が`’0’B`(false)の間、つまりファイルの終端に到達していない間は処理を続けます。

5. `READ FILE(SYSIN) INTO(REC_AREA);` (ループの中):

  • ここで`ENDFILE`条件が発生すると、設定しておいた`ON ENDFILE(SYSIN)`ユニットが自動的に実行され、`EOF_FLAG`が`’1’B`に変わります。次のループ条件チェックで`’0’B`ではないと判断され、ループを抜けることができるわけです。

この構造を使えば、ファイルが空でも、途中でデータが尽きても、常に安全にプログラムを終了させることができます。

なぜこんな回りくどいことを?メインフレームの思想と深い知見

Javaの`try-catch`やCOBOLの`AT END`に比べて、PL/Iの`ONユニット`は少し変わった仕組みに感じるかもしれませんね。なぜこのような設計になっているのでしょうか?

ここには、メインフレームが長年培ってきた「堅牢なシステム運用」と「効率的なリソース管理」の哲学が詰まっているんです。

1. 非同期イベントハンドリング:
PL/Iの`ONユニット`は、特定の「条件」(`ENDFILE`だけでなく、`ZERODIVIDE`、`OVERFLOW`、`CONVERSION`など、様々な条件があります)が発生したときに、まるで割り込み処理のように動作します。これは、現代の非同期イベントハンドリングの考え方に非常に近いものです。プログラムのメインロジックとは独立して、特定のイベントに対応する処理を記述できるため、ロジックをすっきりと保つことができます。
2. OSとの密接な連携:
メインフレームのPL/Iは、OS(z/OSなど)と非常に密接に連携しています。ファイルI/Oの際に発生する様々な物理的・論理的なエラーは、OSが検知し、PL/Iのランタイムに「条件」として通知されます。`ONユニット`は、これらのOSからの通知をプログラム側で捕捉し、適切に処理するための強力なメカニズムなのです。
3. レジリエンス(回復力):
基幹システムは、何が起きても止まってはなりません。`ONユニット`を使うことで、予期せぬエラー(例えば、数値項目に文字データが入っていた場合の`CONVERSION`条件など)が発生しても、プログラムが即座にABENDするのではなく、リカバリー処理(エラーデータをスキップして次へ進む、ログを出すなど)を行って処理を継続する、といった堅牢な設計が可能になります。

私がまだ若かりし頃、この`ENDFILE`の処理を甘く見て、ループの中に`READ`だけを記述し、`ONユニット`を適切に設定しなかったために、夜中にオペレーターから緊急電話がかかってきて「プログラムがファイル終端でABENDしたぞ!」と叱られた苦い経験があります。その時、この`ONユニット`の重要性を骨身に染みて理解しました。

後処理を`ONユニット`内で行うべきか、それとも今回のようにフラグを立ててメインロジックで後処理を行うべきかは、プログラムの規模や複雑さ、エラーリカバリーの要件によって変わってきます。シンプルなファイル終端処理であれば、フラグ制御が最も見通しが良く、推奨されるアプローチです。

まとめ:PL/Iの`ENDFILE`は怖くない!

今回は、PL/Iにおけるファイル読み込みの終端処理、`ENDFILE`条件と`ONユニット`について深く掘り下げてきました。

  • PL/Iのプログラムは`PACKAGE OPTIONS(MAIN)`と`PROCEDURE`で構成される。
  • ファイルは`DECLARE FILE`で宣言し、`OPEN`/`CLOSE`で開閉する。
  • `READ`文でレコードを読み込む。
  • ファイルの終端に到達すると`ENDFILE`条件が発生する。
  • `ON ENDFILE(ファイル名) BEGIN; … END;`で、この条件を安全に捕捉し、処理を継続または終了させることができる。

JavaやCOBOLの経験がある皆さんにとっては、少し独特な記法や考え方に感じるかもしれませんが、これもまたメインフレームという巨大なシステムの歴史の中で磨き上げられてきた、洗練された手法の一つです。

怖がらず、一つずつ慣れていきましょう!PL/Iは、皆さんが思っている以上に柔軟で強力な言語です。この知識が、皆さんの基幹システム改修やマイグレーション調査の一助となれば幸いです。

次回は、PL/Iの変数の宣言と、メインフレームならではのデータ型の扱いに焦点を当ててみたいと思います。お楽しみに!

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