Datasets and Mainframe Utilities

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the section bibliography.

This page is written against ISO/IEC 1989:2023 (edition 3, January 2023; an upcoming 4th edition is in ballot), which says nothing about data set allocation or about utility programs, because those belong to the operating system and not to the language; against IBM Enterprise COBOL for z/OS 6.5 (PTFs through September 2026) on z/OS 3.2, where every item here lives; and against GnuCOBOL 3.2 (28 July 2023, the latest release), which was used for the one portable example, a file check on the PC that was run on 2026-10-20. Everything about JCL, IDCAMS, DFSORT, SDSF, REXX and the Zowe CLI is "z/OS; not run here": it was checked statement by statement against the z/OS 3.2 and Zowe documentation, but it was never executed because no z/OS system was available.

A COBOL program never creates its own data sets on z/OS. The SELECT clause names a ddname, the job binds the ddname to a data set, and the data set already exists, with a record format, a record length and some space chosen by someone. This page is about that someone: how a data set comes into being (three ways), what RECFM, LRECL and BLKSIZE must say so that the program’s FD and the data set agree, and the small IBM programs that copy, compare, sort, list and delete data sets between COBOL steps. The concepts of data sets, catalogs, ISPF and SDSF are introduced in Mainframe Environment Primer; the JOB, EXEC and DD statements themselves, return codes and the nightly job are in JCL for COBOL Programmers and are used here without being explained again. The FD side of the agreement is in File Concepts, SELECT and FD and Sequential Files; VSAM clusters and the DEFINE CLUSTER job are in Indexed Files and VSAM; the COBOL SORT statement and the basic DFSORT job are in Sort and Merge. The same tasks from a PC are on Modern Tooling with VS Code and Zowe.

Where the book is outdated: Taulli’s Modern Mainframe Development (2022, chapters 3 and 7) was written for z/OS 2.4 and Zowe version 1. The data set concepts and the utility names it uses (IEFBR14, IEBGENER, IEBCOPY, IDCAMS, DFSORT) are still current in z/OS 3.2 with the same control statements, but its Zowe command line is not: the CLI is now the 3.x line, and every zowe zos-files command on this page was checked against the current Zowe documentation. The book’s "copy library" job is an IEBCOPY step, which copies members between partitioned data sets; it is not what makes a COPY statement in a program work. The compiler finds copybooks through the SYSLIB DD of the compile step, as Copybooks and REPLACE explains, and IEBCOPY only fills the library that SYSLIB points to. The book mentions CLIST and REXX in a survey of the languages found on the mainframe; this page treats REXX in one short section, as a tool for small automation, and CLIST is not covered because REXX has replaced it for new work.

Three ways to allocate a data set

Whatever the way, the result is the same: a catalog entry for a name such as USERID.CIRC.LOANTRAN, space on a volume, and a set of attributes (organization, record format, record length, block size). The diagram shows the three routes of this section reaching it.

flowchart LR A["ISPF 3.2: Allocate new data set"] --> D[("Catalog entry plus space on DASD")] B["JCL: DD with DISP=(NEW,CATLG), run by IEFBR14 or by the program step"] --> D C["Zowe CLI: zowe zos-files create ... (through z/OSMF)"] --> D D --> E["COBOL SELECT ... ASSIGN TO ddname, bound by a DD statement"]

ISPF option 3.2

Option 3.2 of ISPF is the Data Set Utility, and typing 3.2 on the primary option menu goes straight to it. On its panel, option A allocates a new data set, R renames, D deletes, and leaving the option blank displays the information of the data set named in the fields below. Allocating shows a panel with the fields of the next table (the panel layout, defaults and some field names depend on the ISPF level and on the installation, and the ISPF help, PF1, explains each field).

Field Value for USERID.CIRC.LOANTRAN

Data set name

USERID.CIRC.LOANTRAN, the fully qualified name, entered in the project, group and type fields or in the data set name field of the panel

Volume serial, generic unit

Left blank when the installation’s storage management picks the volume

Space units, primary quantity, secondary quantity

TRACKS, 5 and 1 (the secondary space is used when the primary is full)

Directory blocks

0 for a sequential data set; a number for a partitioned data set, which has a directory of members

Record format

FB

Record length

30

Block size

0, so that the system chooses (see RECFM, LRECL and BLKSIZE)

Data set name type

Blank or BASIC for a sequential data set, PDS for a partitioned data set, LIBRARY for a PDSE

