Utilities - Mother little helpers. A set of programs provided by IBM to perform certain standard tasks, like copying data from one place to another, or updating members in a PDS, or whatever, Let's say, you decide to change some parameters in SYS1.PARMLIB. Of course, you want to play it safe, like
With other words, before you change a crucial member (let's say LNKLST00) you will want to make a safe copy, or backup, of this member. Very simple, it seems:
You fire up explorer, click with the mouse, drag the member.... Oooops - wrong timeframe, wrong system, restarting.....
You enter COPY LNKLST00 LNKLST01 at the MVS master console.... Ooops again, it ain't as simple as that at all
What you really do is: You call up a little helper and tell him to do the copying for you. IBM has provided a lot of little helpers in MVS, and they are called UTILITIES. We will discuss a few of the more useful ones here.
Some utilities are called independent utilities, or stand alone utiliies. Why? Because they can run stand alone, independent of an operating system. In general, though, utilities (aka utility programs) are invoked using standard JCL. For a short introduction see the Job Control Language Introduction You will neither need a JOBLIB nor a STEPLIB DD statement, as the utilities are in the standard system library. Depending on the utility program you will need certain required DD statements in your JCL
With the SYSPRINT DD statement you tell MVS into which output queue you want the utility to place its messages and report. Usually you would code
//SYSPRINT DD SYSOUT=*
or, if you are not interested in the messages at all
//SYSPRINT DD DUMMY
The SYSIN DD statement specifies the dataset that contains the commands you want to give to the utility program. In most cases this is an instream data set, i.e.:
//SYSIN DD *
or
//SYSIN DD DATA
If you do not want to issue any specific commands (but rely on some default action the utility will take), you code:
//SYSIN DD DUMMY
Whenever you code commands for the utility (they are called Utility Control Statements), some special rules apply:
The following utility program will be discussed in this article
A program that REALLY does nothing. Zilch. Why do we need such a thing? Well, look at the following JCL:
//MYJOB JOB CLASS=A,MSGCLASS=A,REGION=256K,MSGLEVEL=(1,1) //NEWFILE DD DISP=(NEW,CATLG),DSN=MY.NEW.FILE, // UNIT=3330,VOL=SER=WORK01, // SPACE=(CYL,(10,10,10)),DCB=SYS1.PARMLIB
From the NEWFILE DD statement it is clearly seen that this job tries to make a new file. Unfortunately, it will fail with a JCL error!!! Can you see the error in the JCL statement?
It is not really obvious, but the error is in the EXEC statement! Which EXEC statement? Well, THAT exec statement, the one that has not been coded. The JCL error message is
JOB HAS NO STEPS
And here is where IEFBR14 comes in: A program that just doesn't do anything - but keeps the MVS JCL parser happy:
//MYJOB JOB CLASS=A,MSGCLASS=A,REGION=256K,MSGLEVEL=(1,1) //ALLOC EXEC PGM=IEFBR14 //NEWFILE DD DISP=(NEW,CATLG),DSN=MY.NEW.FILE, // UNIT=3330,VOL=SER=WORK01, // SPACE=(CYL,(10,10,10)),DCB=SYS1.PARMLIB //
IEFBR14 can also be used to get rid of a data set:
//MYJOB JOB CLASS=A,MSGCLASS=A,REGION=256K,MSGLEVEL=(1,1) //DELETE EXEC PGM=IEFBR14 //GETRID DD DISP=(OLD,DELETE,DELETE),DSN=NOT.NEEDED.ANY.MORE
This is nice, of course, but what if the file to be deleted doesn't exist? Your job will fail with
JCL ERROR, DATASET NOT FOUND
and none of the subsequent steps will execute. Therefore it might be better to code sometimes:
//MYJOB JOB CLASS=A,MSGCLASS=A,REGION=256K,MSGLEVEL=(1,1) //DELETE EXEC PGM=IEFBR14 //GETRID DD DISP=(MOD,DELETE,DELETE),DSN=NOT.NEEDED.ANY.MORE, // UNIT=SYSDA,SPACE=(TRK,(0))If the file exists, it will be deleted, and if it does not exist, it will be created at step beginning and deleted at step end
By the way, do not try to delete a member of a PDS with IEFBR14 - grown men have been known to cry because of this. What would the following JCL do:
//DONTRUN JOB (REALLY,DONT),'RUN THIS!!',CLASS=Z,MSGCLASS=A //ALLOC EXEC PGM=IEFBR14 //GETRID DD DISP=(OLD,DELETE,DELETE),DSN=VERY.IMPORTNT.FILE(OBSOLETE)
Will it delete the member OBSOLETE which is in file VERY.IMPORTNT.FILE? Hm - after the job completes, the member is gone. Good. but also, the file is gone. Not so good. Remember, disposition in JCL refers to the whole dataset, not to individual members!!!
IEBGENER is basically a copy program that copies onr sequential file to another sequential file. It can also be used to read a sequential file and place parts of it into different members of a partitioned dataset.
DDNAME | Description |
---|---|
SYSUT1 | Dataset containing the INPUT data for IEBGENER |
SYSUT2 | Dataset containing the OUTPUT data from IEBGENER |
SYSIN | Utility control statements. Can be DUMMY. If DUMMY, the default operation is COPY from SYSUT1 to SYSUT2, no editing |
SYSPRINT | Utility Message Data Set |
To make a backup copy of a member of a PDS, you might code:
//MYJOB JOB CLASS=A,MSGCLASS=A,REGION=256K,MSGLEVEL=(1,1) //BACKUP EXEC PGM=IEBGENER //SYSPRINT DD SYSOUT=A //SYSUT1 DD DISP=SHR,DSN=SYS1.PARMLIB(LNKLST00) //SYSUT2 DD DISP=SHR,DSN=SYS1.PARMLIB(LNKLST01) //SYSIN DD DUMMY
Note, that you code DISP=SHR on the SYSUT2 DD statement, AND NOT DISP=NEW. DISP applies to the whole dataset, not to a member!!!. Also, do not forget the member name on the SYSUT2 DD statement!!! Can you guess what happens if you do forget?
IEBGENER will gladly write the contents of the member LNKLST00 to the beginning of the dataset SYS1.PARMLIB. Unfortunately, this is the place where the directory of SYS1.PARMLIB is located, and it will be overwritten, making the whole SYS1.PARMLIB dataset contents unaccessible.
Now to a more complicated case. Take a look at the following JCL and Utility Control Statements:
//GENER1A EXEC PGM=IEBGENER //SYSPRINT DD SYSOUT=* //SYSUT1 DD DISP=SHR,DSN=SYS1.BADPTFS(UZ61346) //SYSUT2 DD DISP=(NEW,CATLG), // UNIT=3350,VOL=SER=SMP004, // DSN=SYS1.NEWPTFS, // SPACE=(CYL,(2,2,20)), // DCB=SYS1.SMPPTS //SYSIN DD * GENERATE MAXNAME=3,MAXGPS=2 MEMBER NAME=PART1 GROUP1 RECORD IDENT=(7,'EDM1102',9) MEMBER NAME=PART2 GROUP2 RECORD IDENT=(8,' PRE ',1) MEMBER NAME=PART3
This will take as input the member UZ61346 from the
partitioned dataset
SYS1.BADPTFS and will split it up into 3 parts. The first part
ends with a record that has the text 'EDM1102'
in column 9
for 7 bytes. It will be stored as member PART1
. The second part begins at the next record, and
ends with the record that has the text ' PRE ' in position 1. It will be called PART2
.
The rest of the input data goes into the third member PART3
Okay, but now you want to make a backup of library SYS1.LINKLIB. Would you want to code several hundred jobs, each copying one member at a time to a new dataset? Surely not!. This is where our next utility can shine:
IEBCOPY is a utility program used to copy partitioned datasets (aka libraries)
DDNAME | Description |
---|---|
SYSUT1 | Dataset containing the INPUT data for IEBCOPY |
SYSUT2 | Dataset containing the OUTPUT data from IEBGENER |
SYSIN | Utility control statements. Can be DUMMY. If DUMMY, the default operation is COPY from SYSUT1 to SYSUT2 |
SYSPRINT | Utility Message Data Set |
To make a backup copy of a partitioned dataset, you can code:
//MYJOB JOB CLASS=A,MSGCLASS=A,REGION=256K,MSGLEVEL=(1,1) //CLEANUP EXEC PGM=IEFBR14 //SYS2LNK DD DISP=(MOD,DELETE,DELETE),DSN=SYS2.LINKLIB,SPACE=(TRK,(0)), // UNIT=3330,VOL=SER=WORK01 //BACKUP EXEC PGM=IEBCOPY //SYSPRINT DD SYSOUT=A //SYSUT1 DD DISP=SHR,DSN=SYS1.LINKLIB //SYSUT2 DD DISP=(NEW,CATLG),UNIT=3330,VOL=SER=WORK01, // SPACE=(CYL,(100,10,500)), // DCB=SYS1.LINKLIB, // DSN=SYS2.LINKLIB //SYSIN DD DUMMY
The first step deletes a preexisting SYS2.LINKLIB. In the second step all the members of SYS1.LINKLIB will we copied to a newly created SYS2.LINKLIB. Finito. Simple.
Now, sometimes you might want to use more than one pair of datasets for copying. You could achieve this with multiple IEBCOPY steps, of course. But a more elegant way can be chosen://MYJOB JOB CLASS=A,MSGCLASS=A,REGION=256K,MSGLEVEL=(1,1) //BACKUP EXEC PGM=IEBCOPY //SYSPRINT DD SYSOUT=A //SOURCE DD DISP=SHR,DSN=SYS1.LINKLIB //TARGET DD DISP=SHR,DSN=SYS2.LINKLIB //PARMLIB DD DISP=SHR,DSN=SYS1.PARMLIB //PARMSAVE DD DISP=SHR,DSN=SYS2.PARMLIB //SYSIN DD * COPY INDD=SOURCE,OUTDD=TARGET C I=PARMLIB,O=PARMSAVE
What has changed? We added Utility Control Statements. The first one tells IEBCOPY to copy from INput DDname SOURCE to the OUTput DDname TARGET, i.e. we tell IEBCOPY to use different DDnames than the default ones, SYSUT1 and SYSUT2. The second control statement looks a bit more crypric, but actually, it isn't. Commands and keywords can be abbreviated to one character codes. The second command therefore tells IEBCOPY to copy all members from SYS1.PARMLIB to SYS2.PARMLIB.
We can also copy several datasets into one target file. There are two possibilities, one using concatenation, the other one by using a list of DDames
//MYJOB JOB CLASS=A,MSGCLASS=A,REGION=256K,MSGLEVEL=(1,1) //BACKUP EXEC PGM=IEBCOPY //SYSPRINT DD SYSOUT=A //PROCLIB DD DISP=SHR,DSN=SYS1.PROCLIB //PARMLIB DD DISP=SHR,DSN=SYS2.PARMLIB //PARMPROC DD DISP=SHR,DSN=SYS1.PARMPROC //SYSIN DD * COPY INDD=(PROCLIB,PARMLIB),OUTDD=PARMPROC
A list of DD-names is enclosed in parentheses
Actually, I have not been telling the truth, the whole truth, and nothing but the truth. Members will only be copied when they do not already exist in the target dataset, i.e. like named members will not be overwritten. This is not a problem when the target dataset has just been created. But what if the target dataset does already exist? Then we must tell IEBCOPY explicitly that we want members to be overwritten:
//MYJOB JOB CLASS=A,MSGCLASS=A,REGION=256K,MSGLEVEL=(1,1) //BACKUP EXEC PGM=IEBCOPY //SYSPRINT DD SYSOUT=A //PROCLIB DD DISP=SHR,DSN=SYS1.PROCLIB //PARMPROC DD DISP=SHR,DSN=SYS1.PARMPROC //SYSIN DD * COPY INDD=( (PROCLIB,R)) ,OUTDD=PARMPROC
Very good. So far you know how to copy complete libraries. Obviously, you would also want to just copy a few member, or only one. Don't even think about wildcards, they do not exist!! The correct way to copy just a few members is
//MYJOB JOB CLASS=A,MSGCLASS=A,REGION=256K,MSGLEVEL=(1,1) //BACKUP EXEC PGM=IEBCOPY //SYSPRINT DD SYSOUT=A //PROCLIB DD DISP=SHR,DSN=SYS1.PROCLIB //PARMPROC DD DISP=SHR,DSN=SYS1.PARMPROC //SYSIN DD * COPY INDD=PROCLIB,OUTDD=PARMPROC SELECT MEMBER=(ONEMEMBR) S M=(MEMBERA,MEMBERB,MEMBERC)
with other words, you add another utility control statement where you tell IEBCOPY which members you want selected. Keywords can be abbreviated again. Also, you can copy a full library except for selected members. Look at the next example:
//MYJOB JOB CLASS=A,MSGCLASS=A,REGION=256K,MSGLEVEL=(1,1) //BACKUP EXEC PGM=IEBCOPY //SYSPRINT DD SYSOUT=A //PROCLIB DD DISP=SHR,DSN=SYS1.PROCLIB //PARMPROC DD DISP=SHR,DSN=SYS1.PARMPROC //SYSIN DD * C I=PROCLIB,O=PARMPROC EXCLUDE MEMBER=(ONEMEMBR)
Again, existing members in the target dataset will not be overwritten. If you really want to do this, code
//MYJOB JOB CLASS=A,MSGCLASS=A,REGION=256K,MSGLEVEL=(1,1) //BACKUP EXEC PGM=IEBCOPY //SYSPRINT DD SYSOUT=A //PROCLIB DD DISP=SHR,DSN=SYS1.PROCLIB //PARMPROC DD DISP=SHR,DSN=SYS1.PARMPROC //SYSIN DD * C I=PROCLIB,O=PARMPROC SELECT MEMBER=((MEMBERA,,R))
Note the double parentheses again. This command will take MEMBERA from SYS1.PROCLIB and will copy it to SYS1.PARMPROC, and will overwrite any existing MEMBERA in the process. You might wonder what the pair of commas is all about...they indicate that a parameter has been omitted. The omitted parameter specifies the new name of a member in the target dataset:
//MYJOB JOB CLASS=A,MSGCLASS=A,REGION=256K,MSGLEVEL=(1,1) //BACKUP EXEC PGM=IEBCOPY //SYSPRINT DD SYSOUT=A //PROCLIB DD DISP=SHR,DSN=SYS1.PROCLIB //PARMPROC DD DISP=SHR,DSN=SYS1.PARMPROC //SYSIN DD * C I=PROCLIB,O=PARMPROC SELECT MEMBER=((MEMBERA,MEMBERB,R))
This will take MEMBERA, and will store it as MEMBERB in the target dataset, overwriting an existing MEMBERB.
Every so often (and more often than you like) a PDS fills up with garbage. Actually, every time you change or overwrite an existing member, this member is still part of the dataset, even if it cannot easily be accessed. When your datset fills up this way, a COMPRESS is required for garbage collection. This is done using IEBCOPY again. A compress (=garbage collection) is performed if and only if the input and the output dataset of IEBCOPY are identical:
//MYJOB JOB CLASS=A,MSGCLASS=A,REGION=256K,MSGLEVEL=(1,1) //BACKUP EXEC PGM=IEBCOPY //SYSPRINT DD SYSOUT=A //PROCLIB DD DISP=SHR,DSN=SYS1.PROCLIB //SYSIN DD * COPY INDD=PROCLIB,OUTDD=PROCLIB
would reclaim space in the SYS1.PROCLIB dataset. Another possibility to achieve the same result would have been
//MYJOB JOB CLASS=A,MSGCLASS=A,REGION=256K,MSGLEVEL=(1,1) //BACKUP EXEC PGM=IEBCOPY //SYSPRINT DD SYSOUT=A //SYSUT1 DD DISP=SHR,DSN=SYS1.PROCLIB //SYSUT2 DD DISP=SHR,DSN=SYS1.PROCLIB //SYSIN DD DUMMY
provided by Kevin Shelly
IEHLIST is a utility program used to list information from a VTOC (Volume Table of Contents), a catalog, or a PDS (Partitioned Data Set) directory.A DD reference to a disk volume that IEHLIST requires in order to read stuff on that volume. Choose any DDNAME you want. The utility doesn't care.
Utility control statements.
Utility message data set.
To list a Volume Table of Contents, you can code:
//MYJOB JOB CLASS=A,MSGCLASS=A,REGION=256K,MSGLEVEL=(1,1) //LISTVTOC EXEC PGM=IEHLIST //SYSPRINT DD SYSOUT=A //VOLDD DD UNIT=SYSDA,VOL=SER=DLIB01,DISP=OLD //SYSIN DD * LISTVTOC FORMAT,VOL=3330=DLIB01 /* //
This will print a list of the datasets on that volume along with some of their attributes. There are two choices for the type of report generated. The FORMAT option is formatted for easy reading. The DUMP format shows each record of the VTOC in a dump-like format.
To list the contents of the catalog on a particular volume, you can code:
//MYJOB JOB CLASS=A,MSGCLASS=A,REGION=256K,MSGLEVEL=(1,1) //LISTCTLG EXEC PGM=IEHLIST //SYSPRINT DD SYSOUT=A //VOLDD DD UNIT=SYSDA,VOL=SER=IPL001,DISP=OLD //SYSIN DD * LISTCTLG VOL=3330=IPL001 /* //
In order to list the contents of a PDS directory, you can code:
//MYJOB JOB CLASS=A,MSGCLASS=A,REGION=256K,MSGLEVEL=(1,1) //LISTPDS EXEC PGM=IEHLIST //SYSPRINT DD SYSOUT=A //VOLDD DD UNIT=SYSDA,VOL=SER=MVSRES,DISP=OLD //SYSIN DD * LISTPDS DSNAME=(SYS1.LINKLIB,SYS1.PL1LIB),VOL=3330=MVSRES,FORMAT /* //
Like the LISTVTOC control card, the LISTPDS control card accepts either a FORMAT option or a DUMP option. The FORMAT option is for PDS members created by the linkage editor (load modules).
Any number of control statements can be included in one IEHLIST step as long as all the referenced volumes are mentioned in DD statements similar to the VOLDD DD statement shown above. If the DASD volume is mountable instead of permanently mounted, use something like:
//VOLDD DD UNIT=(SYSDA,,DEFER),VOL=(PRIVATE,SER=PUB001),DISP=OLD
instead.
Oh Boy, do I know this utility.... When I was a rookie system programmer, my boss thought it a good idea if I spent some time in operating. Well, when I started my first shift in operating, the showed me around, nice and friendly. The same afternoon, a truckload of empty BASF magnetic tapes arrived. Can you guess who did the initialization for these tapes????
You don't really need IEHINITT
in the Hercules world, as there is
a hercules program available to do the same thing for you:
hetinit - Initialize a tape Usage: hetinit [options] filename [volser] [owner] Options: -d disable compression -h display usage summary -i create an IEHINITT formatted tape (default: on) -n create an NL tapeYou can run a little script that will initialize hundreds of emulated tapes in seconds. You need
IEHINITT
only if you have attached a real
tape drive (like a 4mm DAT tape) to your Hercules machine.
Some sample jobs follow:
//MYJOB JOB .... //****************************************** //* This job will label 10 tapes with //* standard labels, using Volume Serial //* numbers from 000001 to 000010 //****************************************** //INIT EXEC PGM=IEHINITT //SYSPRINT DD SYSOUT=* //LABEL DD UNIT=(TAPE,,DEFER) //SYSIN DD * LABEL INITT SER=000001,NUMBTAPE=10
The following job will initialize two groups of tapes:
//INIT EXEC PGM=IEHINITT //SYSPRINT DD SYSOUT=* //LABEL DD UNIT=(TAPE,,DEFER) //SYSIN DD * LABEL INITT SER=001000,NUMBTAPE=5 LABEL INITT SER=002000,NUMBTAPE=4This last job will assign disparate serial number
//INIT EXEC PGM=IEHINITT //SYSPRINT DD SYSOUT=* //DD1 DD UNIT=(TAPE,,DEFER),DCB=DEN=1 //DD2 DD UNIT=(TAPE,,DEFER),DCB=DEN=3 //SYSIN DD * DD1 INITT SER=TAPE01 DD1 INITT SER=TAPE02 DD1 INITT SER=TAPE03 DD2 INITT SER=HIGH01 DD2 INITT SER=HIGH02 DD2 INITT SER=HIGH03