Archive for the ‘Perl’ Category
OpenCOBOLとファイル操作(弊社拡張)
OpenCOBOLからのファイル操作ですが内部での定義によって幾つか種類があります。
- 固定長レコードのシーケンシャルファイル
- 固定長レコードのISAM形式ファイル
- 可変長レコードのシーケンシャルファイル
- 他
これらはCOBOLソース内でのSELECT句での定義でファイル名を直接または環境変数を経由して間接的に指定することが可能です。OpenCOBOLの素の状態ですと
SELECT ADBF0320 ASSIGN TO "FILE0001"
ORGANIZATION SEQUENTIAL
ACCESS MODE SEQUENTIAL.
と記述されている場合は、FILE0001または環境変数 DD_FILE0001または dd_FILE0001に設定されているファイル名のファイルのOPENが可能です。弊社ではこの環境変数渡しの機能を活用してperlからOpenCOBOL側へJCL中で使用しているファイル名を渡しています。
さて、JCL中ではSYSINと呼ばれる形式でファイルを作らずにその場で渡したいデータを記述することがあります。
\INPUT ACCEPT1,TYPE=DATASSF,LIST=YES
4241122 登録データ1
4241122 登録データ1追加分
\ENDINPUT;
ADAM2200:
\STEP PROG2000 FILE=USL.CAT1 DUMP=DATA SUBLM=NORMAL;
\ASSIGN FILE0010 USR.F001 SHARE=ALL HOLDMODE=NO;
\ASSIGN FILE0110 USR.F011-T FILESTAT=TEMP PUBLIC NORMAL=PASS;
\ALLOCATE FILE0110 USR.F011-T SIZE=05;
\DEFINE FILE0110 RECSIZE=57 BLOCKSZ=10260 INCRSZ=01
RELSP RECFORM=FB;
\ASSIGN SIN ACCEPT1 FILESTAT=SYSIN;
\ENDSTEP;
上記ではJCL中で定義されたASSIGN1というSYSINの内容をSINというファイル識別名に割り当てています。これをperlに置き換える(この部分自動的に処理しています)と
INPUT "ACCEPT1,TYPE=DATASSF,LIST=YES",<<_EOT;
4241122 登録データ1
4241123 登録データ1追加分
_EOT
ENDINPUT;
ADAM2200:
STEP "PROG2000 FILE=USL.CAT1 DUMP=DATA SUBLM=NORMAL";
ASSIGN "FILE0010 USR.F001 SHARE=ALL HOLDMODE=NO";
ASSIGN "FILE0110 USR.F011-T FILESTAT=TEMP PUBLIC NORMAL=PASS";
ALLOCATE "FILE0110 USR.F011-T SIZE=05";
DEFINE "FILE0110 RECSIZE=57 BLOCKSZ=10260 INCRSZ=01",
"RELSP RECFORM=FB";
ASSIGN "SIN ACCEPT1 FILESTAT=SYSIN";
上記のように変換しています。さて、SYSINの内容ですがまず1レコードが何byteであるという情報がありません。そして1行毎に行の長さが異なっています。今回移植の対象となった対象機のCOBOLではこのような場合には「改行区切りで1レコード」とするようになっていました。つまり可変長レコードです。ところがOpenCOBOLで可変長レコードをファイルとして扱うためには:
レコード先頭1バイトまたは2バイトにレコード長+1レコード分のデータ
レコード先頭1バイトまたは2バイトにレコード長+1レコード分のデータ
・・・
という形式でデータを作成する必要があります(つまり1byte目がレコード長として正しくないと、メモリ上に過大な長さのデータが読み込まれて、あっという間にSegfault します)。OpenCOBOLの外側からファイルの形式について何らかの方法で指示を出す必要が在りましたので、弊社ではファイル名の先頭に「sysin://」という識別子を(URI的に)付けてファイル名を渡すようにしています。これをOpenCOBOL内のファイルハンドラに渡る前に処理し、改行区切りの可変長レコードとして処理しています。同じく、印刷用の中間データなど1行の長さが可変長となる場合について「sysout://」という識別子を付けて指定することができるようにしています。他、標準の固定長レコードのドライバと動作をちょっと変えたドライバを使いたい場合を考え「misam://」や「mseq://」さらにLinux他では/dev/nullに該当するものとして「nullfs://」という識別子を指定可能としています。
標準の固定長レコードのドライバと動作をちょっと変えたいというのは例えばレコード挿入、削除時の細かい振る舞い、二次キー指定時の動作、二次キーを持っているISAMファイルを主キーしか定義していないCOBOLソースから書き込みモードで開いた場合の動作(OpeCOBOLの標準の動作では、書き込みモードでISAMファイルを開くと、一旦削除されますので、最悪二次キーについての定義が欠落します)等々です。
また、「perlfs://CLASSNAME/param」という形式でファイル名を渡す事によりファイルハンドラとしてperlにて記述したものを呼び出すようにもしています。DBとCOBOL内の固定長レコードの編集用コードについてperlで記述できるため、大変柔軟にDBとの連携を図れるようになりました(つまり、DBD::PgやDBD:MySQL、Oracleなどとの連携も可能です。MySQLについては既に運用されていますし、KeyValue系のDBへの接続もそれほどの変更なしに実装できます)。
上記に加え、固定長レコードやキー定義などの情報を別ディレクトリ内の管理ファイルに登録しておくことで、ファイルオープン時に正しい形式のファイルを使用しているかどうかCOBOLプログラム内の定義と照らし合わせて動的にチェックできるようになり、また現在どのようなファイルがオープン状態であるか?を全てモニタできるようにしています。
これらの改造はOpenCOBOLがオープンソースとして配布されていたことで可能になりました。成果は随時コミュニティ等にフィードバックしていきたいと考えております。
OpenCOBOLとSPECIAL-NAMES(拡張)
JCLからCOBOLを呼び出す際に外部パラメータを渡したいということがあります。某社JCLでは
\DCV ACCEPT2,CHARACTER='04' ;
などDCVというコマンドで定義して、これをCOBOL側では
SPECIAL-NAMES.
CHAR002 IS ACCEPT2.
のように記述して取り込んでいるようです(この場合CHAR002に’04’が入ります)。
ところがもちろんOpenCOBOLにはこのような機能はありません。実際 SPECIAL-NAMES. 部分の定義をcobc/parser.yから拾い上げると:
special_name:
mnemonic_name_clause
| alphabet_name_clause
| symbolic_characters_clause
| locale_clause
| class_name_clause
| currency_sign_clause
| decimal_point_clause
| cursor_clause
| crt_status_clause
| screen_control
| event_status
;
となっており、mnemonic_name_clause 部分が使えそうなのですが、system_nameとして使用できるのは
} system_table[] = {
{"SYSIN", CB_DEVICE_NAME, CB_DEVICE_SYSIN, NULL},
{"SYSIPT", CB_DEVICE_NAME, CB_DEVICE_SYSIN, NULL},
{"SYSOUT", CB_DEVICE_NAME, CB_DEVICE_SYSOUT, NULL},
{"SYSLIST", CB_DEVICE_NAME, CB_DEVICE_SYSOUT, NULL},
{"SYSLST", CB_DEVICE_NAME, CB_DEVICE_SYSOUT, NULL},
{"PRINTER", CB_DEVICE_NAME, CB_DEVICE_SYSOUT, NULL},
{"SYSERR", CB_DEVICE_NAME, CB_DEVICE_SYSERR, NULL},
{"CONSOLE", CB_DEVICE_NAME, CB_DEVICE_CONSOLE, NULL},
{"C01", CB_FEATURE_NAME, CB_FEATURE_C01, NULL},
{"C02", CB_FEATURE_NAME, CB_FEATURE_C02, NULL},
{"C03", CB_FEATURE_NAME, CB_FEATURE_C03, NULL},
{"C04", CB_FEATURE_NAME, CB_FEATURE_C04, NULL},
{"C05", CB_FEATURE_NAME, CB_FEATURE_C05, NULL},
{"C06", CB_FEATURE_NAME, CB_FEATURE_C06, NULL},
{"C07", CB_FEATURE_NAME, CB_FEATURE_C07, NULL},
{"C08", CB_FEATURE_NAME, CB_FEATURE_C08, NULL},
{"C09", CB_FEATURE_NAME, CB_FEATURE_C09, NULL},
{"C10", CB_FEATURE_NAME, CB_FEATURE_C10, NULL},
{"C11", CB_FEATURE_NAME, CB_FEATURE_C11, NULL},
{"C12", CB_FEATURE_NAME, CB_FEATURE_C12, NULL},
{"FORMFEED", CB_FEATURE_NAME, CB_FEATURE_FORMFEED, NULL},
{"SWITCH-1", CB_SWITCH_NAME, CB_SWITCH_1, NULL},
{"SWITCH-2", CB_SWITCH_NAME, CB_SWITCH_2, NULL},
{"SWITCH-3", CB_SWITCH_NAME, CB_SWITCH_3, NULL},
{"SWITCH-4", CB_SWITCH_NAME, CB_SWITCH_4, NULL},
{"SWITCH-5", CB_SWITCH_NAME, CB_SWITCH_5, NULL},
{"SWITCH-6", CB_SWITCH_NAME, CB_SWITCH_6, NULL},
{"SWITCH-7", CB_SWITCH_NAME, CB_SWITCH_7, NULL},
{"SWITCH-8", CB_SWITCH_NAME, CB_SWITCH_8, NULL},
{NULL, 0, 0, NULL}
};
上記以外の場合は”Unknown system-name ‘何々'” のメッセージが出力されてエラーとなります。弊社にて対応した際、当初は外部からの値を環境変数経由で渡す事を検討しましたが、COBOL内部で値をセットしてJCLへ返す場合があると言うことで思い切って機能拡張しました。DCVにて渡された値については環境変数(DCV_varname=値)を定義して渡し、値を書き替える場合も環境変数を書き替え、プロセス終了時にDCV_varname環境変数の値を全てファイルへ書き出しています。書き替えた値をJCLへ返す際にこの(環境変数 SYSTEM_STATUS_FILE に定義されたファイル名の)ファイルを読み込んでいます。
これらの拡張とJCLをperlに変換した*.jclスクリプトを使用することで DCV機能は
DCV "ACCEPT2,CHARACTER='04'";
という記述のperlスクリプトと
SPECIAL-NAMES.
CHAR002 IS ACCEPT2.
元のCOBOLプログラムをそのまま解釈できる拡張したOpenCOBOLにて処理できるようになりました。移植対象のコード中にDCVを使用している箇所が4桁箇所以上もありましたが、かなり簡単に移植ができました。
Mac で複数ファイルの一括置換ってどうしてます?
Windows だと Devas ってソフトでバッチリだったんですが、Mac ではいいソフトを見つけることができずにいます。
サブディレクトリ内のファイルも含めてまとめて置換は、今はターミナルで次の手順でやってます。
まず、find で特定拡張子のファイルリストをとって xargs で grep にわたして置換したい単語を検索。
$ find . -name '*.拡張子' | xargs grep 置換したい単語
で、grepに -l オプションつけて置換したい単語が含まれるファイルリストをとって xargs で perl にわたして置換。
$ find . -name '*.拡張子' | xargs grep -l 置換したい単語 | xargs perl -p -i.bak -e 's/置換したい単語/置換後の単語/g'
perl の -i オプションに .bak と付けてるので元ファイルは bak という拡張子で残ります。
「perlだけでやればいいじゃん」と言われそうですが、いきなりやるのは不安なのと置換が行われてないファイルまで全てタイムスタンプが変わったりしたのでこの方法に落ち着きました。
で、置換されたファイルの内容確認して問題なければbakファイル削除。
$ find . -name '*.bak'
で確認して、
$ find . -name '*.bak' | xargs rm
完了〜。 xargs ラヴ♡
Unix系なら他でも使えると思います。
いいっちゃいいんですが、記憶力がないので思い出せる気がしません。historyがあふれるとオレ終了って感じ(なのでメモという意味もあり)。
ちなみに私がターミナルで一番使うコマンドはブッチギリで
$ history | grep コマンド名
history ラヴ♡♡