ISPF is the fastest way for a single data set that someone will edit by hand, such as a copybook library USERID.CIRC.COPYLIB (FB, 80, directory blocks 20) or the source library USERID.CIRC.COBOL. It is the wrong way for anything that has to be repeated: the allocation is not recorded anywhere, so the next environment gets a different one.

JCL: the DD statement and IEFBR14

A new data set is allocated by a DD statement that says DISP=(NEW,CATLG,DELETE) and gives its space and attributes. The statement is carried out by the initiator when the step starts, before the program runs, so a job step whose only purpose is to allocate can run a program that does nothing. That program is IEFBR14: it returns completion code 0 and nothing else, and the allocation and the disposition processing at the end of the step do the work. The following job (z/OS; not run here) creates the transaction data set of the Circulation scenario and a copybook library, and deletes a work file.

//ALLOC    JOB (ACCT),'ALLOCATE CIRC',CLASS=A,MSGCLASS=X
//STEP1    EXEC PGM=IEFBR14
//LOANTRAN DD DSN=USERID.CIRC.LOANTRAN,DISP=(NEW,CATLG,DELETE),
//            RECFM=FB,LRECL=30,BLKSIZE=0,
//            SPACE=(TRK,(5,1),RLSE)
//COPYLIB  DD DSN=USERID.CIRC.COPYLIB,DISP=(NEW,CATLG,DELETE),
//            DSNTYPE=LIBRARY,RECFM=FB,LRECL=80,BLKSIZE=0,
//            SPACE=(TRK,(15,5,20))
//OLDWORK  DD DSN=USERID.CIRC.WORK,DISP=(MOD,DELETE,DELETE),
//            SPACE=(TRK,(1,1))
  • The ddnames (LOANTRAN, COPYLIB, OLDWORK) mean nothing to IEFBR14; they only have to be unique in the step.

  • DISP=(NEW,CATLG,DELETE) creates the data set, catalogs it when the step ends normally and deletes it if the step abends. Without the catalog request, a new data set is lost at the end of the job unless it is passed to a later step.

  • SPACE=(TRK,(5,1),RLSE) asks for 5 tracks, up to 1 more track at a time when full, and the release of unused space when the data set is closed after being written by a program. RLSE does nothing in a step that does not open the data set, as here, but it is the habit to carry to the DD statements of the writing steps. For a partitioned data set the third value of the inner list is the number of directory blocks, 20 here.

  • DSNTYPE=LIBRARY makes a PDSE, a partitioned data set extended; leaving it out gives a PDS, which has to be compressed from time to time (see IEBCOPY).

  • BLKSIZE=0 makes the system determine the block size, which is the normal practice (see RECFM, LRECL and BLKSIZE).

  • DISP=(MOD,DELETE,DELETE) on a data set that may not exist is the usual idiom for "delete it if it is there": MOD on a data set that does not exist is treated like NEW, so the step creates it and the disposition deletes it again, and the job does not fail with "data set not found". Running the job twice is therefore safe, except for the first two DD statements, which fail the second time because the names already exist. A job that is run again and again uses this idiom first and allocates afterwards.

The same DD statements in the program’s own step allocate the data set at the moment it is needed, so a separate IEFBR14 step is only worth having to prepare the data sets before a run, or to clean up after it.

Zowe CLI: zowe zos-files create

The Zowe CLI creates data sets from a workstation through the z/OS Management Facility (z/OSMF) REST services, with no JCL. The command group is zowe zos-files create, and its sub-commands of the current documentation are data-set-sequential (alias ps), data-set-partitioned (alias pds), data-set-classic (alias classic, for JCL, assembler or COBOL source libraries), data-set-binary, data-set-c, data-set-vsam, uss-directory, uss-file, zos-file-system and the generic data-set, which creates a data set from an existing one’s properties. The connection comes from a z/OSMF profile or the team configuration (host, port, user and certificate are options of every command, and are normally kept in the profile, not typed). The commands below (z/OS; not run here) allocate the same objects as the job above:

zowe zos-files create data-set-sequential USERID.CIRC.LOANTRAN --record-format FB --record-length 30 --block-size 27990 --size 5TRK
zowe zos-files create data-set-partitioned USERID.CIRC.COPYLIB --data-set-type LIBRARY
zowe zos-files create data-set USERID.CIRC.LOANSRT --like USERID.CIRC.LOANTRAN
  • The first command sets the format, the length, the block size and the space explicitly. --size takes a number followed by CYL or TRK and sets the primary allocation; the secondary allocation then becomes about 10 percent of it. The defaults of data-set-sequential are, per the documentation, FB, a record length of 80 and a block size of 6160, so a data set for 30-byte records must say so. The block size 27990 is 933 records of 30 bytes, the largest multiple of 30 that fits in the 27998-byte half-track block that is usual on a 3390 disk; the next section shows why a multiple of the record length is needed, and BLKSIZE=0 in JCL achieves the same without the arithmetic.

  • The second command takes the defaults of data-set-partitioned (FB, 80, 5 directory blocks) and --data-set-type LIBRARY makes it a PDSE, as DSNTYPE=LIBRARY does in JCL. The other values of --data-set-type listed by the documentation are BASIC, EXTPREF, EXTREQ, HFS, LARGE, PDS and PIPE.

  • The third command creates a data set with the attributes of an existing one, which is the quick way to make a work file the same shape as a master. --like can be combined with an override such as --record-length.

  • The space is given with --size here, whose unit is explicit; if --primary-space is used instead, check in --help which unit applies before relying on it, because the documentation page does not state it.

The rest of the daily commands of the group, again checked against the documentation, are:

zowe zos-files list data-set "USERID.CIRC.*" --attributes
zowe zos-files upload file-to-data-set LOANTRAN USERID.CIRC.LOANTRAN
zowe zos-files download data-set USERID.CIRC.LOANTRAN --file LOANTRAN --extension ""
zowe zos-files delete data-set USERID.CIRC.WORK --for-sure

list data-set accepts a name or a pattern, and --attributes adds the details of each match; upload file-to-data-set copies a local file to a data set or to a member written as name(member), with --binary for no conversion and --encoding for a conversion you choose; download data-set writes a local file whose name and extension are the options --file and --extension (the default extension is .txt); and delete data-set needs --for-sure, which is the CLI’s confirmation that the deletion is permanent. When a text file is uploaded into a fixed-length data set, check with the list command and a browse in ISPF that the records came out with the length the FD expects; that detail depends on the conversion and was not tested here.

RECFM, LRECL and BLKSIZE

These three attributes, usually called the DCB attributes after the data control block that holds them, tell the access method how the bytes of the data set are cut into records and blocks. They are the connection between the data set and the program, and a mismatch is the commonest allocation problem of a COBOL developer.

Attribute What it says

RECFM

F fixed, V variable, U undefined; B after it means blocked (several records per block), A or M means an ANSI or machine carriage-control character in front of each line of a print file. FB is the format of the Circulation masters and transactions, and VB is used for variable-length files.

LRECL

The logical record length in bytes. For F it is the length of every record. For V it includes the 4-byte record descriptor word (RDW), so it is the longest record plus 4.

BLKSIZE

The physical block length in bytes. For FB it must be a multiple of LRECL; for VB it must be at least the longest block, which is LRECL plus the 4-byte block descriptor word (BDW). A traditional limit on disk is 32760 bytes. BLKSIZE=0 or an omitted value lets the system determine the best value for the device.

How the FD produces the attributes of the data set it opens is the subject of the files pages; the rule of thumb for the Circulation records is below. The attributes can be given in three places (the data set label when it was created, the DD statement of the step, and the program), and the sources are merged when the file is opened, with the values of the program taking part through its FD and the DD statement overriding what the label says.

Data set Record in the FD RECFM and LRECL BLKSIZE

USERID.CIRC.BOOKMAST (as a sequential export)

BOOK-RECORD, 60 bytes

FB, 60

0 (system-determined)

USERID.CIRC.LOANTRAN

LOAN-RECORD, 30 bytes

FB, 30

0 (system-determined)

A report file

A 133-byte print line with a carriage-control byte

FBA, 133

0 (system-determined)

A variable-length extract with records of 10 to 100 bytes

RECORD VARYING FROM 10 TO 100

VB, 104 (100 plus the 4-byte RDW)

0 (system-determined)

