はじめに
「JCLって何ですか」と聞かれて、「ジョブ制御言語です」と答えるのは正しい。しかしそれだけでは何もわからない。
JCLを初めて見た人の反応は、大抵こうだ。
//MYJOB JOB (ACCT),'SAMPLE JOB',CLASS=A,MSGCLASS=X
//STEP1 EXEC PGM=IEFBR14
//INFILE DD DSN=MY.INPUT.FILE,DISP=SHR
//OUTFILE DD DSN=MY.OUTPUT.FILE,DISP=(NEW,CATLG,DELETE),
// UNIT=SYSDA,SPACE=(CYL,(5,2),RLSE)
//SYSOUT DD SYSOUT=*
「//で始まる行は何?」「DSNって何?」「DISP=(NEW,CATLG,DELETE)の3つのカッコは?」
最初は暗号に見える。しかし読めるようになると、バッチ処理の設計思想が丸ごと見えてくる。JCLは単なる制御言語ではなく、メインフレームが60年かけて磨いた「大量データ処理のアーキテクチャパターン集」だ。
この記事では、JCLを読めるようになること、そしてそこから何が見えるようになるかを解説する。
1. JCLとは何か──3行で
JCLの役割を一言で言えば、「どのプログラムを・どのファイルを使って・どの順番で動かすか」を記述する指示書だ。
JCLの文(ステートメント)は3種類しかない。
| ステートメント | 役割 | Linuxで言うと |
|---|---|---|
JOB |
ジョブ全体の定義(課金情報・優先度・通知先) | シェルスクリプトのヘッダー部分 |
EXEC |
実行するプログラムまたは手続き(PROC)の指定 | コマンドの呼び出し |
DD |
データ定義(ファイルの論理名と物理名のマッピング) | リダイレクト(<、>)または環境変数 |
この3種類だけで、数十ステップにわたる複雑なバッチ処理を記述する。
Linuxのシェルスクリプトとの根本的な違い
Linuxのシェルスクリプトと比べると、JCLの設計思想の違いが際立つ。
# Linuxシェルスクリプト:手続き的、コードとデータが密結合
#!/bin/bash
INPUT=/data/input.csv
OUTPUT=/data/output.csv
java -jar processor.jar < $INPUT > $OUTPUT
if [ $? -ne 0 ]; then
echo "失敗"
exit 1
fi
//* JCL:宣言的、プログラムとデータが疎結合
//STEP1 EXEC PGM=PROCESSOR
//INFILE DD DSN=MY.INPUT.FILE,DISP=SHR
//OUTFILE DD DSN=MY.OUTPUT.FILE,DISP=(NEW,CATLG,DELETE)
//SYSOUT DD SYSOUT=*
シェルスクリプトはプログラムがファイルパスを直接知っている(密結合)。JCLではプログラムが知るのは論理名(INFILE、OUTFILE)だけで、物理的なファイルの場所はJCLが担う(疎結合)。この設計思想は、現代で言えばDI(依存性の注入)に相当する。
2. JCLを読む──基本構造の解読
実際のJCLを行ごとに読み解く。以下は「入力ファイルを読み込んで処理し、出力ファイルを生成する」という典型的なバッチジョブだ。
//PAYJOB JOB (0001),'PAYROLL BATCH', (1)
// CLASS=B,MSGCLASS=X,NOTIFY=&SYSUID
//***********************************************
//* 給与計算バッチ処理
//***********************************************
//STEP010 EXEC PGM=PAYROLL1 (2)
//TRANSIN DD DSN=PAYROLL.TRANS.INPUT, (3)
// DISP=SHR
//MASTERIO DD DSN=PAYROLL.MASTER.FILE, (4)
// DISP=OLD
//RPTOUT DD SYSOUT=A (5)
//SYSOUT DD SYSOUT=*
//*
//STEP020 EXEC PGM=PAYROLL2, (6)
// COND=(0,NE,STEP010)
//CALCIN DD DSN=PAYROLL.MASTER.FILE, (7)
// DISP=SHR
//TAXOUT DD DSN=PAYROLL.TAX.OUTPUT,
// DISP=(NEW,CATLG,DELETE), (8)
// UNIT=SYSDA,
// SPACE=(TRK,(100,50),RLSE)
//SYSOUT DD SYSOUT=*
注:カラム制限について
実際のJCLでは、継続行は「2〜3列目が//、続くパラメータは16列目以降に記述」「前の行末はカンマで終わる」という厳密なカラム制限がある。本記事では可読性のためインデントを整えているが、実機では桁数を守らないとJCLエラーになる点に注意してほしい。
各行の読み方
(1) JOB文:ジョブの宣言
//PAYJOB JOB (0001),'PAYROLL BATCH',CLASS=B,MSGCLASS=X,NOTIFY=&SYSUID
-
//PAYJOB── ジョブ名(8文字以内) -
JOB── このステートメントがJOB文であることの宣言 -
(0001)── 課金アカウント番号 -
'PAYROLL BATCH'── ジョブの説明(SYSLOG・コンソールに表示される) -
CLASS=B── ジョブクラス(優先度や実行キューの指定) -
MSGCLASS=X── ジョブログの出力先 -
NOTIFY=&SYSUID── 完了通知を送るユーザー(&SYSUIDは実行ユーザーの変数)
(2) EXEC文:プログラムの実行
//STEP010 EXEC PGM=PAYROLL1
-
//STEP010── ステップ名(このジョブ内でユニークな名前) -
EXEC PGM=PAYROLL1──PAYROLL1というロードモジュールを実行する
EXECにはPGM=(プログラム直接指定)とPROC=(手続き呼び出し)の2種類がある。PROCはJCLのサブルーチンに相当し、共通処理を再利用できる。
(3)(4) DD文:データ定義(入力ファイル)
//TRANSIN DD DSN=PAYROLL.TRANS.INPUT,DISP=SHR
//MASTERIO DD DSN=PAYROLL.MASTER.FILE,DISP=OLD
-
//TRANSIN── 論理ファイル名(プログラムはこの名前でファイルを参照する) -
DD── Data Definitionの略 -
DSN=── Data Set Name(物理ファイル名) -
DISP=SHR── 共有アクセス(他ジョブも同時に読める) -
DISP=OLD── 排他アクセス(このジョブだけが使用、他からはアクセス不可)
(5) SYSOUT:帳票・ログ出力
//RPTOUT DD SYSOUT=A
//SYSOUT DD SYSOUT=*
-
SYSOUT=A── 帳票クラスAのプリンタキューへ出力 -
SYSOUT=*── ジョブログと同じ出力クラスへ(コンソール/ログ)
(6) EXEC文(条件付き):前ステップの結果を見て実行を制御
//STEP020 EXEC PGM=PAYROLL2,COND=(0,NE,STEP010)
-
COND=(0,NE,STEP010)── 「0 ≠ STEP010のRC」が真のとき、このステップをスキップする
つまり「STEP010が正常終了(RC=0)したときだけSTEP020を実行する」という条件制御だ。CONDパラメータについては次節で詳しく解説する。
(7)(8) DD文(出力ファイル):ファイルのライフサイクル定義
//TAXOUT DD DSN=PAYROLL.TAX.OUTPUT,
// DISP=(NEW,CATLG,DELETE),
// UNIT=SYSDA,
// SPACE=(TRK,(100,50),RLSE)
-
DISP=(NEW,CATLG,DELETE)── 3つの状態指定(後述) -
UNIT=SYSDA── ディスク装置タイプの指定 -
SPACE=(TRK,(100,50),RLSE)── トラック単位で100トラック確保、追加は50トラック、未使用は解放
3. JCLが見せる「バッチ処理の設計思想」
CONDパラメータ──ステップ間の条件制御
JCLのCONDパラメータは、前のステップの結果を見て後続ステップの実行を制御する仕組みだ。
//STEP010 EXEC PGM=VALIDATE
//STEP020 EXEC PGM=TRANSFORM,
// COND=(0,NE,STEP010)
//STEP030 EXEC PGM=LOAD,
// COND=((0,NE,STEP010),(0,NE,STEP020))
//STEP040 EXEC PGM=NOTIFY,
// COND=(0,LT)
CONDの評価式の構造は COND=(指定値, 演算子, ステップ名) で、評価は「指定値 [演算子] 実際のRC」という順序になる。
COND=(4,LT,STEP010) の評価:
「4 < STEP010のRC」が真 → スキップ
つまり「RCが4より大きければスキップ」
CONDの演算子と意味:
| 演算子 | 評価式 | スキップする条件 |
|---|---|---|
EQ |
指定値 == RC | 指定値とRCが等しいとき |
NE |
指定値 != RC | 指定値とRCが等しくないとき |
LT |
指定値 < RC | RCが指定値より大きいとき |
GT |
指定値 > RC | RCが指定値より小さいとき |
LE |
指定値 <= RC | RCが指定値以上のとき |
GE |
指定値 >= RC | RCが指定値以下のとき |
CONDパラメータには2つの直感に反するポイントがある。
① 条件が真のとき「実行する」ではなく「スキップする」
COND=(0,NE,STEP010)は「0 ≠ STEP010のRC」が真(=RCが0以外)のときにスキップ。裏を返せば「STEP010がRC=0(正常)のときだけ実行する」となる。
② 評価の主語は「指定値」
COND=(4,LT,STEP010)は「4 < RCのときスキップ」だ。「RCが4より小さいときスキップ」ではない。指定値が左辺になることを意識しないと、LT・GTの読み方が逆になる。
この2点を頭に入れておくと、どんなCOND指定も迷わず読める。
DISPパラメータ──ファイルのライフサイクル管理
DISPパラメータの3つの値は、ファイルのライフサイクル全体を定義する。
DISP=(開始時の状態, 正常終了時の処置, 異常終了時の処置)
| 開始時の状態 | 意味 |
|---|---|
NEW |
新規作成 |
OLD |
既存・排他アクセス |
SHR |
既存・共有アクセス |
MOD |
既存に追記、なければ新規作成 |
| 終了時の処置 | 意味 |
|---|---|
CATLG |
カタログに登録(次回も名前でアクセス可能) |
UNCATLG |
カタログから削除(ファイル自体は残る) |
DELETE |
ファイルを削除 |
KEEP |
そのまま保持(カタログ操作なし) |
PASS |
次のステップへ引き渡す(ジョブ内でのみ有効) |
典型的なパターンの読み方:
DISP=(NEW,CATLG,DELETE) ── 新規作成。正常終了でカタログ登録(次回アクセス可)、異常終了で削除(不完全なゴミを残さない)
DISP=(OLD,KEEP,KEEP) ── 排他アクセス。正常でも異常でもファイルはそのまま保持
DISP=(SHR,PASS,DELETE) ── 共有で読んで次のステップへ引き渡す。異常終了なら削除
DISP=(MOD,CATLG,CATLG) ── 追記。正常でも異常でもカタログに残す
DISP=(NEW,CATLG,DELETE)というパターンを見たとき、それは「このファイルは中間生成物で、正常終了時は次のジョブで使うために登録しておき、異常終了時は不完全なファイルをゴミとして残さない」という設計意図を示している。コードの1行から、設計者のリカバリー戦略が読み取れる。
DDNAMEによる論理名と物理名の分離
JCLの最も重要な設計原則が、プログラムはファイルの論理名しか知らないという疎結合だ。
プログラム内のコード:
READ FROM INFILE ← 論理名だけ知っている
JCL側の定義:
//INFILE DD DSN=PROD.INPUT.DATA.2026,DISP=SHR
↑ 物理的なファイル名はここで定義
環境(本番/テスト/DR)が変わるときも、プログラムを変更する必要はない。JCLのDSN指定を変えるだけでよい。
Javaで言えばこれは依存性の注入(DI)そのものだ:
// Javaの場合(SpringのDI)
@Autowired
private DataSource dataSource; // ← 論理名だけ知っている
// 実際のDBは設定ファイル(application.properties)で定義
//* JCLの場合
//INFILE DD DSN=PROD.INPUT.DATA,DISP=SHR
//* ↑ 環境設定(application.propertiesに相当)
JCLはSpringが登場する何十年も前から、この分離を実践していた。
4. JCLを読むと「移行の難しさ」がわかる
ステップ依存関係はDAGとして読める
複数ステップのJCLを図にすると、**有向非巡回グラフ(DAG)**として表現できる。
STEP010(バリデーション)
│ RC=0のみ通過
▼
STEP020(変換処理A) STEP030(変換処理B)
│ RC=0のみ通過 │ RC=0のみ通過
└──────────┬────────────┘
▼
STEP040(マージ・ロード)
│
▼
STEP050(通知・クリーンアップ)
※ RC>0でも実行(COND省略)
このDAGをモダンなワークフローエンジン(Apache Airflow、AWS Step Functions)に移行するとき、CONDパラメータの条件を正確に再現する必要がある。「評価の主語は指定値」「真のときスキップ」という2つのポイントを誤解すると、異常時の分岐が逆転したまま動いてしまう。
RESTARTパラメータが示すチェックポイント設計
JCLのJOB文にはRESTARTパラメータがある。
//PAYJOB JOB (0001),'PAYROLL BATCH',RESTART=STEP030
これは「STEP030から再実行する」という指定だ。10ステップのジョブが7ステップ目で失敗した場合、最初からやり直すのではなく、任意のステップから再開できる。
この仕組みは、現代のCI/CDパイプラインの「失敗したジョブだけ再実行」に相当するが、JCLは1960年代からこれを実装していた。
移行設計でここを見落とすと、失敗時の再実行手順が設計されないまま本番稼働することになる。
PASSとTEMPファイル──ステップ間のデータ受け渡し
ステップ間でデータを受け渡すパターンとして、PASSと一時ファイル(TEMP)がある。
//STEP010 EXEC PGM=EXTRACT
//TEMPDATA DD DSN=&&TEMP,
// DISP=(NEW,PASS,DELETE),
// UNIT=SYSDA,SPACE=(CYL,(10,5))
&&TEMP(&&で始まる名前が一時ファイル)はSTEP010で生成され、DISP=(NEW,PASS,DELETE)によって次のステップへ引き渡される。異常終了時は削除される。
//STEP020 EXEC PGM=TRANSFORM
//TEMPDATA DD DSN=&&TEMP,
// DISP=(OLD,DELETE,DELETE)
STEP020が&&TEMPを受け取り、使い終わったら正常/異常を問わず削除する。
この一時ファイルはジョブの外からはアクセスできない、ステップ間の「パイプ」として機能している。
Linuxのパイプ(|)との違いは、JCLの一時ファイルがディスクを経由することだ。前のステップが完全に書き終わってから次のステップが読み始める。これが「シーケンシャルなバッチ処理」の本質であり、大量データをメモリに持たずに処理できる理由でもある。
5. JCLをモダンなパイプラインと対比する
JCLの概念は、現代のCI/CD・ワークフローエンジンと構造的に対応している。
| JCL | GitHub Actions | Apache Airflow | 説明 |
|---|---|---|---|
| JOB | workflow | DAG | 処理全体の定義 |
| STEP | job / step | Task | 処理の単位 |
COND=(0,NE) |
if: steps.X.outcome == 'success' |
trigger_rule='all_success' |
前段の結果による実行制御 |
DD DSN= |
env: FILE_PATH= |
op_kwargs={'path': ...} |
外部リソースの注入 |
DISP=(NEW,CATLG,DELETE) |
アーティファクトのアップロード/クリーンアップ | XCom / S3 | 中間成果物の管理 |
RESTART=STEP030 |
jobs.<id>.needs + 再実行 |
depends_on_past |
チェックポイントからの再開 |
SYSOUT=* |
runs-on のログ出力 |
log |
実行ログの管理 |
| PROCの呼び出し | Reusable Workflow | SubDAG / TaskGroup | 共通処理の再利用 |
GitHub Actionsで書き直すと
先ほどのJCLサンプルをGitHub Actionsのイメージに翻訳すると:
# GitHub Actions(JCLの給与計算バッチに相当するイメージ)
name: Payroll Batch
on:
schedule:
- cron: '0 1 * * *' # 毎日午前1時
jobs:
validate: # STEP010: EXEC PGM=PAYROLL1
runs-on: ubuntu-latest
steps:
- name: Run validation
run: java -jar payroll1.jar
env:
TRANSIN: s3://payroll/trans/input # DD TRANSIN
MASTERIO: s3://payroll/master/file # DD MASTERIO
transform: # STEP020: EXEC PGM=PAYROLL2
needs: validate # COND=(0,NE,STEP010) に相当
if: success() # 正常終了のみ実行
runs-on: ubuntu-latest
steps:
- name: Run transformation
run: java -jar payroll2.jar
JCLとGitHub Actionsは記法がまったく異なるが、表現している設計パターンは同一だ。
この対比がわかると、JCLの移行先としてAirflowやStep Functionsを選んだとき、どのパラメータを何に対応させるかが自然に見えてくる。
JCLが読めると何がわかるのか──まとめ
JCLを読めるようになると、以下のことが見えてくる。
技術的に見えること
- バッチジョブの依存関係がDAGとして可視化できる
- 各ステップのリカバリー設計(COND・RESTART・DISP)が読み取れる
- ファイルのライフサイクル(作成・保持・削除)が明示されている
設計思想として見えること
- プログラムとデータの疎結合(論理名と物理名の分離)
- 中間データの管理戦略(PASS・一時ファイル・カタログ)
- 異常時の振る舞いが正常時と同じ粒度で設計されている
移行設計に役立つこと
- CONDパラメータの条件をAirflow/Step Functionsに正確にマッピングできる
- DISPパラメータからS3ライフサイクルポリシーの設計ができる
- RESTARTパラメータからチェックポイント設計を再構築できる
JCLは「古い記法の制御ファイル」ではない。60年間、止まらず動き続けてきたバッチ処理のベストプラクティスが、宣言的な形式で結晶化したものだ。
おわりに
JCLを読めるようになることは、メインフレームへの「入門」ではない。大量データ処理の設計思想を、その源流から理解することだ。
今日のCI/CDパイプライン、ワークフローエンジン、データパイプラインが解決しようとしている問題──依存関係の制御、チェックポイントからの再開、中間データの管理──は、JCLがすでに1960年代に解いていた問題と同じだ。
読めなかったJCLが読めるようになったとき、現代のアーキテクチャが「新しいもの」ではなく「同じ問題への別の答え」として見えてくる。
本記事ではJCLの読み方と設計思想の概要を扱ったが、**「富士通・日立・IBM各社の撤退タイムラインと現場への影響」「2030/2035年問題のキャパシティ・クランチ」**など、日本のメインフレームの現状と課題については、noteの連載で詳しく論じている。