COBOL Module 12 – Mainframe batch concepts: data sets, JCL and compilation (IBM z/OS)
JCL jobs, steps and DD statements
Read the JOB, EXEC and DD statements of a z/OS batch job, connect COBOL ASSIGN names to DD statements, and run the same steps locally with GnuCOBOL.
What you will learn
- Read the JOB, EXEC and DD statements of a batch job, field by field
- Connect a COBOL ASSIGN name to its DD statement and run the step locally with a DD_ variable
- Choose DISP values that keep, catalog or delete a data set when a step ends well or badly
- Size a new data set with SPACE and DCB, and use SYSOUT, instream data, DUMMY and PARM
Before you start
What you’ll learn · Before you start
What you will learn
- Read the JOB, EXEC and DD statements of a batch job, field by field
- Connect a COBOL ASSIGN name to its DD statement and run the step locally with a DD_ variable
- Choose DISP values that keep, catalog or delete a data set when a step ends well or badly
- Size a new data set with SPACE and DCB, and use SYSOUT, instream data, DUMMY and PARM
Before you start
On this page
A nightly posting job of 12 JCL statements runs two COBOL programs, and every file either program touches is named once, in a DD statement, never in the programs. JCL jobs like it carry the batch work of z/OS, and reading their statements tells you what each job reads, writes and keeps.
This lesson reads the job DAILYPST statement by statement, connects each ASSIGN name to its DD statement, and runs the same two steps with GnuCOBOL, where environment variables play the part of the DD statements.
A job is a JOB statement, then steps of EXEC and DD statements
A JCL statement has fields: the identifier // in columns 1 and 2, a name of 1 to 8 letters, digits or $ # @, the
first not a digit, starting in column 3, then, after a blank, the operation, its parameters separated by commas, and
comments. Nothing goes past column 71. A line that starts //* is a comment, and /* ends instream data. A statement
too long for one line stops after a comma and continues on the next line, which leaves column 3 blank and resumes in
columns 4 to 16.
Three operations build JCL jobs. A job: A unit of z/OS batch work: one JOB statement and the steps after it, submitted to JES as one piece. When it ends, its job log lists every step with its condition code. starts with one JOB statement. Each job step: One program run inside a job: an EXEC statement that names the program, then the DD statements that give it its data sets. The program's return code becomes the step's condition code. starts with an EXEC statement that names a program, and the DD statements after it are that step’s files, one per ddname the program uses. The job DAILYPST has two steps, POST and REPORT:
//DAILYPST JOB (ACCT),'DAILY POSTING',CLASS=A,MSGCLASS=X
//* Concept listing, not run: posts the day's transactions, then
//* prints the totals of each branch from what was posted.
//POST EXEC PGM=POSTTXN
//STEPLIB DD DISP=SHR,DSN=BANK.PROD.LOAD
//TXNIN DD DISP=SHR,DSN=BANK.PROD.TXN.DAILY(0)
//POSTED DD DSN=BANK.PROD.POSTED(+1),
// DISP=(NEW,CATLG,DELETE),
// SPACE=(TRK,(10,5),RLSE),
// DCB=(RECFM=FB,LRECL=22)
//SYSOUT DD SYSOUT=*
//REPORT EXEC PGM=BRREPORT
//STEPLIB DD DISP=SHR,DSN=BANK.PROD.LOAD
//POSTED DD DISP=SHR,DSN=BANK.PROD.POSTED(+1)
//BRANCHES DD *
MUM MUMBAI
PUN PUNE
/*
//BRREPT DD SYSOUT=*
//SYSOUT DD SYSOUT=* jcl_reader.py checks a deck against those rules before anyone submits it, and compares each step’s DD statements with the ASSIGN names of its program. It reads the job as written, then a copy with four mistakes:
"""Reads the subset of JCL this lesson teaches and checks a deck before anyone submits it: the fields of every
statement (z/OS MVS JCL Reference, JCL statement fields), continuations, the steps and their DD statements, and
whether every ASSIGN name of each step's COBOL program has a DD statement. It runs nothing on z/OS.
Rules checked: // in columns 1 and 2; a name in column 3 of 1 to 8 letters, digits or # @ $, the first not a digit;
an operation of JOB, EXEC or DD; nothing past column 71; a statement whose parameters end with a comma continues on the
next line, which leaves column 3 blank. A DD with no name adds a data set to the DD before it (a concatenation).
"""
import re
import sys
from pathlib import Path
NAME = re.compile(r"[A-Z#@$][A-Z0-9#@$]{0,7}$")
ASSIGN = re.compile(r"ASSIGN\s+TO\s+\"?([A-Z0-9#@$-]+)\"?")
SYSTEM_DDS = {"STEPLIB", "SYSOUT", "SYSPRINT", "SYSUDUMP", "CEEDUMP"}
def read(path):
problems, steps, statements, data_lines = [], [], 0, 0
continuing, instream, seen_job = False, False, False
for n, line in enumerate(Path(path).read_text().splitlines(), start=1):
if line.startswith("//*"):
continue
if line.startswith("/*"):
instream = False
continue
if not line.startswith("//"):
if instream:
data_lines += 1
else:
problems.append(f"line {n}: not a JCL statement and not instream data")
continue
instream = False
if len(line) > 71 and line[71:].strip():
problems.append(f"line {n}: text past column 71")
body = line[2:71].rstrip()
if continuing:
if not body.startswith(" "):
problems.append(f"line {n}: a continuation leaves column 3 blank")
continuing = body.strip().endswith(",")
continue
statements += 1
name, rest = ("", body.strip()) if body.startswith(" ") else (body.split(" ", 1) + [""])[:2]
op, _, operand = rest.strip().partition(" ")
operand = operand.strip().split(" ")[0]
if name and len(name) > 8:
problems.append(f"line {n}: the name {name} has {len(name)} characters: at most 8")
elif name and not NAME.match(name):
problems.append(f"line {n}: the name {name} must start with a letter or # @ $")
if op not in ("JOB", "EXEC", "DD"):
hint = "did the line before end without a comma?" if "=" in op else "is the name in column 3?"
problems.append(f"line {n}: {op} is not JOB, EXEC or DD: {hint}")
continue
if op == "JOB":
seen_job = True
elif not seen_job:
problems.append(f"line {n}: the deck does not start with a JOB statement")
if op == "EXEC":
program = re.search(r"PGM=([A-Z0-9#@$]+)", operand)
steps.append({"name": name or "(no name)", "pgm": program.group(1) if program else "?", "dds": []})
elif op == "DD" and name and steps:
if name in steps[-1]["dds"]:
problems.append(f"line {n}: step {steps[-1]['name']} has a second DD statement {name}")
steps[-1]["dds"].append(name)
instream = operand.startswith("*")
continuing = operand.endswith(",")
return problems, steps, statements, data_lines
def assigns(program):
source = Path(program.lower() + ".cob")
return ASSIGN.findall(source.read_text()) if source.exists() else None
for deck in sys.argv[1:]:
problems, steps, statements, data_lines = read(deck)
print(f"{deck}: {statements} statements, {data_lines} lines of instream data")
for step in steps:
print(f" step {step['name']:8} PGM={step['pgm']:9} DD {' '.join(step['dds'])}")
names = assigns(step["pgm"])
if names is None:
continue
for missing in [a for a in names if a not in step["dds"]]:
problems.append(f"step {step['name']}: {step['pgm']} assigns {missing}, and the step has no DD {missing}")
for unused in [d for d in step["dds"] if d not in names and d not in SYSTEM_DDS]:
problems.append(f"step {step['name']}: no ASSIGN of {step['pgm']} names the DD {unused}")
for p in problems:
print(f" problem: {p}")
print(f" {len(problems)} problem(s)") Output
daily-posting.jcl: 12 statements, 2 lines of instream data step POST PGM=POSTTXN DD STEPLIB TXNIN POSTED SYSOUT step REPORT PGM=BRREPORT DD STEPLIB POSTED BRANCHES BRREPT SYSOUT 0 problem(s) posting-bad.jcl: 13 statements, 2 lines of instream data step POST PGM=POSTTXN DD STEPLIB TXIN POSTED SYSOUT STEPLIB POSTED BRANCHES BRREPORTS SYSOUT problem: line 9: DCB=(RECFM=FB,LRECL=22) is not JOB, EXEC or DD: did the line before end without a comma? problem: line 11: REPORT is not JOB, EXEC or DD: is the name in column 3? problem: line 12: step POST has a second DD statement STEPLIB problem: line 13: step POST has a second DD statement POSTED problem: line 18: the name BRREPORTS has 9 characters: at most 8 problem: line 19: step POST has a second DD statement SYSOUT problem: step POST: POSTTXN assigns TXNIN, and the step has no DD TXNIN problem: step POST: no ASSIGN of POSTTXN names the DD TXIN problem: step POST: no ASSIGN of POSTTXN names the DD BRANCHES problem: step POST: no ASSIGN of POSTTXN names the DD BRREPORTS 10 problem(s)
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 jcl_reader.py daily-posting.jcl posting-bad.jcl
Four mistakes made ten problems. The missing comma at the end of line 8 turned the DCB line into a statement of its own, and the blank in column 3 of line 11 hid the step name REPORT, so every DD statement after it landed in step POST and five more problems followed from that one. Fix the first problem, then check again. Step through the job as the system runs it:
The drawing shows the whole scene; its 7 steps are listed below.
Every step as text
- The job. The JOB statement names the job, DAILYPST, and gives JES its accounting field, a programmer name, the job class A and the message class X for the job log.
- Step POST. An EXEC statement starts the first step and names its program. The step is called POST, and the program POSTTXN is found in the load library that STEPLIB names.
- The step's files. The DD statements after the EXEC are the files of this step only. TXNIN gives the program today's transactions, POSTED a new data set to write, SYSOUT the spool for its DISPLAY lines.
- The step ends. POSTTXN rejected two records and ended with return code 4. The system now deals with each data set as its DISP says and frees the step's files.
- Step REPORT. The next EXEC starts step REPORT with program BRREPORT. Steps run one after another, so REPORT can read the data set POST has just written.
- Its own files. REPORT has DD statements of its own. POSTED names the same data set as in POST, BRANCHES carries the instream lines up to /*, and BRREPT sends the report to the spool.
- The job log. When the last step ends, the job log lists each step with its condition code, POST 0004 and REPORT 0000, beside the SYSOUT data sets.
ASSIGN names a ddname, and the DD statement names the data set
POSTTXN says SELECT TXN-FILE ASSIGN TO TXNIN. TXNIN is a ddname: The name of a DD statement in a z/OS job step. A COBOL program's ASSIGN clause names a ddname, and the DD statement says which data set the program gets under that name. More in “Sequential files, records and FILE STATUS”: when the program opens
the file, the system finds the step’s DD statement named TXNIN and gives the program the data set it names. IBM’s Basic
Skills make the point that, because the program never names the data set, a change of name or place needs a change to
the JCL and no new compile. GnuCOBOL reads a DD_ variable in the DD statement’s place:
from session import files, run
# The two steps of job DAILYPST run with GnuCOBOL: each DD statement of daily-posting.jcl becomes a DD_ variable that
# names a file of this folder, and each program's exit status is its step's return code.
files("posttxn.cob", "brreport.cob", "txn.txt", "branches.txt")
run("""
cobc -x posttxn.cob
cobc -x brreport.cob
!DD_TXNIN=txn.txt DD_POSTED=posted.txt ./posttxn
cat posted.txt
DD_POSTED=posted.txt DD_BRANCHES=branches.txt DD_BRREPT=brrept.txt ./brreport
cat brrept.txt
# The REPORT step again, with no DD for BRANCHES
!DD_POSTED=posted.txt DD_BRREPT=brrept.txt ./brreport
""") Output
$ cobc -x posttxn.cob $ cobc -x brreport.cob $ DD_TXNIN=txn.txt DD_POSTED=posted.txt ./posttxn POSTTXN: rejected PUN AC0004 X 000010000 POSTTXN: rejected PUN AC0003 C 00009A000 POSTTXN: read 00006, posted 00004, rejected 00002 [exit status 4] $ cat posted.txt MUM AC0001 C 000250000 PUN AC0003 D 000045050 MUM AC0002 C 000120000 MUM AC0001 D 000002500 $ DD_POSTED=posted.txt DD_BRANCHES=branches.txt DD_BRREPT=brrept.txt ./brreport BRREPORT: 2 branches printed $ cat brrept.txt BRANCH CREDITS DEBITS MUMBAI 3,700.00 25.00 PUNE 0.00 450.50 # The REPORT step again, with no DD for BRANCHES $ DD_POSTED=posted.txt DD_BRREPT=brrept.txt ./brreport BRREPORT: OPEN statuses 35 00 00 [exit status 12]
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 posting_session.py
POSTTXN posted 4 of 6 transactions, rejected 2 and ended with exit status 4, which on z/OS is the step’s return code. BRREPORT then totalled the posted file by branch. Run again with no DD_BRANCHES, its OPEN got file status 35: IBM’s OPEN notes say a file is available only with a DD allocation, and an unavailable input ends OPEN with status 35. Step through both lookups:
The drawing shows the whole scene; its 5 steps are listed below.
Every step as text
- The program's side. POSTTXN says SELECT TXN-FILE ASSIGN TO TXNIN. TXNIN is a ddname, not a file name, so the program never knows which data set it reads.
- The DD statement. When OPEN runs on z/OS, the system looks for the DD statement of the step whose name is TXNIN, and it finds //TXNIN DD.
- The data set. The DD statement names the data set, the newest generation of BANK.PROD.TXN.DAILY, which the catalog finds. A test run changes this line, never the program.
- On this computer. GnuCOBOL looks for an environment variable in the DD statement's place. DD_TXNIN=txn.txt sends the same OPEN to a file of this folder.
- No DD at all. Without a DD statement on z/OS, or a DD_ variable here, the input is not present, and OPEN INPUT ends with file status 35. BRREPORT showed it for BRANCHES.
The DD statement says more than a name. Each of its parameters has one job:
| Parameter | What it says |
|---|---|
| DSN | the data set’s name, with a member or a generation in brackets |
| DISP | whether the data set exists, and what happens to it when the step ends well or fails |
| SPACE | how much disk a new data set gets, and how much more each time it fills |
| DCB | the record format, record length and block size of a new data set |
| SYSOUT | output for the spool, in an output class, instead of a data set |
| DD * | the lines that follow, up to /*, are the data: instream data |
| DUMMY | no data set at all: a read finds the end at once |
| PARM, on EXEC | a string of up to 100 characters for the program |
Key idea
The program names ddnames; the DD statements of each step name the data sets. Change the JCL, or the DD_ variables here, and the same program reads other data.
Checkpoint
Check: JOB, EXEC and DD statements and their fields · ASSIGN names, ddnames and DD_ variables
Practise: read the job and its steps
These questions use daily-posting.jcl, jcl_reader.py and the two runs of the steps.
Practice that adapts to you
Practise this lesson
5 questions about 6 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
DISP at the end of a step: what is kept, cataloged or deleted, with a failure reproduced Pro: opens with a Pro, Premium or Ultimate pass
DISP decides what is left behind when a step ends, and a wrong value turns one failed run into two problems. This part works through every position of DISP, reproduces a failed step with two different values, and drills the defaults that catch people out.
In this part:
- Three positions, and the defaults that fill the gaps
- A step that fails halfway, under two DISP values
- A drill on the defaults, with sample tests
- Work out what DISP does to a data set when a step ends
- Results of the sample tests
3 runnable examples (COB, Python, Shell) · 1 exercise with sample tests · about 9 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
SPACE, DCB, SYSOUT, instream data, DUMMY and PARM, with the numbers worked out Pro: opens with a Pro, Premium or Ultimate pass
A new data set needs a size and a shape, and a step needs places for its printed output, its control data and its options. This part sizes the posting file on a 3390 disk, makes the two space abends happen on paper, and runs DUMMY and PARM on this computer.
In this part:
- Sizing SPACE and BLKSIZE for 1,200,000 records, with the numbers
- SYSOUT, instream data and DUMMY: output and input without a data set
- PARM, from the EXEC statement into the LINKAGE SECTION
1 diagram · 4 runnable examples (COB, Python) · about 8 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
Interview questions
A warm-up answered in full, then three questions about jobs that failed or quietly did the wrong thing:
-
Fresher to mid · Warm-up
How does a COBOL program on z/OS find the data set it reads, and why can the data set change without a new compile?
Through a ddname. The program says SELECT TXN-FILE ASSIGN TO TXNIN and never names a data set. When it opens the file, the system looks in the job step for the DD statement named TXNIN, and that statement names the data set, such as the newest generation of BANK.PROD.TXN.DAILY, which the catalog finds. Because the data set's name lives only in the JCL, a test job can point the same DD at test data, or an administrator can rename or move the data set, and only the JCL changes. If the step has no DD statement for an input, the file is not present and OPEN ends with status 35. GnuCOBOL does the same locally through a DD_TXNIN environment variable.
-
Mid to senior
An extract step writes a new data set with DISP=(NEW,CATLG,CATLG). Last night it abended at record 4 of 5, yet the next step ended with return code 0 and the totals downstream were short. Explain what happened, how you would change the job, and how you clean up.
-
Mid to senior
A production posting job ended with return code 0 every night for a week, yet nothing was posted. Its input DD had been changed to DD DUMMY in a test copy that was promoted by mistake. Why was the run silent, and how do you make such a mistake fail loudly?
-
Fresher to mid
A step that writes the daily posting file with SPACE=(TRK,(100,50)) has run for years. After two branches merged, the day's file grew from 400,000 to 1,200,000 records of 80 bytes, and the step abended with B37. Explain the abend with numbers, and size the new SPACE.
Organisations named on this page
IBM’s z/OS and Enterprise COBOL manuals are the source of the JCL, DISP and SPACE rules on this page, and the runs used GnuCOBOL 3.2 and Python. MySmartCoPilot is independent of IBM and of the GnuCOBOL project.
Flashcards · 7 cards
Recall it, then check
Say the answer to yourself before you look: recalling it is what makes it stick.
-
What are the fields of a JCL statement?
// in columns 1 and 2, a name of 1 to 8 characters starting in column 3 whose first is not a digit, then the operation, its parameters separated by commas, and comments. Nothing goes past column 71.
-
How is a JCL statement continued on the next line?
Stop after a comma, then leave column 3 of the next line blank and carry on in columns 4 to 16. //* starts a comment line, and /* ends instream data.
-
What do the JOB, EXEC and DD statements do?
One JOB statement starts the job. Each EXEC starts a step and names its program, and the DD statements after it are that step's files, one for each ddname the program uses.
-
What connects SELECT ... ASSIGN TO TXNIN to a data set on z/OS?
TXNIN is a ddname. At OPEN the system finds the step's DD statement named TXNIN and hands the program the data set it names, so a new data set needs only a change to the JCL.
-
What happens when a step's input file has no DD statement at all?
The file is not available and OPEN ends with status 35. Locally, a missing DD_ variable and file gave the same status.
-
What do DISP, SPACE and DCB say on a DD statement?
DISP: whether the data set exists, and what happens to it when the step ends well or fails. SPACE: how much disk a new one gets, and how much more each time it fills. DCB: its record format, record length and block size.
-
What do DD *, DUMMY and SYSOUT mean on a DD statement?
DD *: the lines that follow, up to /*, are the data. DUMMY: no data set at all, so reading it gives the end of the file at once. SYSOUT: output for the spool, in an output class.
Key takeaways
- A job is one JOB statement and steps; each EXEC starts a step and names its program, and the DD statements after it are that step’s files.
- A statement has the identifier, a name of up to 8 characters in column 3, the operation and its parameters, nothing past column 71, and a continuation after a trailing comma.
- ASSIGN names a ddname, the DD statement names the data set, and GnuCOBOL’s DD_ variables do the DD statement’s job locally; an input with no DD at all fails OPEN with status 35.
Terms in this lesson
- extent
- One piece of disk space, a run of whole tracks side by side, that belongs to a data set. A data set starts with the space of its primary amount and gains an extent each time a secondary amount is added.
- job
- A unit of z/OS batch work: one JOB statement and the steps after it, submitted to JES as one piece. When it ends, its job log lists every step with its condition code.
- job step
- One program run inside a job: an EXEC statement that names the program, then the DD statements that give it its data sets. The program's return code becomes the step's condition code.
References
- z/OS 3.2 MVS JCL Reference: JCL statement fields (IBM)
- z/OS 3.2 MVS JCL Reference: Continuing the parameter field (IBM)
- z/OS 3.2 MVS JCL Reference: JOB statement (IBM)
- z/OS 3.2 MVS JCL Reference: EXEC statement, PGM parameter (IBM)
- z/OS Basic Skills: COBOL relationship between JCL and program files (IBM)
- Enterprise COBOL for z/OS 6.5: OPEN statement notes (IBM)
- Enterprise COBOL for z/OS 6.5: OUTDD compiler option (IBM)
- z/OS 3.2 MVS JCL Reference: DISP parameter defaults (IBM)
- z/OS 3.2 MVS JCL Reference: Summary of disposition processing (IBM)
- z/OS 3.2 MVS JCL Reference: SPACE parameter, system assignment of space (IBM)
- z/OS 3.2 DFSMS Using Data Sets: Basic access methods track capacity (IBM)
- z/OS 3.2 DFSMS Using Data Sets: System-determined block size (IBM)
- z/OS 3.2 MVS System Messages: IEC030I (B37) (IBM)
- z/OS 3.2 MVS System Messages: IEC031I (D37) (IBM)
- z/OS 3.2 MVS JCL User's Guide: Printing job log and sysout data sets together (IBM)
- z/OS 3.2 MVS JCL Reference: DUMMY parameter (IBM)
- z/OS 3.2 MVS JCL Reference: PARM parameter syntax (IBM)
- Enterprise COBOL for z/OS 6.5 Programming Guide: Accessing main program parameters under z/OS (IBM)
- GnuCOBOL 3.2 default.conf: filename-mapping (GnuCOBOL project (GNU))
Report a problem with this lesson
Kept only in this browser. Your Learn progress