Enterprise COBOL takes the record format from the FD when it contains RECORDING MODE, and otherwise infers it from the record descriptions; BLOCK CONTAINS 0 RECORDS in the FD hands the choice of the block size to the system, the standing practice on z/OS (see Sequential Files). The practical consequences are these:

  • The record length of the FD and the LRECL of the data set are the same number for fixed records. A 60-byte BOOK-RECORD read from a data set with LRECL=80 is a mismatch, and the program typically fails to open it with a file status that reports conflicting attributes (status 39, see File Status Codes) instead of reading shifted records.

  • A block size of an FB data set must be a multiple of the record length. When the system is asked to determine it (BLKSIZE=0) it picks a suitable value for the device the data set lands on, so coding a number by hand is only useful to reproduce an old size; the 27990 of the Zowe example above is such a value for 30-byte records on a 3390 disk.

  • A copy whose output record length differs from the input is no longer a plain copy: IEBGENER needs control statements that describe the editing (see IEBGENER and ICEGENER), and DFSORT reformats with its own statements. Check the manual before copying between different lengths.

  • Running out of space ends a step with a system abend of the x37 family (B37, D37 and E37, whose common causes are a full volume, a primary quantity filled with no secondary quantity given, and no further extent available). The cure is a larger SPACE (and a secondary quantity), not a change to the program.

The IBM utilities a COBOL developer meets

IBM supplies a family of small programs that a job step runs with EXEC PGM= and that a COBOL developer uses to prepare, move and inspect data between steps. Each takes its work from DD statements and, where it needs more detail, from control statements in SYSIN, and writes its report to SYSPRINT (or SYSOUT for DFSORT). Their return code (0 good, 4 warning, 8 or more error, by the usual convention) is tested in the JCL like that of any step.

Utility What it does Use in the Circulation job

IEFBR14

Does nothing; the DD statements of its step allocate or delete data sets

Prepare or remove the working data sets before and after the nightly run

IEBGENER (or ICEGENER)

Copies a sequential data set, or builds partitioned members from sequential input; can print and edit

Back up LOANTRAN, print a data set for a quick look, create a member from a data set

IEBCOPY

Copies, merges, compresses and unloads partitioned data sets (PDS and PDSE), selecting members

Move compiled programs and copybooks between libraries; compress a PDS

IDCAMS

Access method services: defines, copies, lists and deletes VSAM clusters and catalog entries

Define and load BOOKMAST; LISTCAT and DELETE of data sets

SORT (DFSORT) and ICETOOL

Sorts, merges, copies, filters, reformats, summarizes and reports on records

Order LOANTRAN by book and date, split by transaction type, count and reconcile records

SDSF

The ISPF application that displays jobs and their spool output

Read the SYSPRINT of each utility step and the return codes

On a PC the same chores are touch, cp, cat, ls, sort and rm, and the GnuCOBOL example at the end of the page does a few of them on the Circulation file. The program and the utilities are chained in the nightly run described on JCL for COBOL Programmers.

IEBGENER and ICEGENER

IEBGENER copies the sequential data set named by the SYSUT1 DD to the one named by SYSUT2. SYSPRINT receives the utility’s messages, and SYSIN holds control statements, or is DUMMY when no editing is wanted. The IBM example that prints a data set uses exactly that shape, with SYSUT2 going to the printer class; the following job (z/OS; not run here) makes a backup copy of the loan transactions and then lists the copy on the job output:

//BACKUP   EXEC PGM=IEBGENER
//SYSPRINT DD SYSOUT=*
//SYSIN    DD DUMMY
//SYSUT1   DD DISP=SHR,DSN=USERID.CIRC.LOANTRAN
//SYSUT2   DD DSN=USERID.CIRC.LOANTRAN.BACKUP,DISP=(NEW,CATLG,DELETE),
//            RECFM=FB,LRECL=30,BLKSIZE=0,SPACE=(TRK,(5,1),RLSE)
//LISTING  EXEC PGM=IEBGENER
//SYSPRINT DD SYSOUT=*
//SYSIN    DD DUMMY
//SYSUT1   DD DISP=SHR,DSN=USERID.CIRC.LOANTRAN.BACKUP
//SYSUT2   DD SYSOUT=*
  • With SYSIN DD DUMMY there is no editing, so the record length and the format of the output come from the input data set and the system selects the block size, as in the IBM example.

  • The same program also copies an in-stream or sequential data set into a member of a PDS (SYSUT2 names USERID.CIRC.COPYLIB(LOANREC), a member, not a whole library), which is how a copybook is loaded from a job. The control statements GENERATE and RECORD describe editing and the creation of several members; they are rarely needed for the Circulation job and are described in the IBM manual.

  • ICEGENER is the DFSORT front end that does the same unedited copy, generally faster. The IBM manual advises it in preference to IEBGENER for unedited copies when DFSORT is installed, and notes that it may be installed under the name IEBGENER, so that the job above may in fact run it. Both take the same DD names.

  • IEBGENER copies sequential data sets and members; it cannot copy a VSAM cluster (IDCAMS REPRO does that) and cannot copy a whole PDS in one step (IEBCOPY does that).

