BSP - Beratung, Schulung, Projekte


JCL in the N.U.D.E

Oh - You have installed MVT on Hercules? Great. And you managed to IPL the beast? Even better. You managed to generate TCAM and even (multiple) TSO user support? Purrrfect.

And now? What is the first program everyone runs in a new environment? Sure: The "Hello World" program. But ... Typing "Hello World" at the MVT master terminal didn't get you very far, did it?

Enters JCL - Job Control Language. Don't worry, this is not intended to control your Job in real life, it is intended to control the flow of work you hand to MVT

Just envision JCL to be a (stripped down) script language for the MVT (and later) Operating systems. Yes, stripped down is the word, because it really doesn't have too many features and elements. Basically, we will use only three major elements (and there a only a few more around). But that doesn't mean that JCL isn't useful - just look at the language "C" - how many language elements are in this beast? Just a few, but look what can be done with them. (Okay, unfair comparision, you may forget it)

For the following I assume that you have an initiator started that serves the jobclass A. (That would be the default. If you want another class, start an initiator using the

     S INIT.INIT1,,,C

Command at the console. INIT1 is just an identifier (you can use a different one if you want, and C is the jobclass to be supported)

I also assume that your printer (usually connected to PRT00E.TXT) serves output class A as well.

Classes? Are we at school? A more appropriate term would be Queue, a first-in-first-out mechanism. The operating system has Input queues (where jobs wait to be run), and output queues (where the output of the job waits to be printed). These queues have one letter names, and we choose A as well for the input as for the output queue name

Now for the juicy things, a few definitions:

Dataset
A collection of records/data needed/used/created by a program
Step
A program to be run, together with references to the datasets this program is going to use. It is also called a Jobstep
Job
A series of Jobsteps that belong together and a run consecutively. A job is described to the operating system via a series of JCLK statements

The first thing to learn about JCL is the "General JCL Syntax". Some simple rules

  1. Most JCL Statements start with // in column 1 and 2. (There is also a delimiter statement /*, which we discuss later)
  2. Everything to the right of column 72 is ignored
  3. JCL MUST BE CODED IN UPPERCASE
  4. Every job begins with a JOB statement
  5. Every Job has at least 1 Step (= EXEC statement)
  6. Every Step (usually) has at least on dataset (= DD statement)

That wasn't too difficult, was it? Let's take a look at a Job statement

         1         2         3         4         5         6         7
----+----0----+----0----+----0----+----0----+----0----+----0----+----0--
//MYJOB   JOB  DIRTCHEAP,VOLKER,CLASS=A

You recognize the // in columns 1 and 2. This is followed by the Jobname. This can be 1 to 8 characters, where the first character must be alphabetic, the others can be alphanumeric. For your private system you can choose any name you like. Note, that there is NO intervening blank between the JCL identifier (//) and the name. The name is followed by (at least) one space, then we have the text JOB, which is the JCL operand, telling all the world: This is a job statement!

The operand again is followed by (at least) one space. Then we have the operands, which are separated by commas. Some operands, or parameters, as they are called, have '=' sign in them, we call them keyword parameters, the others are called positional parameters. Positionals must be before the keyword parameters. For your private MVT system the first parms in the JOB statement may be omitted. They are the account code (whom are you going to charge, anyways), and the Programmer's name.

The CLASS=A parameter tells MVT to run the job in an initiator that serves the input queue with the name A

There are more parameters available in the JOB statement, some of them used often, some not so often. How do you put them all into 72 columns? You can't!. You will have to continue the JCL statement. Continuation is simple. Just remember that the parameters a separated from each other via a comma. If the system sees a comma, followed by a space, then it assumes that the statement is continued in the next line, somewhere between colum 4 an 16. I recommend that all continuation should be placed in column 16. Let's look at a longer JOB statement

         1     1   2         3         4         5         6         7 7
----+----0----+6---0----+----0----+----0----+----0----+----0----+----0-2
//MYJOB   JOB  CLASS=A,MSGCLASS=A,
//             MSGLEVEL=(1,1)

Four things are new in this statement:

And what is the fourth new element here? The fact that a keyword parameter (MSGLEVEL) has a list of positional subparameters. A list of subparameters is enclosed in brackets, and the sub-parameters are separated from each other with commas. In this case we told the system that we want all JCL messages (that was the first "1"), and all allocation messages (that is the cond "1"). If you don't want a certain message type, put a 0 there instead. I recommend a 1, though

Now to running a program. Oh, you didn't write a program yet? No problem, IBM has provided a few programs for you. These programs are called Utilities, little helpers. One of them is called IEBGENER (well, don't mind the weird names, they are just names): Our JCL now looks like this:

         1     1   2         3         4         5         6         7 7
