COBOL Module 12 – Mainframe batch concepts: data sets, JCL and compilation (IBM z/OS)
JCL procedures, symbols and conditional steps
Run JCL steps on the return codes of earlier ones with IF/THEN/ELSE or COND, and turn steps into procedures whose symbols change per call.
What you will learn
- Decide which steps of a job run from the return codes of earlier steps, with IF/THEN/ELSE/ENDIF
- Connect a COBOL program's RETURN-CODE to its step's return code and the steps that follow
- Write a procedure with symbolic parameters and call it with other values
- Read COND tests as bypass tests and rewrite them with IF/THEN/ELSE without changing the job
- Modify a procedure's statements from the calling job, and refer to GDG generations safely on a restart
Before you start
What you’ll learn · Before you start
What you will learn
- Decide which steps of a job run from the return codes of earlier steps, with IF/THEN/ELSE/ENDIF
- Connect a COBOL program's RETURN-CODE to its step's return code and the steps that follow
- Write a procedure with symbolic parameters and call it with other values
- Read COND tests as bypass tests and rewrite them with IF/THEN/ELSE without changing the job
- Modify a procedure's statements from the calling job, and refer to GDG generations safely on a restart
Before you start
On this page
A nightly job of five steps must post only when validation ended with a return code of 4 or less, print totals only when nothing failed, and tell someone when anything ended with 8 or more. Two IF statements decide all three, and the same posting steps serve test and production through one procedure.
This lesson runs a chain of COBOL steps whose return codes decide what runs next, then writes the posting steps once as a procedure whose symbols change with each call.
A step’s return code decides which steps run next
conditional step: A job step that runs or is skipped by the return codes of the steps before it, under an IF/THEN/ELSE/ENDIF construct or a COND parameter. run or are skipped by the return codes of the steps before them. A COBOL program sets RETURN-CODE: A special register that a program sets to report how it ended. When the run ends, its value goes to the operating system as the exit status, which a shell shows in $?. More in “Install GnuCOBOL, compile with cobc and run programs”, and when it ends, that value becomes its step’s return code, which the job log shows as the condition code. The IF/THEN/ELSE/ENDIF statements of JCL then test it: IBM’s reference says RC means the highest return code of the steps that have run so far, and a step that did not start or ended abnormally is not counted. nightly.jcl uses two such tests:
//NIGHTLY JOB (ACCT),'NIGHTLY POSTING',CLASS=A,MSGCLASS=X
//* Concept listing, not run: an instream procedure with two
//* symbols, called for production, and steps run on return codes.
//POSTPROC PROC HLQ=BANK.TEST,RUNMODE=TRIAL
//POST EXEC PGM=POSTTXN,PARM='MODE=&RUNMODE'
//STEPLIB DD DISP=SHR,DSN=&HLQ..LOAD
//TXNIN DD DISP=SHR,DSN=&HLQ..TXN.DAILY(0)
//POSTED DD DSN=&HLQ..POSTED(+1),DISP=(NEW,CATLG,DELETE),
// SPACE=(CYL,(115,12),RLSE),DCB=(RECFM=FB,LRECL=22)
//SYSOUT DD SYSOUT=*
// PEND
//EXTRACT EXEC PGM=EXTRACT
//VALIDATE EXEC PGM=VALIDATE
// IF (RC <= 4) THEN
//RUN EXEC POSTPROC,HLQ=BANK.PROD,RUNMODE=FULL
// ENDIF
// IF (RC < 8) THEN
//REPORT EXEC PGM=BRREPORT
// ELSE
//NOTIFY EXEC PGM=NOTIFY
// ENDIF Conditional steps can be tried on this computer, where an exit status plays the return code. stepend.cob stands in for each step’s program, and run_chain.sh applies the two IF statements, once for a night where POST ends with 0 and once for one where it ends with 12:
IDENTIFICATION DIVISION.
PROGRAM-ID. STEPEND.
* Stands in for the program of one job step: it is given the
* step's name and a return code, and ends with that code in
* RETURN-CODE, as a real program does when it is done.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 COMMAND-TEXT PIC X(40).
01 STEP-NAME PIC X(8).
01 CODE-TEXT PIC X(4).
01 STEP-CODE PIC 9(4).
PROCEDURE DIVISION.
ACCEPT COMMAND-TEXT FROM COMMAND-LINE
UNSTRING COMMAND-TEXT DELIMITED BY ALL SPACE
INTO STEP-NAME CODE-TEXT
END-UNSTRING
COMPUTE STEP-CODE = FUNCTION NUMVAL (CODE-TEXT)
DISPLAY " " STEP-NAME " runs and sets RETURN-CODE "
STEP-CODE
MOVE STEP-CODE TO RETURN-CODE
GOBACK. from session import files, run
# Job NIGHTLY's five steps on this computer: stepend.cob stands in for each step's program, and run_chain.sh applies
# the IF/THEN/ELSE/ENDIF statements of nightly.jcl. First a night where POST ends with 0, then one where it ends with 12.
files("stepend.cob", "run_chain.sh")
run("""
cobc -x stepend.cob
bash run_chain.sh 0
bash run_chain.sh 12
""") Output
$ cobc -x stepend.cob $ bash run_chain.sh 0 EXTRACT runs and sets RETURN-CODE 0000 VALIDATE runs and sets RETURN-CODE 0004 POST runs and sets RETURN-CODE 0000 REPORT runs and sets RETURN-CODE 0000 NOTIFY is bypassed: the ELSE of IF (RC < 8) highest return code of the job: 4 $ bash run_chain.sh 12 EXTRACT runs and sets RETURN-CODE 0000 VALIDATE runs and sets RETURN-CODE 0004 POST runs and sets RETURN-CODE 0012 REPORT is bypassed: RC 12 is not < 8 NOTIFY runs and sets RETURN-CODE 0000 highest return code of the job: 12
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 chain_session.py
On the good night RC was 4 after VALIDATE, so POST ran, and REPORT ran because RC stayed below 8. On the bad night POST ended with 12, so the ELSE branch ran NOTIFY in place of REPORT. Step through the bad night:
The drawing shows the whole scene; its 5 steps are listed below.
Every step as text
- Two steps run. EXTRACT ends with return code 0 and VALIDATE with 4, a warning. RC, the highest code so far, is 4.
- The first test. IF (RC <= 4) is true, so step RUN calls the procedure POSTPROC, and its step POST runs.
- POST fails. POSTTXN ends with return code 12, an error. RC is now 12, the highest code of the job.
- The second test. IF (RC < 8) is false. The steps between THEN and ELSE are bypassed, so REPORT does not print totals of a failed posting.
- The ELSE. The steps between ELSE and ENDIF run instead, so NOTIFY tells someone that the night failed. The job ends with RC 12 in its job log.
Older JCL decides the same things with the COND parameter of the EXEC statement, a bypass test: when one of its tests is true, the step is skipped. Where an IF says when the steps inside it run, a COND test says when its own step does not. A job that abends runs no later step by default, unless a test names the abend.
Checkpoint
Check: Return codes and IF/THEN/ELSE/ENDIF
A procedure is a set of steps whose symbols each call can change
The posting step of nightly.jcl is written once, as a procedure, and called by a step with
EXEC POSTPROC,HLQ=BANK.PROD,RUNMODE=FULL. procedure: A set of JCL steps written once and called by name from an EXEC statement: instream between PROC and PEND in the job, or cataloged as a member of a procedure library. come in two kinds: an instream procedure sits inside the job
between a PROC and a PEND statement, and a cataloged procedure is a member of a procedure library that any job may
call. The PROC statement gives each symbolic parameter: A name such as &HLQ in a JCL procedure that each call can give a value. The PROC statement sets its default, and a value on the calling EXEC statement replaces it for that call. a default, HLQ=BANK.TEST
here, and a value on the calling EXEC statement wins over it. expand_proc.py expands POSTPROC both ways:
"""Expands the instream procedure POSTPROC of nightly.jcl the way the system does when a step calls it: the PROC
statement gives each symbol a default, the calling EXEC statement may give it another value, and every &name in the
procedure is replaced. A period right after a symbol is a delimiter and is dropped, so &HLQ..LOAD becomes
BANK.PROD.LOAD (z/OS MVS JCL Reference, Using symbols before fixed code). && starts a temporary data set name, not a
symbol."""
import re
from pathlib import Path
SYMBOL = re.compile(r"(?<!&)&([A-Z#@$][A-Z0-9#@$]{0,7})\.?")
def keywords(text):
"""HLQ=BANK.TEST,RUNMODE=TRIAL -> {'HLQ': 'BANK.TEST', 'RUNMODE': 'TRIAL'}"""
return dict(part.split("=", 1) for part in text.split(",") if "=" in part)
def substitute(line, values):
return SYMBOL.sub(lambda m: values.get(m.group(1), m.group(0)), line)
lines = Path("nightly.jcl").read_text().splitlines()
start = next(i for i, l in enumerate(lines) if " PROC " in l)
end = next(i for i, l in enumerate(lines) if l.split()[1:2] == ["PEND"])
defaults = keywords(lines[start].split(" PROC ", 1)[1].strip())
body = lines[start + 1:end]
call = next(l for l in lines[end:] if "EXEC POSTPROC" in l)
overrides = keywords(call.split("EXEC POSTPROC", 1)[1].strip(", "))
for title, values in (("called with the defaults", defaults), ("called by " + call.split()[0][2:], {**defaults, **overrides})):
print(f"POSTPROC {title}: " + ", ".join(f"&{k}={v}" for k, v in values.items()))
for line in body:
if "&" in line:
print(" " + substitute(line, values)) Output
POSTPROC called with the defaults: &HLQ=BANK.TEST, &RUNMODE=TRIAL //POST EXEC PGM=POSTTXN,PARM='MODE=TRIAL' //STEPLIB DD DISP=SHR,DSN=BANK.TEST.LOAD //TXNIN DD DISP=SHR,DSN=BANK.TEST.TXN.DAILY(0) //POSTED DD DSN=BANK.TEST.POSTED(+1),DISP=(NEW,CATLG,DELETE), POSTPROC called by RUN: &HLQ=BANK.PROD, &RUNMODE=FULL //POST EXEC PGM=POSTTXN,PARM='MODE=FULL' //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),
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 expand_proc.py
Called with the defaults, every data set is a BANK.TEST one and the PARM says TRIAL; called by step RUN, the same
statements name BANK.PROD data sets and FULL. A period right after a symbol ends its name and disappears, which is why
the procedure writes &HLQ..LOAD with two periods: IBM’s example is &DEPT..POK for D58.POK. Step through one
statement:
The drawing shows the whole scene; its 4 steps are listed below.
Every step as text
- The procedure's default. The PROC statement gives the symbol HLQ the value BANK.TEST. A call that says nothing about HLQ runs the procedure against test data.
- The call's value. Step RUN calls the procedure with HLQ=BANK.PROD. A value on the calling EXEC statement wins over the PROC statement's default.
- The symbol in the statement. The DD statement is coded DSN=&HLQ..TXN.DAILY(0). The first period ends the symbol's name and disappears; the second stays in the data set name.
- The statement that runs. The system replaces &HLQ. with BANK.PROD, giving DSN=BANK.PROD.TXN.DAILY(0). The same procedure, called with the default, reads BANK.TEST.TXN.DAILY(0).
Three more statements help JCL procedures and symbols serve many jobs. JCLLIB names the private libraries searched for cataloged procedures and INCLUDE groups, one JCLLIB per job. INCLUDE brings a group of JCL statements from such a library into the job. SET gives symbols values for the statements that follow, and IBM’s reference says a value on a calling EXEC or PROC statement overrides the SET value.
Key idea
A step’s return code is its program’s RETURN-CODE, and IF tests the highest one so far; a procedure is written once, and its symbols take the values of each call.
Checkpoint
Check: Procedures and symbolic parameters
Practise: return codes, procedures and symbols
These questions use the two nights of chain_session.py and the expansion of POSTPROC.
Practice that adapts to you
Practise this lesson
6 questions about 5 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
COND, the bypass test, worked through every operator and three traps Pro: opens with a Pro, Premium or Ultimate pass
COND says when to skip a step, not when to run it, and the classic COND mistake is to read it the other way round. This part works through every operator, reproduces a COND that never fires, and drills the rules with sample tests.
In this part:
- Reading COND the right way round, with IBM’s table
- When COND cannot say what IF says
- Abends, EVEN and ONLY, and the first step
- Rewriting a COND job with IF, without changing what runs
- A drill with sample tests
- Decide whether COND bypasses a step
- Results of the sample tests
2 runnable examples (JCL, Python) · 1 exercise with sample tests · about 7 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).
Overrides, symbols that eat periods, and GDG generations on a restart Pro: opens with a Pro, Premium or Ultimate pass
A procedure earns its keep when one call changes a little and the rest stays shared. This part overrides a cataloged procedure from a replay job, shows the period mistakes that make valid but wrong names, and restarts a job that had already created generations.
In this part:
- Overriding a cataloged procedure from the calling job
- The period after a symbol, and names that are valid and wrong
- Restarting a job that had already created generations
1 diagram · 3 runnable examples (JCL, Python) · about 7 minutes
Opening this part with your pass…Pro part. It opens with the same pass as the part above.
Interview questions
A warm-up answered in full, then three questions about conditions, procedures and restarts that misbehaved:
-
Fresher to mid · Warm-up
What is RC in a JCL IF statement, and how does a COBOL program set it?
A COBOL program sets the return code of its step by moving a value to the special register RETURN-CODE before it ends, for example 0 for a clean run, 4 for warnings and 8 or more for errors. When the program ends, the value becomes the step's return code, the condition code in the job log. In IF (RC <= 4) THEN, RC is the highest return code of the steps that have run so far, not the last one; steps that did not start or ended abnormally are not counted. So after codes 0, 8 and 4, RC is 8. To test one step, name it, as in VALIDATE.RC. Locally, GnuCOBOL passes RETURN-CODE to the shell as the exit status.
-
Mid to senior
A team replaced IF (RC < 8) THEN REPORT ELSE NOTIFY ENDIF with COND parameters: COND=(8,LE) on REPORT and COND=(8,GT) on NOTIFY. Months later a night on which POST ended with 12 paged nobody. Explain why, and write a version that works.
-
Mid to senior
A cataloged procedure moved from test to production. Its first production night ended with return code 0, but the branch summary was cataloged as BANK.PRODRPT instead of BANK.PROD.RPT, and the job that mails the summary found no data set. Find the cause, fix it, and stop it from happening again.
-
Mid to senior
A month-end job writes BANK.PROD.STMT(+1) in STEP1 and BANK.PROD.STMT(+2) in STEP2; STEP3 prints both and abended. The operator restarted at STEP3 without changing the JCL, and STEP3 failed again. What went wrong, and what are the two correct ways to recover?
Organisations named on this page
The IF, COND, procedure, symbol and restart rules on this page follow IBM’s z/OS MVS JCL Reference and User’s Guide; the steps ran on this computer with GnuCOBOL 3.2. MySmartCoPilot has no link with IBM.
Flashcards · 8 cards
Recall it, then check
Say the answer to yourself before you look: recalling it is what makes it stick.
-
Where does a JCL step's return code come from?
From the COBOL program's RETURN-CODE when it ends; the job log shows it as the step's condition code.
-
What does RC stand for in a JCL IF statement?
The largest return code among the steps run so far; a step that never started or that abended does not count.
-
How does the COND parameter differ from an IF statement?
COND is a bypass test: when one of its tests is true, its own step is skipped. An IF says when the steps inside it run.
-
Do later steps run after a step abends?
Not by default: a job that abends runs no later step unless a test names the abend.
-
How does an instream JCL procedure differ from a cataloged one?
An instream procedure sits inside the job between PROC and PEND; a cataloged procedure is a member of a procedure library that any job may call.
-
How does a symbolic parameter get its value?
The PROC statement gives it a default, such as HLQ=BANK.TEST, and a value on the calling EXEC statement wins: EXEC POSTPROC,HLQ=BANK.PROD.
-
Why does a procedure write &HLQ..LOAD with two periods?
A period right after a symbol ends its name and vanishes, so the second period is the one that stays: BANK.PROD.LOAD.
-
What do JCLLIB, INCLUDE and SET do?
JCLLIB names the private libraries searched for procedures and INCLUDE groups; INCLUDE brings a group of JCL statements into the job; SET gives symbols values for the statements after it, and a value given on the calling EXEC or PROC statement wins over SET.
Key takeaways
- A COBOL program’s RETURN-CODE becomes its step’s return code, and IF/THEN/ELSE/ENDIF tests RC, the highest code of the steps that have run, to decide which conditional steps run next.
- A job that abends runs no later step by default; COND is the older way to write conditions, as a test that bypasses a step when it is true.
- JCL procedures are written once, instream between PROC and PEND or cataloged in a library, and the symbols of each call replace their defaults; two periods keep one after a symbol.
Terms in this lesson
- conditional step
- A job step that runs or is skipped by the return codes of the steps before it, under an IF/THEN/ELSE/ENDIF construct or a COND parameter.
- procedure
- A set of JCL steps written once and called by name from an EXEC statement: instream between PROC and PEND in the job, or cataloged as a member of a procedure library.
- symbolic parameter
- A name such as &HLQ in a JCL procedure that each call can give a value. The PROC statement sets its default, and a value on the calling EXEC statement replaces it for that call.
References
- z/OS 3.2 MVS JCL Reference: IF/THEN/ELSE/ENDIF statement construct (IBM)
- z/OS 3.2 MVS JCL Reference: Relational-expression keywords (IBM)
- z/OS 3.2 MVS JCL User's Guide: Relationship of the IF/THEN/ELSE/ENDIF statement construct to the COND parameter (IBM)
- z/OS 3.2 MVS JCL Reference: COND parameter of the EXEC statement (IBM)
- z/OS 3.2 MVS JCL Reference: COND subparameter definition (IBM)
- z/OS 3.2 MVS JCL Reference: Summary of COND parameters (IBM)
- z/OS 3.2 MVS JCL Reference: COND parameter on the first statement in a job (IBM)
- Enterprise COBOL for z/OS 6.5: RETURN-CODE special register (IBM)
- z/OS 3.2 MVS JCL Reference: PROC statement (IBM)
- z/OS 3.2 MVS JCL Reference: PEND statement (IBM)
- z/OS 3.2 MVS JCL Reference: Procedures and symbols (IBM)
- z/OS 3.2 MVS JCL Reference: Using symbols before fixed code (IBM)
- z/OS 3.2 MVS JCL Reference: SET statement (IBM)
- z/OS 3.2 MVS JCL Reference: JCLLIB statement (IBM)
- z/OS 3.2 MVS JCL Reference: INCLUDE statement (IBM)
- z/OS 3.2 MVS JCL Reference: Location of modifying statements in the JCL (IBM)
- z/OS 3.2 MVS JCL Reference: Rules for modifying EXEC parameters (IBM)
- z/OS 3.2 MVS JCL User's Guide: Restarting a job with generation data sets (IBM)
- GnuCOBOL 3.2 NEWS (GnuCOBOL project (GNU))
Report a problem with this lesson
Kept only in this browser. Your Learn progress