IEBCOPY

IEBCOPY works on partitioned data sets, the libraries that hold source members, copybooks, JCL procedures and load modules. It copies or merges members between PDSs or PDSEs, unloads a library to a sequential data set and reloads it, selects or excludes members by name, renames and replaces them, and compresses a PDS. Its DD statements are SYSPRINT (messages), the input and output libraries under ddnames of your choice, work data sets SYSUT3 and SYSUT4 (the spill data sets of the IBM examples; the unit name SYSDA is an installation choice), and SYSIN with the control statements. The following job (z/OS; not run here) copies the copybooks BOOKREC, MEMBREC and LOANREC from the development library to the test library, and then compresses the development library in place:

//COPYMEM  EXEC PGM=IEBCOPY
//SYSPRINT DD SYSOUT=*
//SYSUT3   DD UNIT=SYSDA,SPACE=(TRK,(1,1))
//SYSUT4   DD UNIT=SYSDA,SPACE=(TRK,(1,1))
//INLIB    DD DISP=SHR,DSN=USERID.CIRC.COPYLIB
//OUTLIB   DD DISP=OLD,DSN=USERID.CIRC.TEST.COPYLIB
//SYSIN    DD *
  COPY OUTDD=OUTLIB,INDD=INLIB
  SELECT MEMBER=(BOOKREC,MEMBREC,LOANREC)