----+----0----+6---0----+----0----+----0----+----0----+----0----+----0-2
//MYJOB   JOB  CLASS=A,MSGCLASS=A,
//             MSGLEVEL=(1,1),REGION=256K
//S1      EXEC PGM=IEBGENER

The system is requested to run the program IEBGENER. S1 is (an optional) Stepname (with the same rules as for the Jobname). We added a REGION=256K parameter to the jobcard, which tells the system to provide an area of 256K of memory for each step in the job

The IEBGENER program is a rather powerfull little thing. One of the functions it can do is copying datasets from one place to another. The datasets need to be described in the JCL with DD (for Data Definition) statements . Our JCL now changes to

         1     1   2         3         4         5         6         7 7
----+----0----+6---0----+----0----+----0----+----0----+----0----+----0-2
//MYJOB   JOB  CLASS=A,MSGCLASS=A,
//             MSGLEVEL=(1,1),REGION=256K
//S1      EXEC PGM=IEBGENER
//SYSUT1   DD  DISP=SHR,DSN=SYS1.PROCLIB(INIT)
//SYSUT2   DD  SYSOUT=A
//SYSPRINT DD  SYSOUT=A
//SYSIN    DD  DUMMY
//

Before I forget about htis- note the last line of the JCL stream. A line with only two slashess (//) at hte beginning tell MVT that this is the end of the JCL stream. If you omit this line the system will wait for it and not start the job at all. That's why I have one member with only a // line in it ti complete a job where I forgot the EOJ indicator

The DD statements define the datasets to be used. Each (actually this is not quite true) DD statement must have a name (The usual name rules apply again). This name is called the DDNAME. If you access a dataset that is already in the system and "catalogued" (whatever this means), then you need to specify the name of the dataset (DSN, for DATASET NAME). The name of a dataset is made up from simple names, separated by dots '.' - and the simple names...can again be up to 8 characters, the first one must be alphabetic, the others alphanumeric. Thus SYS1.PROCLIB is a "qualified" dataset name.

If a dataset is a PDS (=Partitioned Data Set), then you can refer to one of its members by enclosing it in brackets and appending it to the dataset name. Looking at SYSUT1 in the above JCL now tells you that this DD statement refers to member INIT of PDS SYS1.PROCLIB.

Well - what we haven't told you yet: SYSUT1 is the DD statement that is used for the INPUT of the IEBGENER copy operation

Could you guess what SYSUT2 might be? Correct: It is the DD statement which describes the output dataset of the copy operation. Or short:

IEBGENER copies sequential datasets from SYSUT1 to SYSUT2

What dataset is described in SYSUT2 in our JCL? No "real" dataset at all, but an output queue, as specified by the SYSOUT keyword

The job, as written, copies the contents of SYS1.PROCLIB(INIT) to the output queue A, with other words, the member INIT will be printed

What about SYSPRINT and SYSIN DD statements? Well, leave them as is for the moment. SYSIN would be the place where you give additional commands to IEBGENER, and SYSPRINT is the description of the dataset where IEBGENER places it's responses to your command.

Make a subdirectory jcl under your Hercules directory. Place a file iebgener.jcl with the above contents into this subdirectory. At the HERCULES CONSOLE (no, NOT the MVS console, the Hercules console!!) enter the command

devinit 00a jcl/iebgener.jcl

The jobn should run, and after some moments you could look at the file prt00e.txt to see the jobs output

Well, now that you know about dataset names, what is this other parameter in the SYSUT1 DD statement all about? DISP=SHR?

When you tell MVT that you want to use a dataset, you have to give it some information what you intend to do with it in order to provide some (minimal) locking mechanism. This is done using the DISP (for disposition= keyword parameter. It takes up to three positional subparameters. The first (or possibly only) parameter specifies the disposition at the beginning of the step

DISP=SHR
This is an existing file and I just want to read it, and so may others
DISP=OLD
This is an existing file, and I want to change/overwrite it
DISP=MOD
This is an existing sequential file, and I want to append to it
DISP=NEW
This file will be created in this step

In the last three cases I want to become the only user of the file, anybody else please wait his turn

The second parameter specifies what should happen with the file at step end. The options are

DISP=(...,KEEP)
Keep the file as is
DISP=(...,CATLG)
Keep the file and make an entry in the catalog
DISP=(...,PASS)
Keep the file, and let another step make the final decision
DISP=(...,DELETE)
Remove the file from the disk and the catalog

There can be a third parameter (which can take the same values as the second subparameter) which specifies what the system should do with the datset if the jobstep dies, or abends (from ABnormal END). But this situation of course never happens to you, or me, or anybody alse, does it?

Look at the following job

         1     1   2         3         4         5         6         7 7
----+----0----+6---0----+----0----+----0----+----0----+----0----+----0-2
//MYJOB   JOB  CLASS=A,MSGCLASS=A,
//             MSGLEVEL=(1,1),REGION=256K
//S1      EXEC PGM=IEBGENER
//SYSUT1   DD  DISP=SHR,DSN=SYS1.PROCLIB(INIT)
//SYSUT2   DD  DISP=OLD,DSN=VOLKER.SEQ.FILE
//SYSPRINT DD  SYSOUT=A
//SYSIN    DD  DUMMY
//

If you try to run it on your system, you will most probably get a message

        JOB NOT RUN, JCL ERROR
because you do not have a dataset called VOLKER.SEQ.FILE anywhere on your system. (DISP=OLD means, the requested file EXISTS at the beginning of the jobstep)

Let us change the JCL now:

         1     1   2         3         4         5         6         7 7
----+----0----+6---0----+----0----+----0----+----0----+----0----+----0-2
//MYJOB   JOB  CLASS=A,MSGCLASS=A,
//             MSGLEVEL=(1,1),REGION=256K
//S1      EXEC PGM=IEBGENER
//SYSUT1   DD  DISP=SHR,DSN=SYS1.PROCLIB(INIT)
//SYSUT2   DD  DISP=(NEW,CATLG),DSN=VOLKER.SEQ.FILE
//SYSPRINT DD  SYSOUT=A
//SYSIN    DD  DUMMY
//

This seems to be better, as it specifies to MVT that the file VOLKER.SEQ.FILE should be created at the beginning of the step, and should be catalogued (for easier retrieval) at the end of the jobstep. So far, so good. BŁT - when MVT was conceived, disk space was extremely expensive, and lots of data was store on cheaper media, like tapes. Also, when using the vastly expensive disks, you had to guestimate how much disk space you would use. This is done with the SPACE parameter on the DD statement. The syntax would be

        SPACE=(unit,(primary,secondary,directory))

This sure looks like technical mumble jumble, doesn't it. Okay, some rules. On your private MVT system use CYL as the unit. Period. Disk space is CHEAP, nowadays. And how much is a cylinder? Wait a moment, I'll look it up for you ... assuming that you have created your MVT system with 3330 Model 1 disks, you have

        13030 bytes per TRK             (meaning tracks)
        19 TRK = 247 570 bytes per CYL  (meaning cylinders)
Sometimes, the unit is given in number of bytes per I/O (the so called block size). Thus
SPACE=(TRK,5)
tells the system to give you 5 tracks. A setting of
SPACE=(CYL,(10,5)
Requests 10 cylinders of space. If these fill up, you will get additional 5 cylinders (a so called secondary extent). And if this one fills up - you get another secondary extent, until you have reached a maximum of 15 extents. Seems complicated, but really isn't that much.
SPACE=(3120,(200,100))
will give you 200 blocks of 3120 bytes, and then secondary extents of 100 blocks (of 3120 bytes again) until you reached the maximum number of extents.

All this allocation stuff only makes sense on disks, not for tapes. Therefore you have to specify where the dataset should be located. This is done with the UNIT= keyword parameter.

Your best option is to specify UNIT=SYSDA, if you want the dataset to be placed on a disk, or UNIT=TAPE (guess where the dataset will go then...) For some strange reason these are called esoteric device names. If you want to be more specific (but there is usually no need to do so), you could code the generic device name like in UNIT=3330 or UNIT=2400

Our JCL now looks like this

//MYJOB   JOB  CLASS=A,MSGCLASS=A,MSGLEVEL=(1,1),REGION=256K
//S1      EXEC PGM=IEBGENER
//SYSUT1   DD  DISP=SHR,DSN=SYS1.PROCLIB(INIT)
//SYSUT2   DD  DISP=(NEW,CATLG),DSN=VOLKER.SEQ.FILE,
//             UNIT=SYSDA,
//             SPACE=(CYL,(1,1)),
//             VOL=SER=WORK01
//SYSPRINT DD  SYSOUT=A
//SYSIN    DD  DUMMY
//

Hold the press! What is the VOL=SER=WORK01 doing here? Well, you can explicitly tell MVT onto which disk drive to place the dataset. We don't use mount points, we don't use drive letter, in MVT we use drive names, which are 6 characters (not 8, as you might have thought). You gave each disk drive a name during the DASDLOAD operation when you loaded the MVT disk drives. In the job above you tell the system to put the new dataset onto the disk drive with the VOLume SERial number WORK01. If you omit the VOLume parameter, the system will select a disk drive for you. The job above will copy one member of a partitioned dataset into a newly created sequential file. You do not need to specify any more descriptive parameter, as IEBGENER takes the input dataset as a "template" for the target dataset. But you could, and in somme situation should, specify the physical layout of the dataset explicitly. This can be done very easily with the DCB parameter:

//MYJOB   JOB  CLASS=A,MSGCLASS=A,MSGLEVEL=(1,1),REGION=256K
//S1      EXEC PGM=IEBGENER
//SYSUT1   DD  DISP=SHR,DSN=SYS1.PROCLIB(INIT)
//SYSUT2   DD  DISP=(NEW,CATLG),DSN=VOLKER.PDS.FILE(NEWMEM),
//             UNIT=SYSDA,
//             SPACE=(CYL,(1,1,5)),
//             VOL=SER=WORK01,
//             DCB=(RECFM=FB,LRECL=80,BLKSIZE=5600)
//SYSPRINT DD  SYSOUT=A
//SYSIN    DD  DUMMY
//

This tells MVT that the dataset has Fixed length records (RECFM=F_) which are Blocked (RECFM=_B). Each record has a Logical RECord L of 80 (LRECL=80) and each Block has a size of 5600 bytes (BLKSIZE=5600) Some valid option are

        F = fixed       B = Blocked     A = ASA Print control characters
        V = variable                    M = Machine print control characters
        U = unspecified

If you already have a dataset that has the correct attribute, you can use this dataset as a template

//             DCB=ANOTHER.ALREADY.EXISTING.DATASET

What, if we wanted to copy to a partitioned data set? Well, we have to tell MVT two things

  1. We want to create a PDS, and not a sequential file
  2. We need to specify the name of the member in the target dataset
Our JCL now changes to

//MYJOB   JOB  CLASS=A,MSGCLASS=A,MSGLEVEL=(1,1),REGION=256K
//S1      EXEC PGM=IEBGENER
//SYSUT1   DD  DISP=SHR,DSN=SYS1.PROCLIB(INIT)
//SYSUT2   DD  DISP=(NEW,CATLG),DSN=VOLKER.PDS.FILE(NEWMEM),
//             UNIT=SYSDA,
//             SPACE=(CYL,(1,1,5)),
//             VOL=SER=WORK01,
//             DCB=SYS1.PROCLIB
//SYSPRINT DD  SYSOUT=A
//SYSIN    DD  DUMMY
//

Note the member name in the SYSUT2 dataset, and the third sub-parameter in the SPACE keyword. This third sub-parameter, if specified, determines how many members can be stored in the PDS. The number 5 does NOT mean, that you can have 5 members. It tells MVT to reserved 5 blocks of 256 bytes for directory information. Such a directory block can store information of at least 5 members, sometimes more. Therefore the file being created can contain at least 25 members in its directory. Usually it is better to over-estimate the number of directory blocks. If youi run out of directory space than no new members can be stored in the file, even if there is lots of empty space available in the file and on the disk.

Okay, if you had a dataset with the text "Hello World" in it, you could now print it. But you haven't. Still, it is possible to print the "Hello World" phrase with JCL:

//MYJOB   JOB  CLASS=A,MSGCLASS=A,MSGLEVEL=(1,1),REGION=256K
//S1      EXEC PGM=IEBGENER
//SYSUT1   DD  *
Hello World
//SYSUT2   DD  SYSOUT=A
//SYSPRINT DD  SYSOUT=A
//SYSIN    DD  DUMMY
//

Remember, SYSUT1 provides the input data for the copy operation. the DD * special code tells MVT that the data is provided as part of the JCL stream, immediately following the DD * line. There can be 1 or more lines following the DD * line, and they will be read by IEBGENER and placed to SYSUT2 (Output queue A in this example). Where do the input data stop? At the next JCL statement, of course. And JCL statements are identified by either // or /* in the first two positions. Therefore, you can also have a nice banner like this:

//MYJOB   JOB  CLASS=A,MSGCLASS=A,MSGLEVEL=(1,1),REGION=256K
//S1      EXEC PGM=IEBGENER
//SYSUT1   DD  *
**********************************************************
*                                                        *
*                       Hello World                      *
*                                                        *
**********************************************************
//SYSUT2   DD  SYSOUT=A
//SYSPRINT DD  SYSOUT=A
//SYSIN    DD  DUMMY
//

So far, so good. But what, if there is a // in column 1 of the data? You use a different indicator for instream data:

//MYJOB   JOB  CLASS=A,MSGCLASS=A,MSGLEVEL=(1,1),REGION=256K
//S1      EXEC PGM=IEBGENER
//SYSUT1   DD  DATA
/////////////////////////////////////////////////////////
//                                                     //
//                      Hello World                    //
//                                                     //
/////////////////////////////////////////////////////////
/*
//SYSUT2   DD  SYSOUT=A
//SYSPRINT DD  SYSOUT=A
//SYSIN    DD  DUMMY
//

DD DATA indicates that all the following lines are instream data, up to (but not including) the delimiter /*. Your next question now is perfectly obvious: Waht if the data contains /* as well? IBM has thought of this and has givven you the chance to define your own instream-data-delimiter:

//MYJOB   JOB  CLASS=A,MSGCLASS=A,MSGLEVEL=(1,1),REGION=256K
//S1      EXEC PGM=IEBGENER
//SYSUT1   DD  DATA,DLM=$$
/*******************************************************/
/*                                                     */
/*                      Hello World                    */
/*                                                     */
/*******************************************************/
$$
//SYSUT2   DD  SYSOUT=A
//SYSPRINT DD  SYSOUT=A
//SYSIN    DD  DUMMY
//

In this example we chose the delimiter to be $$. You can choose any combination of characters, but, this two byte combination may not occurs in columns 1 and two of any input line.

The program IEBGENER, being an IBM supplied utility, will be found by the system without you having to do anything. But what if you want to run a program that you have created yourself? Well, you could place it into the system library SYS1.LINKLIB, but I would strongly advise against it. It is better, usually, to create your own private library into which your program will be placed. Then you must tell MVT that your private library is to be searched as well, either by specifying it in the LNKLST00 member of SYS1.PARMLIB (and if this is greek to you, you should consult the MVT basics NUDE document), or you can specify the library inside your JCL:

//MYJOB   JOB  CLASS=A,MSGCLASS=A,MSGLEVEL=(1,1),REGION=256K
//JOBLIB   DD  DISP=SHR,DSN=MY.GLOBAL.LOADLIB
//S1      EXEC PGM=MYPGM01
//STEPLIB  DD  DISP=SHR,DSN=MY.PRIVATE.LOADLIB
//DD1      DD  ....
//S2      EXEC PGM=OTHRPGM2
//STEPLIB  DD  DISP=SHR,DSN=ANOTHER.TEST.LOADLIB
//S3      EXEC PGM=LASTPGM
//.....
//S4      EXEC PGM=REALLAST
//

The //JOBLIB DD statement add the named library to the beginning of the MVT searchpath. This is valid for ALL steps in the job, unless a step has a //STEPLIB DD statement. The //STEPLIB DD statement replaces the puts its dataset at the beginning of the searchpath, overwriting the JOBLIB entry. In this case, therefore, program MYPGM01 will be taken from MY.PRIVATE.LOADLIB, if present, or froma system library. If it isn't found there, either, the jobstep will abend, with an abend code of S806. The program LASTPGM will be taken form MY.GLOBAL.LOADLIB, or the system libraries.

And what is if I have my programs spread of several libraries? You can only have one //STEPLIB DD statement per step, and only one //JOBLIB DD statement per job. In mVT, though, you can combine several datasets logically into one, using something called concatenation.

That's a strange word, isn't it? Well, it means "chaining together" and is done like this

//MYJOB   JOB  CLASS=A,MSGCLASS=A,MSGLEVEL=(1,1),REGION=256K
//JOBLIB   DD  DISP=SHR,DSN=MY.GLOBAL.LOADLIB
//S1      EXEC PGM=MYPGM01
//STEPLIB  DD  DISP=SHR,DSN=MY.PRIVATE.LOADLIB
//DD1      DD  ....
//S2      EXEC PGM=OTHRPGM2
//STEPLIB  DD  DISP=SHR,DSN=ANOTHER.TEST.LOADLIB
//S3      EXEC PGM=LASTPGM
//.....
//S4      EXEC PGM=REALLAST
//STEPLIB  DD  DISP=SHR,DSN=MY.PRIVATE.LOADLIB
//         DD  DISP=SHR,DSN=MY.GLOBAL.LOADLIB
//

i.e, you follow a named DD statement by one or more unnamed DD statements (up to 255). The system will first look into the first dataset, then into the second, etc. etc.

for the REALLAST program this means that first the MY.PRIVATE.LOADLIB will be searched, and then the MY.GLOBAL.LOADLIB (which coincidentally, is the JOBLIB)

Literature

You can still order a red reference card from IBM for JCL: SX28-6783-0: OS/360 Job Control Language: Syntax Reference Summary. It costs only a few dollars and is quite handy You can access IBM JCL manuals via the net:


OS/390 MVS JCL User Guide
OS/390 MVS JCL Reference
z/OS 1.4 JCL User Guide
z/OS 1.4 JCL Reference

You can also download a printable PDF version of the documents
OS/390 MVS JCL User's guide
OS/390 MVS JCL Reference
z/OS 1.4 JCL User Guide
z/OS 1.4 JCL Reference

Keep in mind, though, that these are IBM manuals, i.e. the reference won't teach you, it will list the syntax with all its nooks and crannies You might want to start reading with the User's guide - have fun.

You should also keep in mind that the manuals at the IBM site contain the JCL of today, i.e. OS/390 Version 2.10 or later. Many features available today were not around 40 years ago, when JCL was invented. The OS/360 JCL is a (large) subset of the OS/390 JCL

Enjoy your private hercules mainframe



© Volker Bandke