BSP - Beratung, Schulung, Projekte


Table of Contents

MVS Utilities

Introduction

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

  • Wear a belt
  • Wear suspenders
  • Keep your hands in your pockets
  • 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.

    General Syntax Guidelines

    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

    SYSPRINT

    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

    SYSIN

    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:

    Table of Contents

    Utility Programs

    The following utility program will be discussed in this article

    IEFBR14
    A Dummy Program. Does Nothing. Really. Nothing. Nada. Ne Riens. Inge Ting. Nix.
    IEBGENER
    Copies sequential data sets
    IEBCOPY
    Copy members or complete PDS
    IEHINITT
    Initialize a new magnetic tape
    IEHLIST
    Prints datasets and VTOC information
    IEHMOVE
    Copy/move datasets from one device to another
    BSPOSCMD
    Run MVS OS commands from batch
    BSPSETPF
    Set console function keys from batch
    SACLIP
    Relabel a DASD device

    Table of Contents

    IEFBR14

    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!!!

    Table of Contents

    IEBGENER

    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.

    Required DD statements

    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:

    Table of Contents

    IEBCOPY

    IEBCOPY is a utility program used to copy partitioned datasets (aka libraries)

    Required DD statements

    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.

    Compress

    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
    

    Table of Contents

    IEHLIST

    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.

    Required DD statements

    anyname

    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.

    SYSIN

    Utility control statements.

    SYSPRINT

    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.

    Table of Contents

    IEHINITT

    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 tape
    
    
    You 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=4
    
    This 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