/*
//COMPRESS EXEC PGM=IEBCOPY
//SYSPRINT DD SYSOUT=*
//A        DD DISP=OLD,DSN=USERID.CIRC.COPYLIB
//B        DD DISP=OLD,DSN=USERID.CIRC.COPYLIB
//SYSIN    DD *
  COPY OUTDD=B,INDD=A
/*
  • COPY OUTDD=...,INDD=... names the ddnames of the output and the input; a SELECT MEMBER=(...) statement after it limits the copy to the listed members (and EXCLUDE does the reverse). A selected member can be written with the replace option, as in SELECT MEMBER=((LOANREC,,R)) (the form used in the IBM example), to replace a member of the same name in the output; without it, check in SYSPRINT whether the member was copied, because a changed copybook that was not replaced is a classic source of builds that use old code.

  • The second step is the compress-in-place form: the same data set under two ddnames, one as input and one as output, and no SELECT. The IBM manual warns to take a backup first, because an interruption during a compress can destroy the library, and not to compress a library other users are using at the time. Compression does not apply to a PDSE, which does not need it, and the need to do this on a PDS is one of the reasons to prefer a PDSE for new libraries.

  • The utility reports the members copied, replaced or not copied in SYSPRINT, which is where to look first when a build does not see a changed copybook.

IDCAMS

IDCAMS is the access method services program. Its commands in SYSIN define VSAM clusters, copy data in and out of them and manage catalog entries. DEFINE CLUSTER, REPRO and PRINT for the Circulation book master, with every parameter explained, are in Indexed Files and VSAM; this section adds the commands for looking at the catalog and for cleaning up, and the way an IDCAMS step reports failures. The following job (z/OS; not run here) lists the Circulation data sets, deletes a cluster so that a job can define it again, and saves the cluster’s records to a sequential data set:

//CATALOG  EXEC PGM=IDCAMS
//SYSPRINT DD SYSOUT=*
//SYSIN    DD *
  LISTCAT LEVEL(USERID.CIRC)
  LISTCAT ENTRIES(USERID.CIRC.BOOKMAST) ALL
  DELETE USERID.CIRC.BOOKOLD CLUSTER PURGE
  IF MAXCC = 8 THEN SET MAXCC = 0
  REPRO INDATASET(USERID.CIRC.BOOKMAST) -
        OUTDATASET(USERID.CIRC.BOOKBAK)
/*
  • LISTCAT LEVEL(USERID.CIRC) lists the catalog entries whose names start with those qualifiers; ENTRIES(name) with ALL lists one entry with all its fields, including the attributes (key length and offset, record size, control interval size, space and the high-used values). A listing like this is the quickest way to check that an allocation did what was intended.

  • DELETE ... CLUSTER PURGE removes a VSAM cluster, its components and its catalog entry; PURGE deletes it even if its retention period has not expired. For a non-VSAM data set the entry type is NONVSAM, and SCRATCH (the removal from the volume as well as from the catalog) or NOSCRATCH decides whether the data is removed from the disk.

  • DELETE of an entry that does not exist gives a condition code of 8 with a message that the entry was not found (a message of the family IDC3012I). IF MAXCC = 8 THEN SET MAXCC = 0 resets the step’s code, so that a clean-up that finds nothing to delete does not stop the job; the hyphen at the end of a line continues an IDCAMS command on the next line.

  • REPRO INDATASET(...) OUTDATASET(...) copies the records; with a VSAM input and a sequential output data set that has been allocated with RECFM=FB and LRECL=60 first (for example with the IEFBR14 job above), it makes a flat backup that IEBGENER or a COBOL program can read. REPRO accepts FROMKEY, TOKEY, SKIP and COUNT to select a range.

  • The return code of the IDCAMS step is the highest condition code of its commands (MAXCC), and the messages (IDC...I) in SYSPRINT explain every non-zero code.

DFSORT and ICETOOL

The basic DFSORT job, with SORT FIELDS, INCLUDE, EQUALS and a first ICETOOL step, is on Sort and Merge; this section adds the operations that are used between steps to split and to check files. The following ICETOOL job (z/OS; not run here) splits the sorted transactions into one data set per transaction type with OUTFIL, then reports the number of records and statistics of the book number, and lists the records in a printed report:

//TOOLRUN  EXEC PGM=ICETOOL
//TOOLMSG  DD SYSOUT=*
//DFSMSG   DD SYSOUT=*
//IN       DD DISP=SHR,DSN=USERID.CIRC.LOANSRT
//OUTCHK   DD DSN=USERID.CIRC.LOANCHK,DISP=(NEW,CATLG,DELETE),
//            RECFM=FB,LRECL=30,SPACE=(TRK,(5,1),RLSE)
//OUTRET   DD DSN=USERID.CIRC.LOANRET,DISP=(NEW,CATLG,DELETE),
//            RECFM=FB,LRECL=30,SPACE=(TRK,(5,1),RLSE)
//RPT      DD SYSOUT=*
//CTL1CNTL DD *
  OUTFIL FNAMES=OUTCHK,INCLUDE=(1,1,CH,EQ,C'O')
  OUTFIL FNAMES=OUTRET,INCLUDE=(1,1,CH,EQ,C'R')
/*
//TOOLIN   DD *
  COPY FROM(IN) USING(CTL1)
  COUNT FROM(IN)
  STATS FROM(IN) ON(8,6,ZD)
  DISPLAY FROM(IN) LIST(RPT) TITLE('Loan transactions') ON(1,1,CH) ON(2,6,ZD) ON(8,6,ZD) ON(14,8,ZD)
/*
  • The statements of CTL1CNTL are ordinary DFSORT control statements (USING(CTL1) on an operator means "read CTL1CNTL`"). `OUTFIL FNAMES=ddname,INCLUDE=(...) writes to the named DD only the records that satisfy the condition, so one pass over the input produces both output data sets. The positions are those of LOANREC: type at 1, member at 2, book at 8, date at 14.

  • COPY and SORT move records from the FROM data set to the TO data set, or to the OUTFIL outputs of the control data set. COUNT writes a message with the number of records, STATS reports statistics (minimum, maximum, total and average) of a numeric field given in ON(position,length,format), here the six zoned-decimal digits of the book number, and DISPLAY produces a report of the listed fields on the LIST DD. These operators are the building blocks of a reconciliation step: the count of the input must equal the sum of the counts of the outputs, and a job step that tests it turns a silent loss of records into a non-zero return code.

  • TOOLMSG receives ICETOOL’s messages and DFSMSG those of DFSORT, one of each is needed in every ICETOOL step.

For a one-off check, nothing replaces the messages in DFSMSG and TOOLMSG: they show the number of records read, written, deleted and inserted by each operator, which is the first thing to compare with the expected counts.

SDSF

SDSF, reached from the ISPF menu where it is installed, shows the jobs on the JES2 queues and the output of each one. The panels, PREFIX, OWNER, and the row commands S, ? and P are in Mainframe Environment Primer. For utilities the habit is the following: open the job, find the step’s SYSPRINT (or TOOLMSG, DFSMSG, SYSTSPRT) data set among its spool files, read the messages from the first one that is not informational, and compare the step’s return code with the expectation. The IBM utilities number their messages with a prefix of their own (IEB for IEBGENER and IEBCOPY, IDC for IDCAMS, ICE for DFSORT and ICETOOL), and the message id is what to look up in the manuals.

The same information is available without ISPF. The Zowe CLI submits a job from a data set, lists jobs by name prefix and owner, and views the spool files; the following commands are checked against the Zowe documentation (z/OS; not run here):

zowe zos-jobs submit data-set "USERID.CIRC.JCL(ALLOC)" --view-all-spool-content
zowe zos-jobs list jobs --prefix "ALLOC*"
zowe zos-jobs view all-spool-content JOB00234

The first submits the job and prints the whole job output once it ends, and the third prints every spool file of a job whose id (here an example value, JOB00234, the one used in the Zowe documentation) the second command listed. SDSF also has a REXX interface and a Java interface for programs that process the spool, described in the SDSF Operation and Customization guide; for a COBOL developer that is only a pointer, to use when the output of many jobs has to be searched by a script.

REXX for small automation

REXX is the scripting language of z/OS (it also runs on z/OS UNIX and on PCs, and the language is not the subject here). On z/OS a REXX program, called an exec, is a member of a partitioned data set, and it drives TSO commands by sending them as strings: any statement that is a string expression is passed to the current host environment, which in TSO is ADDRESS TSO. For a COBOL developer REXX is the quick way to check an allocation, count records, build a list of members, or script an ISPF task, without compiling a program. The following exec (z/OS; not run here) checks the Circulation transaction data set against what the FD expects, reads it and counts the transactions by type:

/* REXX - LOANCHK: check and count USERID.CIRC.LOANTRAN */
dsn = "'USERID.CIRC.LOANTRAN'"
x = LISTDSI(dsn)
if x > 4 then do
  say "LOANCHK: cannot read" dsn", reason" SYSREASON
  exit 8
