PL/I STREAM I/Oの華、EDIT指定:ピクチャ編集文字を使いこなし、データ整形と改行を自在に操る!
おい、諸君!今日のテーマはPL/IのSTREAM I/O、特にGET/PUT EDITについてだ。メインフレームの現場で、バッチ処理をバリバリ書いてる諸君なら、一度は「うーん、このデータ、どうやって整形して出力すればいいんだ?」なんて悩んだことがあるはずだ。レコード入出力やVSAMも重要だが、画面表示や一時的なファイル出力なんかではSTREAM I/Oが活躍する場面も多い。そして、そのSTREAM I/Oで、データを見やすく、あるいはプログラムで扱いやすい形に整えるための強力な武器が、そう、「EDIT指定」なんだ。
正直、最初は「なんだこのピクチャ編集文字は!」って頭を抱えるかもしれない。でも、一度コツを掴めば、これほど便利なものはない。今日は、このEDIT指定の役割、ピクチャ編集文字の種類、そしてそれらを駆使したデータ整形と改行制御のテクニックを、実例を交えながら、俺が長年培ってきた現場の知恵とともに伝授しよう。
なぜGET/PUT EDITなのか? STREAM I/Oの基本に立ち返る
まず、GET/PUT EDITの話をする前に、STREAM I/Oの基本をさらっと確認しておこう。PL/IにおけるSTREAM I/Oというのは、データが文字の連続体、つまり「ストリーム」として扱われる入出力方式だ。レコードの区切りとか、フィールドの長さとかをあまり意識せず、文字の並びとして読み書きするイメージだな。
GET/PUT EDITは、このSTREAM I/Oにおいて、データのフォーマットを明示的に指定しながら入出力を行うためのものだ。対照的に、GET/PUT LISTなんかは、PL/Iのデフォルトのルールに従って自動的にフォーマットしてくれるが、細かい制御はできない。
例えば、顧客マスターのデータをCSV形式で出力したい、とか、特定のフォーマットでログを出力したい、なんていう場合には、GET/PUT EDITが必須となる。
/ STREAM I/Oの基本例 /
DECLARE (CARD_IN, LINE_OUT) FILE STREAM OUTPUT; / STREAMモードのファイル宣言 /
/ LIST指定での出力例 /
PUT LIST(‘Hello, World!’); / PL/Iのデフォルトフォーマットで出力 /
/ EDIT指定での出力例 /
DECLARE (CUSTOMER_ID, CUSTOMER_NAME) PIC'(A10)’, / 文字列型 /
(ORDER_AMOUNT) PIC ‘9(7)V99’; / 数値型 (仮数部7桁、小数部2桁) /
/ EDIT指定でフォーマットを制御して出力 /
PUT EDIT (CUSTOMER_ID, CUSTOMER_NAME, ORDER_AMOUNT)
(A(10), X(2), A(20), X(2), ‘ $’, F(9,2));
この例で `PUT EDIT` の部分が、まさにEDIT指定の出番だ。括弧の中に、出力したい変数と、それに続くフォーマット指定を並べている。
EDIT指定の主役:ピクチャ編集文字(Picture Specification)の世界
さて、EDIT指定の核心は、なんといっても「ピクチャ編集文字」にある。これは、データ型や桁数だけでなく、どのような形式でデータを表現するかを細かく指定するための記号の集まりだ。PL/Iのピクチャ仕様は非常に強力で、数値の整形、文字列の配置、小数点や通貨記号の挿入など、様々な要求に応えてくれる。
代表的なピクチャ編集文字をいくつか見ていこう。
文字列型(Character String)関連
- `A(n)`: 固定長文字列。`n` で指定した長さで出力される。
- 例: `A(10)` -> ‘ABC ‘ (10文字、足りなければブランクで埋める)
- `X(n)`: 固定長文字(文字コード)。`n` で指定した長さで出力される。
- 例: `X(4)` -> ‘41424344’ (ASCIIコードで ‘ABCD’ の場合)
- `SKIP(n)`: 指定した `n` 行だけ改行する。`SKIP` だけなら1行改行。
- `LINE(n)`: 指定した `n` 行目に移動する。
- `PAGE`: 新しいページに出力する。
数値型(Numeric)関連
- `F(w, d)`: 固定小数点数。
- `w`: 全体の桁数
- `d`: 小数点以下の桁数
- 例: `F(7, 2)`
- `12345.67` -> ‘ 12345.67’ (左寄せ、ブランク詰め)
- `123.45` -> ‘ 123.45’
- `PIC’…’` : より詳細なフォーマット指定が可能。これがEDIT指定の真骨頂だ!
- `9`: 数字。
- `V`: 仮の小数点位置。実際のデータには含まれない。
- `S`: 符号。`+` または `-` を表示する。`+` または `-` の位置を指定できる。
- `Z`: ゼロ抑制。先頭のゼロをブランクで置き換える。
- “: ゼロ抑制と同時に、先頭のゼロをアスタリスクで埋める(小切手などで不正防止に)。
- `.`: 小数点。
- `,`: 千単位の区切り文字。
- `CR`, `DB`: クレジット/デビット表示。
- `$` , `¥`: 通貨記号。
- ` `/`-`: 符号表示位置。
その他の制御文字
- `X(n)`: 指定した `n` 個のブランクを出力する。`X(2)` は2つのブランク。
- `’literal’`: リテラル文字列。そのまま出力される。
実践!GET/PUT EDITを使ったデータ整形と改行制御の例
理屈だけではつまらないだろう。現場で役立つ具体的なコード例を見ていこう。
例1:CSV形式でのデータ出力
顧客リストをCSV形式で出力するケースを考えてみよう。
/ CSV出力用ファイル宣言 /
DECLARE CSV_FILE FILE STREAM OUTPUT RECORD SEQUENTIAL; / レコードモードでもEDIT指定は使える /
DECLARE (CUST_ID, CUST_NAME, CUST_ADDRESS) PIC'(A10)’, / 顧客ID, 名前, 住所 /
(ORDER_TOTAL) PIC ‘9(8)V99’; / 注文合計金額 /
/ データの準備(ここでは固定値だが、実際はファイルやDBから読み込む) /
CUST_ID = ‘C12345’;
CUST_NAME = ‘山田 太郎’;
CUST_ADDRESS = ‘東京都千代田区’;
ORDER_TOTAL = 15000.75;
/ CSV形式でEDIT指定して出力 /
PUT FILE(CSV_FILE) EDIT (CUST_ID, CUST_NAME, CUST_ADDRESS, ORDER_TOTAL)
(
A(10), / 顧客ID (10文字) /
‘,’, / 区切り文字 /
A(20), / 顧客名 (20文字、必要ならブランク詰め) /
‘,’, / 区切り文字 /
A(30), / 住所 (30文字、必要ならブランク詰め) /
‘,’, / 区切り文字 /
PIC’9(8)V99′, / 金額 (8桁、小数点2桁) /
SKIP(1) / 1行改行 /
);
/ 別のレコードを出力する場合 /
CUST_ID = ‘C67890’;
CUST_NAME = ‘佐藤 花子’;
CUST_ADDRESS = ‘大阪府大阪市’;
ORDER_TOTAL = 2500.00;
PUT FILE(CSV_FILE) EDIT (CUST_ID, CUST_NAME, CUST_ADDRESS, ORDER_TOTAL)
(
A(10),
‘,’,
A(20),
‘,’,
A(30),
‘,’,
PIC’9(8)V99′,
SKIP(1)
);
/ ファイルクローズ(必要であれば) /
CLOSE FILE(CSV_FILE);
ポイント:
- `A(n)` で各フィールドの長さを指定し、カンマ `,` で区切り文字をリテラルとして埋め込んでいる。
- `PIC’9(8)V99’` は、`ORDER_TOTAL` の実際の値 `15000.75` を、そのまま `15000.75` として出力する。もし `PIC’9(8)’` などとしてしまうと、小数点以下が切り捨てられたり、エラーになったりする可能性がある。
- 最後の `SKIP(1)` で、1レコード(1行)を出力するごとに改行している。CSVファイルは通常、行ごとにレコードが区切られるので、これは必須の処理だ。
例2:整形されたレポート形式での出力
次は、ちょっと見栄えの良いレポート形式で出力する例だ。
/ レポート出力用ファイル宣言 /
DECLARE REPORT_FILE FILE STREAM OUTPUT;
DECLARE (ITEM_CODE, ITEM_NAME) PIC'(A8)’,
(QUANTITY) PIC ‘9(5)’,
(UNIT_PRICE) PIC ‘9(7)V2’,
(TOTAL_PRICE) PIC ‘$$$$,$$9.99’; / 通貨記号、カンマ区切り、ゼロ抑制付き /
/ ヘッダー行の出力 /
PUT FILE(REPORT_FILE) EDIT (‘商品コード’, ‘商品名’, ‘数量’, ‘単価’, ‘合計金額’)
(SKIP(1), A(10), X(2), A(25), X(2), A(5), X(2), A(10), X(2), A(15));
/ 区切り線の出力 /
PUT FILE(REPORT_FILE) EDIT (‘——————————————————————————-‘)
(SKIP(1));
/ データ行の出力(ループ処理などを想定) /
ITEM_CODE = ‘A001’;
ITEM_NAME = ‘プログラミング入門’;
QUANTITY = 2;
UNIT_PRICE = 3500.50;
TOTAL_PRICE = QUANTITY 50000; / (UNIT_PRICE QUANTITY) / 100; ← 実際は小数点の扱い注意 /
/ 実際の合計金額は (QUANTITY UNIT_PRICE) になるが、ここでは単純化 /
PUT FILE(REPORT_FILE) EDIT (ITEM_CODE, ITEM_NAME, QUANTITY, UNIT_PRICE, TOTAL_PRICE)
(
A(10), / 商品コード /
X(2), / スペース /
A(25), / 商品名 /
X(2), / スペース /
F(5,0), / 数量 (整数) /
X(2), / スペース /
PIC’$$$$,$$9.99′, / 単価 /
X(2), / スペース /
PIC’$$$$,$$9.99′, / 合計金額 /
SKIP(1) / 改行 /
);
/ 別のデータ行 /
ITEM_CODE = ‘B002’;
ITEM_NAME = ‘メインフレームシステムアーキテクチャ’;
QUANTITY = 1;
UNIT_PRICE = 12000.00;
TOTAL_PRICE = QUANTITY 1000000;
PUT FILE(REPORT_FILE) EDIT (ITEM_CODE, ITEM_NAME, QUANTITY, UNIT_PRICE, TOTAL_PRICE)
(
A(10),
X(2),
A(25),
X(2),
F(5,0),
X(2),
PIC’$$$$,$$9.99′,
X(2),
PIC’$$$$,$$9.99′,
SKIP(1)
);
/ ページ制御の例(もし必要なら) /
/ PUT FILE(REPORT_FILE) EDIT (‘次のページへ…’) (PAGE); /
ポイント:
- `PIC’$$$$,$$9.99’` のように、通貨記号 `$`、千単位区切り `,`、ゼロ抑制 `$`(ここでは `$` がゼロ抑制の役割も兼ねる)を組み合わせて、見やすい金額表示を実現している。
- `F(5,0)` は、`QUANTITY` を5桁の整数として出力する。
- `X(2)` で、各フィールドの間に2つのスペースを挿入し、見やすくしている。
- ヘッダー行とデータ行で、各フィールドの表示幅 (`A(10)`, `A(25)` など) を揃えることで、表のような体裁を整えている。
- `SKIP(1)` は、各行の最後に出力され、次の行への移動を指示する。
例3:入力データの読み込みと整形(GET EDIT)
出力だけでなく、入力でもEDIT指定は強力だ。外部ファイルから特定のフォーマットでデータを読み込む際に役立つ。
/ 入力ファイル宣言 /
DECLARE INPUT_FILE FILE STREAM INPUT;
DECLARE (IN_ID) PIC'(A5)’,
(IN_DATE) PIC’9(8)’, / YYYYMMDD形式 /
(IN_VALUE) PIC ‘9(7)V2’; / 仮の小数点位置 /
/ ファイルの終端に達した場合のONユニット /
ON ENDFILE(INPUT_FILE) BEGIN
PUT SKIP(1) LIST(‘入力ファイルの終端に達しました。’);
/ 必要ならここで処理を終了させる /
RETURN;
END;
/ データの読み込みと整形 /
GET FILE(INPUT_FILE) EDIT (IN_ID, IN_DATE, IN_VALUE)
(
A(5), / 5文字のIDを読み込む /
X(1), / 区切り文字(例:タブやスペース1つ)をスキップ /
PIC’9(8)’, / 8桁の日付を読み込む /
X(1), / 区切り文字をスキップ /
PIC’9(7)V2′ / 7桁の数値を読み込む(仮の小数点位置) /
);
/ 読み込んだデータを整形して表示 /
PUT SKIP(1) EDIT (‘ID:’, IN_ID, ‘ Date:’, IN_DATE, ‘ Value:’, IN_VALUE)
(A, X(1), A(5), X(2), A, X(1), PIC’9(8)’, X(2), A, X(1), PIC’9(7)V2′);
/ さらに日付のフォーマットを変更して表示 /
DECLARE (DISP_YEAR, DISP_MONTH, DISP_DAY) PIC ‘9(2)’;
DISP_YEAR = SUBSTR(IN_DATE, 1, 2);
DISP_MONTH = SUBSTR(IN_DATE, 3, 2);
DISP_DAY = SUBSTR(IN_DATE, 5, 2); / 実際は6,7桁目 /
PUT SKIP(1) EDIT (‘YYYY/MM/DD:’, DISP_YEAR, ‘/’, DISP_MONTH, ‘/’, DISP_DAY)
(A, X(1), PIC’9(2)’, X(1), A, X(1), PIC’9(2)’, X(1), A, X(1), PIC’9(2)’);
/ ON ENDFILE を再度発生させるため、ファイル終端を模倣 /
/ 実際にはファイルの終端に達するまでループする /
ポイント:
- `GET FILE(INPUT_FILE) EDIT (…) (…)` の形式で、入力ファイルからデータを読み込み、指定したフォーマットで変数に格納している。
- `X(1)` は、入力データ中の区切り文字(スペースやタブなど)を読み飛ばすのに便利だ。
- `PIC’9(8)’` で読み込んだ日付 `20231027` を、`SUBSTR` 関数を使って年・月・日に分割し、`PIC’9(2)’` で2桁表示に整形している。
- `ON ENDFILE` ユニットは、ファイル処理で必須の考慮事項だ。これがないと、ファイルの終端でプログラムが異常終了してしまう。
ONユニットと制御フロー:エラーハンドリングの重要性
GET/PUT EDITに限った話ではないが、ファイル入出力においては、エラーハンドリングが非常に重要だ。特に、想定外のデータフォーマットや、ファイルが存在しない、アクセス権がないといった状況に遭遇した場合、プログラムが abruptly 終了しないように、適切な処置を講じる必要がある。
PL/Iでは `ON` ユニットを使って、様々な例外的な状況(イベント)が発生した際の処理を定義できる。
- `ON ERROR`: 処理中にエラーが発生した場合。
- `ON FAILURE`: 入出力操作が失敗した場合(ファイルが開けない、書き込めないなど)。
- `ON ENDFILE`: ファイルの終端に達した場合。
- `ON UNDEFINEDFILE`: 未定義のファイルにアクセスしようとした場合。
これらの `ON` ユニットを適切に設定することで、堅牢なプログラムを作成することができる。例えば、`ON ENDFILE` でファイルの終端を検知し、ループを抜ける処理を入れたり、`ON FAILURE` でエラーログを出力して処理を継続したり、といった具合だ。
/ エラーハンドリングの例 /
DECLARE ERROR_MSG PIC'(A80)’;
ON ERROR BEGIN
/ エラー発生時の詳細情報を取得 /
PUT SKIP(1) LIST(‘ PROGRAM ERROR DETECTED ‘);
/ ここでPL/Iの組み込み関数などを使ってエラーコードやメッセージを取得し、ログファイルに記録する /
/ 例: ERROR_MSG = ERROR_MESSAGE; /
PUT SKIP(1) LIST(‘Error Message: ‘ || ERROR_MSG);
/ 必要に応じて処理を中断 /
SIGNAL CONDITION(ERROR); / エラーを再発生させる /
END;
ON FAILURE(MY_FILE) BEGIN
PUT SKIP(1) LIST(‘ FILE I/O FAILURE DETECTED for MY_FILE ‘);
/ ファイル操作の失敗原因を調査し、適切なメッセージを出力 /
PUT SKIP(1) LIST(‘File: MY_FILE’);
/ 必要なら処理を中断 /
SIGNAL FILE(MY_FILE); / ファイル関連のエラーを再発生させる /
END;
/… ファイル処理のコード …/
/ ONユニットの設定は、通常、ファイルオープン前や処理の開始部分で行う /
まとめ:EDIT指定はPL/Iの強力な武器
さて、今日はPL/IのSTREAM I/OにおけるGET/PUT EDITと、ピクチャ編集文字を使ったデータ整形、改行制御について解説してきた。
- EDIT指定は、STREAM I/Oにおいてデータのフォーマットを細かく制御するための強力な手段だ。
- ピクチャ編集文字を使いこなせば、数値の桁揃え、ゼロ抑制、通貨記号の挿入、日付のフォーマット変更など、様々なデータ整形が可能になる。
- `SKIP` や `LINE`、`PAGE` といった制御文字を組み合わせることで、出力の改行やページ送りも自在に操作できる。
- `ON` ユニットによるエラーハンドリングは、堅牢なプログラム設計のために不可欠だ。
メインフレームの保守・開発現場では、既存のソースコードを読んだり、改修したりする機会が多いだろう。その際、GET/PUT EDITが使われている箇所は、しばしば「読みにくい」と感じるかもしれない。しかし、今日学んだピクチャ編集文字の知識があれば、そのコードが何をやろうとしているのか、そしてなぜそのように書かれているのかが、きっと理解できるようになるはずだ。
逆に、これから新規開発や改修を行う際には、ぜひこのEDIT指定を積極的に活用してほしい。見やすく、正確で、そして効率的なコードを書くための強力な武器になることは間違いない。
「あのPL/Iのソースコード、読みにくいんだよな…」なんて言われずに、「なるほど、これはEDIT指定で綺麗に整形されてるな!」と、諸君のコードが後輩エンジニアに尊敬されるような、そんな未来を目指して、今日もコーディングに励もうじゃないか!
何か質問があれば、いつでも声をかけてくれ。俺の知っていることは、全部教えてやるからな!