end
say "RECFM="SYSRECFM "LRECL="SYSLRECL "BLKSIZE="SYSBLKSIZE
if SYSLRECL <> 30 then do
  say "LOANCHK: LRECL is not 30, the FD would not match"
  exit 8
end
address TSO
"ALLOC FI(LOANIN) DA("dsn") SHR REUSE"
if rc > 0 then do
  say "LOANCHK: ALLOC failed with return code" rc
  exit 8
end
"EXECIO * DISKR LOANIN (STEM rec. FINIS"
if rc > 0 then do
  say "LOANCHK: EXECIO failed with return code" rc
  "FREE FI(LOANIN)"
  exit 8
end
"FREE FI(LOANIN)"
out = 0; ret = 0; bad = 0
do i = 1 to rec.0
  select
    when substr(rec.i,1,1) = "O" then out = out + 1
    when substr(rec.i,1,1) = "R" then ret = ret + 1
    otherwise bad = bad + 1
  end
end
say rec.0 "records:" out "checkouts," ret "returns," bad "rejects"
if bad > 0 then exit 4
exit 0
  • LISTDSI is called as a function, and returns 0 when it found the data set and set its variables, 4 when some information is not available, and 16 when it failed (then only SYSREASON is valid, with codes such as 24 for "data set not found"). SYSRECFM, SYSLRECL and SYSBLKSIZE hold the three attributes of this page, and SYSDSORG the organization (PS, PO, VS). For a VSAM data set most of the variables are not accurate, so the check suits sequential and partitioned data sets. The fully qualified name is inside single quotes; without them the user’s prefix would be put in front of it.

  • ALLOC and FREE are TSO commands that give the ddname LOANIN to the data set and release it again; EXECIO does not allocate for you. EXECIO * DISKR LOANIN (STEM rec. FINIS reads every record into rec.1, rec.2 and so on, puts the number of records in rec.0 and closes the file. Its return code is 0 for normal completion and 20 for a severe error. The documentation advises putting all EXECIO operands in quotation marks so that they cannot be replaced by variables of the same name, which the whole string does here.

  • exit 4 and exit 8 give the exec a return code that a job can test. To run the exec in batch, the TSO terminal monitor program IKJEFT01 runs it, with the library of execs on SYSEXEC and the commands on SYSTSIN (z/OS; not run here):

//LOANCHK  EXEC PGM=IKJEFT01
//SYSEXEC  DD DISP=SHR,DSN=USERID.CIRC.REXX
//SYSTSPRT DD SYSOUT=*
//SYSTSIN  DD *
  %LOANCHK
/*

What SAY prints goes to SYSTSPRT. For anything larger than a script (reading a million records, say, or updating a data set), a COBOL program or a DFSORT step is the right tool: an exec that reads all records into a stem keeps them all in memory.

The same chores on a PC with GnuCOBOL

GnuCOBOL has no catalog and no DD statements: a "data set" is an ordinary file, its name comes from the assignment name or from a DD_name environment variable (see File Concepts, SELECT and FD), and there is no RECFM, LRECL or BLKSIZE to set. A LINE SEQUENTIAL file is the Circulation record followed by a newline, so a 30-byte record occupies 31 bytes. The utilities of this page therefore become ordinary shell commands. The session below was run on 2026-10-20 in the GnuCOBOL 3.2 container, in a directory holding the four-record LOANTRAN file of the scenario. wc counts, awk shows the length of each record, cp plays IEBGENER, cmp compares, an empty file is what IEFBR14 allocates, and rm deletes:

mkdir -p circ && cd circ
cp ../LOANTRAN LOANTRAN
echo "records : $(wc -l < LOANTRAN)"
echo "bytes   : $(wc -c < LOANTRAN)"
awk '{ print length($0) }' LOANTRAN | sort -u | sed 's/^/length  : /'
cp LOANTRAN LOANCOPY
cmp LOANTRAN LOANCOPY && echo "copy identical"
: > LOANEMPTY
ls -l LOANEMPTY | awk '{print "empty   : " $5 " bytes"}'
rm LOANCOPY LOANEMPTY
ls

The run printed:

records : 4
bytes   : 124
length  : 30
copy identical
empty   : 0 bytes
LOANTRAN

The 124 bytes are the four records of 30 bytes plus four newlines, and the single length of 30 shows that every record has the length that LOAN-RECORD expects, which is the check that LISTDSI and LRECL perform on z/OS. The LOANCOPY and LOANEMPTY files were created and removed again, which is the clean-up that DISP=(MOD,DELETE,DELETE) performs in the IEFBR14 job. The sort, filter and count of the DFSORT section are sort -s, grep and wc -l on the PC, and the variable-length records of a RECORD VARYING file are stored by GnuCOBOL with its own 4-byte length prefix, not the RDW (see Sequential Files).

Common mistakes

  • Forgetting LRECL when allocating. The defaults of the tools suit 80-byte records (the Zowe data-set-sequential defaults are FB, 80 and 6160), so a data set meant for 30-byte records must say LRECL=30 or --record-length 30, or the program fails to open it with a conflict of attributes.

  • Coding a block size that is not a multiple of the record length. For FB, use BLKSIZE=0 and let the system choose.

  • Allocating without CATLG. DISP=(NEW,PASS) or DISP=NEW alone leaves a data set that is gone at the end of the job; a later step or job cannot find it.

  • Running an allocation job twice. A NEW on a name that is already cataloged fails the step. Delete first (DISP=(MOD,DELETE,DELETE) or IDCAMS DELETE followed by SET MAXCC = 0), or allocate with --like in a script that checks first.

  • Using IEBGENER to copy a VSAM cluster or IEBCOPY to copy a sequential file. IEBGENER and IEBCOPY do not handle VSAM; use IDCAMS REPRO. IEBCOPY is for partitioned data sets only.

  • Compressing a PDS that is in use, or without a backup. Other users may see the library as damaged while it is being compressed, and an interrupted compress can destroy it. Use a PDSE for new libraries.

  • Believing that IEBCOPY of the copybook library makes COPY work. The compile step finds copybooks through SYSLIB; the copy only moves members into the library SYSLIB names.

  • Ignoring the return code of a utility step. A step that ends with return code 8 and a job that carries on with an empty file gives a nightly run with no transactions; test the code with COND or IF in the JCL.

  • Quoting a data set name wrongly in REXX or Zowe. In a REXX exec a name without single quotes gets the user’s prefix added; in a shell the quotes of a pattern such as "USERID.CIRC.*" stop the shell from expanding it.

  • Using a REXX exec for bulk data. EXECIO into a stem holds every record in memory; use DFSORT or a COBOL program for large files.