2.3 EXEC语句
EXEC语句表明作业或过程中的每一个作业步的开始,并告知系统如何执行该作业步。EXEC语句格式如下:
//[作业步名] EXEC位置参数[,关键字参数]…[符号参数=值]…[注释]
为了执行作业中的程序,你必须使用EXEC语句。每个作业步由EXEC语句开始并指定要执行的程序名,一个作业最多有255个作业步。EXEC语句通常用来调用你要执行的程序,这是作业步的一个重要组成部分。此外,你也可以使用EXEC语句调用一个编目过程(Cataloged procedure)。
什么是编目过程?编目过程是存放在过程库中且可以使用成员名调用的一组作业流语句的集合。过程可以包含一个或多个EXEC语句。
在介绍JOB语句时,你知道JOB语句有特定的编码规则。类似地,EXEC也要遵守这些JCL语法。像JOB语句一样,EXEC语句也有5个字段,它们是:
● 标识符字段(//):占据第1列和第2列。
● 名字字段:指定作业步的名字,从第3列开始。
● 操作符字段(EXEC):说明JCL语句的类型。
● 参数字段:用来说明EXEC语句使用的参数。
● 注解字段:注解是可选的,即只提供注释功能,不影响运行结果。
图2.2显示的是EXEC语句的编写规则。
这里显示的例子中,作业步的名字是STEP1,操作符是EXEC,位置参数是PGM=IEBGENER,注解为EXEC语句。什么是IEBGENER? IEBGENER是一个系统例程(System utility)程序用来供作业步运行时调用的。关于它的具体功能,我们在后面再做详细的介绍。
图2.2 EXEC语句的编写规则
作业步名字STEP1标识对应的EXEC语句,这样,作业流中的语句就可以使用作业步的名字来访问它。即使你后面的作业流语句不访问某些作业步,我们建意你仍然给每一个EXEC语句一个作业步名。给作业步名字时必须遵照JCL的命名规则,违反规则将导致JCL错误。
下面是作业步的命名规则。
● 作业步名字必须从第3列开始。
● 作业步名字长度必须是1到8个字符。
● 作业步名字的第1个字符必须是字母(A到Z)或通配符(#、@和$)。
● 第1个字符不能为数字。
● 作业步名字的其余字符只能是字母数字或通配符。
● 特殊字符和空格不能在作业步名字中使用。
下面的表2.2给出几个正确与错误的作业步名字的例子。
表2.2 正确与错误的作业步名字的例子
2.3.1 EXEC语句的位置参数
EXEC语句中的位置参数有两个:PGM=和PROC=。每条EXEC语句必须有且仅有一个位置参数或过程名。跟在操作符EXEC后面的参数部分可以有多个参数。EXEC语句的第1个参数是位置参数,用来指定作业步要执行的程序或要调用的过程。这个位置参数的编码看起来很像关键字参数,因为,它使用了PGM=或PROC=短语。下面是位置参数的例子。
//STEP1 EXEC procedure-name 调用过程 //STEP2 EXEC PROC=procedure 调用过程 //STEP1 EXEC PGM=program-name执行程序
2.3.1.1 PGM=位置参数
PGM=指定作业步要运行的程序,其格式如下:
PGM={program-name} {*.stepname.ddname} {*.stepname.procstepname.ddname}
在下面的例子中,作业步STEP1执行的是程序COMPARE,而名为STEP3的EXEC语句采用程序间接调用方式,所调用的程序名由作业步STEP2中名为DDA的DD语句决定,在该DD语句中定义了系统库SYS1.LINKLIB,程序P40是该库的一个成员。“P40”即STEP3中要调用执行的程序名。
//MYJOB1 JOB 168,NEWMAN,CLASS=B,MSGCLASS=H //STEP1 EXEC PGM=COMPARE //STEP2 EXEC PGM=UPDT //DDA DD DSNAME=SYS1.LINKLIB(P40),DISP=OLD //STEP3 EXEC PGM=*.STEP2.DDA
2.3.1.2 PROC=位置参数
PROC=指定系统要执行的过程。其格式如下:
{PROC=procedure-name} {procedure-name}
其中,procedure-name指定需要调用的过程名。过程名由1~8个字母或通配符开头的字符数字构成。所调用的过程名可以是:
● 编目过程的成员名或别名。
● 由PROC语句定义的流内过程的过程名,该流内过程必须在本作业内且本作业步前定义。
下面的例子演示了PROC=位置参数在EXEC语句中的用法。其中,作业步STEP1执行的是过程MYPROC,而STEP2执行的是过程PAYWRKS。
//MYJOB1 JOB 168,NEWMAN,CLASS=B,MSGCLASS=H //STEP1 EXEC PROC=MYPROC //STEP2 EXEC PROC=PAYWRKS
2.3.1.3 省略PROC=关键字
如果你省略PGM=或PROC=关键字,操作系统就会自动当成你所要调用的是过程,并帮你到对应的过程库中查找它们。下面显示的是省略PROC=关键字的EXEC语句的语法格式。
//STEP1 EXEC procedure-name
比如,在下面的例子中,作业步STEP1调用的是名为MYPROC的过程,而STEP2调用的则是名为OPERATE的过程。
//MYJOB1 JOB 168,NEWMAN,CLASS=B,MSGCLASS=H //STEP1 EXEC MYPROC //STEP2 EXEC OPERATE
作为JCL编写者,如果你没有遵守关于PGM和PROC位置参数的编写规则,就会在执行时出现JCL错误。比如,如果你将PGM拼写为PGN,下面的EXEC语句就会在运行时出错。
//MYJOB1 JOB 168,NEWMAN,CLASS=B,MSGCLASS=H //STEP1 EXEC PGN=COMPARE
在下面的例子中,等号(=)的前后都有空格,这时,系统就会将“PGM”当成一个过程名而不当成EXEC语句的位置参数来处理。当运行作业流时,系统就会返回过程没找到的错误信息。解决的办法是去掉等号前后的空格。
//MYJOB1 JOB 168,NEWMAN,CLASS=B,MSGCLASS=H //STEP1 EXEC PGM = COMPARE
2.3.2 关键字参数
你可以按任何顺序编写EXEC语句中的关键字参数,它们必须跟在要执行的程序名或过程名的后面。下面是在EXEC语句中可以使用的关键字参数的清单。
ACCT、RD、DYNAMNBR、ADDRSPC、PERFORM、REGION、PARM、COND、TIME、DPRTY和CSID在EXEC语句中编写的关键字参数的值只适用于本作业步。两个在EXEC语句中最常使用的关键字参数是:
● PARM参数。
● COND参数。
2.3.2.1 PARM参数
PARM参数用于向本作业步执行的程序传递变量信息。该程序必须有相应的指令接收这些信息,并使用它们。PARM参数的格式为:
●(1)PARM[.过程步名]= 子参数
●(2)PARM[.过程步名]=(子参数,子参数)
●(3)PARM[.过程步名]=(‘子参数’,子参数)
●(4)PARM[.过程步名]=‘子参数,子参数’
其中,子参数包含传递给程序的变量信息,包括所有的逗号、撇号及括号在内,所有子参数的总长度不得超过100个字符。当某子参数中含有特殊字符或空格时,可以将该子参数用引号括起来,与其他子参数一起时需要用括号括起来,或将所有参数用引号括起来。
编写PARM参数的规则是:
● PARM参数最多包含100个字符。
● PARM可以由多个用逗号隔开的值组成。
● 子参数必须用括号或撇号括起来。
● 特殊字符必须用撇号括起来。
在下面的例子中,系统将参数P1、123及P2=5传递给作业步STEP1执行的程序APG22,并将MAP、LET传递到过程ASFCLG中名为LKED的过程步。
//MYJOB1 JOB 168,NEWMAN,CLASS=B,MSGCLASS=H //STEP1 EXEC PGM=APG22,PARM=’P1,123,P2=5’ //STEP2 EXEC PROC=ASFCLG,PARM.LKED=(MAP,LET)
2.3.2.2 COND参数
正如我前面介绍的,在JOB语句中使用COND参数可以对整个作业进行控制。你也可以在作业步的EXEC语句中使用COND语句来控制单个作业步。
当COND参数在EXEC语句中使用时,可以让系统检查所有已经执行的作业步的返回码,以决定是否要跳过(不执行)当前作业步。如果任何一个条件成立,系统就会跳过该作业步。
COND参数用于对先前作业步执行的返回码进行测试,以决定是否执行本作业步。其格式为:
COND=(code,operator) 或 COND=(code,operator,stepname) 或 COND=(code,operator,stepname.procstepname)
正如JOB语句一样,代码(code)子参数表示返回码而操作符(Operator)子参数表示要测试的比较类型。如果你只是指定了代码(Code)和操作符(Operator)子参数,测试就会是对作业中所有正确的作业步而言的。子参数stepname.procstepname而不是子参数stepname,只是对已经执行的特定作业步的返回码进行比较。你可以指定的代码只可以从0到4095,用来与COND参数中的返回码比较。你一共可以编写8个比较运算,只要有一个比较值为真,该作业步就会跳过。
像JOB语句中的COND参数一样,EXEC语句中的COND参数中的每个测试都有自己的运算符。每个测试中的运算符是独立于其他测试中的运算符的。
● GT大于(Greater than)。
● GE大于等于(Greater than or equal to)。
● EQ等于(Equal to)。
● NE不等于(Not equal to)。
● LT小于(Less than)。
● LE小于或等于(Less than or equal to)。
下面是COND参数的一些例子。
//MYJOB1 JOB 168,NEWMAN,CLASS=B,MSGCLASS=H //STEP1 EXEC PGM=IEFBR14 //STEP2 EXEC PGM=DISKUTIL,COND=(4,GT,STEP1)
本例中的COND条件可以翻译为:如果4大于作业步STEP1的返回码,就不执行作业步STEP2。换句话说,如果STEP1的返回码小于4,系统将不执行STEP2。由于没有设置EVEN或ONLY,如果先前的作业步异常终止,系统将不会执行本作业步。
//MYJOB1 JOB 168,NEWMAN,CLASS=B,MSGCLASS=H //STEP1 EXEC PGM=DELETE //STEP2 EXEC PGM=UPDATE, // COND=(8,GT) //DD1 DD DSN=INPUT
本例中的COND参数可以翻译为:如果8大于前面任何作业步的返回码,就不执行作业步STEP2,换句话说,如果前面任何作业步的返回码小于8,作业步(STEP2)就会跳过。
//MYJOB1 JOB 168,NEWMAN,CLASS=B,MSGCLASS=H //STEP1 EXEC PGM=DELETE //STEP2 EXEC PGM=UPDATE, // COND=(8,GT,STEP1) //DD1 DD DSN=INPUT
作业步STEP2中的COND参数包含了作业步子参数,因此,这条COND语句可以翻译为:如果作业步STEP1的返回码小于8,就跳过作业步STEP2。
//MYJOB1 JOB 168,NEWMAN,CLASS=B,MSGCLASS=H //STEP1 EXEC PGM=CINDY //STEP2 EXEC PGM=NEXT,COND=(4,EQ,STEP1) //STEP3 EXEC PGM=LAST ,COND=((8,LT,STEP1),(8,GT,STEP2))
在本例中,如果STEP1的返回码为4,STEP2将不被执行。在STEP3执行前,系统将执行第1个返回码测试。而由于STEP2并未被执行,所以将不会进行第2个返回码的测试。由于8大于4所以STEP3被执行。
COND参数除了可以使用代码(Code)、操作符(Operator)、作业步名(Stepname)和过程步名(Procedure Stepname)外,还可以使用EVEN和ONLY子参数。
对于正常结束的程序的返回码,这两个子参数不适用。它们只与前面异常结束的作业步有关。当作业步运行过程中出现意外状况而程序不能处理时,就会异常结束(Abnormal Termination)了。
如果COND参数中不使用EVEN或ONLY参数,当作业中有程序异常结束时,就会跳过(不执行)所有剩下的作业步。
EVEN(即使)子参数,顾名思义,就是即使前面有作业步异常结束了,当前作业步也要执行。反过来,ONLY(只有)子参数,同样从字面上理解,只有当前面的作业步异常结束时,当前的作业步才执行。
下面来看一个EVEN子参数的例子。
//MYJOB1 JOB 168,NEWMAN,CLASS=B,MSGCLASS=H //STEP1 EXEC PGM=PGM1 //STEP2 EXEC PGM=PGM2 //STEP3 EXEC PGM=PGM3 //STEP4 EXEC PGM=PGM4,COND=EVEN //FILE1 DD DSN=IBMUSER.TEST.FILE,DISP=SHR
在上面的例子中,由于在EXEC语句中使用了COND=EVEN参数,这样,程序PGM4总是会执行的,即使前面的作业步比如作业步(STEP3)异常结束了。
EVEN表示即使前面的作业步ABEND了,本作业步也要执行;ONLY表示本作业步只有在前一个作业步ABEND的情况下才执行。下面再来看一个ONLY参数的例子。
//MYJOB1 JOB 168,NEWMAN,CLASS=B,MSGCLASS=H //STEP1 EXEC PGM=PGM1 //STEP2 EXEC PGM=PGM2 //STEP3 EXEC PGM=PGM3 //STEP4 EXEC PGM=PGM4,COND=ONLY //FILE1 DD DSN=IBMUSER.TEST.FILE,DISP=SHR
由于在上面的作业步STEP4使用了COND=ONLY参数,这样,程序PGM4只有当前面的作业步异常终止时才会执行。
由于EVEN和ONLY子参数是互相排斥的,因此,它们不能出现在同一个作业步中。但是,EVEN或ONLY可以编写在作业步容许的8个返回码的测试中,它们出现的顺序对结果没有影响。比如,图2.3左右两边的代码是等价的。
图2.3 返回码测试顺序不影响结果的例子
让我们再看下面的例子。
//MYJOB1 JOB 168,NEWMAN,CLASS=B,MSGCLASS=H //STEP1 EXEC PGM=PGM1 //STEP2 EXEC PGM=PGM2 //STEP3 EXEC PGM=PGM3,COND=(16,GE),(20,LE,STEP1),ONLY)
由于设置了ONLY子参数,系统只在下列两种情况满足时执行作业步STEP3。
● 之前作业步(STEP1或STEP2)异常终止。
● 返回值的测试条件都不满足。
换句话说,系统将会在以下3个条件都满足的情况下才执行作业步STEP3。
● 作业步STEP1或STEP2异常终止。
● 作业步STEP1和STEP2的返回码大于等于17。
● 作业步STEP1的返回码小于等于19。
2.3.2.3 程序库
在作业流中,每个作业步以EXEC语句开始,用来表明所要执行的程序。为了执行EXEC语句中的程序,系统会检索程序库。
如果你没有告诉系统你要执行的程序所在的程序库名字,系统就会自动检索你的系统程序员为公司定义的系统库;如果你指定了你自己的程序库,即所谓的私有程序库(Private Program Library),系统就会直接到你指定的程序库中检索要执行的程序。
系统库是由你的系统程序员定义在系统的参数文件LINKLIST中的。通常来讲,LINKLIST会定义多个系统程序库,其中,系统程序库SYS1.LINKLIB肯定会是其中之一。这样,当你的作业步要执行程序时,如果没有指定私有程序库,系统就会自动到LINKLIST程序库中查找所要执行的程序。不同的公司LINKLIST中定义的系统程序库是不同的,但它们都会包含系统默认检索的、装有可执行程序的数据集。
一旦系统在LINKLIST参数文件中定义的SYS1.LINKLIB中找到要执行的程序,就会调用该程序。如果系统找不到所要执行的程序,当你运行你的作业时就会异常结束(abend)。
下面的作业流例子中,程序员希望执行程序PARM1。操作系统就会到系统程序库SYS1.LINKLIB或LINKLIST检索程序PARM1。
000001 //IBMUSERG JOB ACCT#, 000002 // IBMUSER, 000003 // NOTIFY=IBMUSER, 000004 // MSGLEVEL=(1,1) 000005 //STEP1 EXEC PGM=PARM1,PARM='CHINA' 000006 //SYSPRINT DD SYSOUT=(*)
但是,由于程序PARM1并不存在于系统程序库中,系统就会显示下面的错误信息。
CSV003I REQUESTED MODULE PARM1 NOT FOUND CSV028I ABEND806-04 JOBNAME=IBMUSERG STEPNAME=STEP1
你可以将你的程序存放在私有程序库而不是系统程序库中。在上面的例子中,我们需要调用的程序是存放在私有程序库IBMUSER.TEST.LOAD中的,这样,编写作业流时,你必须告诉系统你的私有库的数据集名字。你可以使用系统特定的DD语句JOBLIB来完成这项功能。
DD语句JOBLIB让操作系统在检索系统程序库SYS1.LINKLIB之前检索你的私有程序库。如果我们希望系统从我们的私有程序库IBMUSER.TEST.LOAD中调用程序PARM1,我们就可以修改上面的作业流,并在作业流中增加JOBLIB语句。JOBLIB语句必须写在第1个EXEC语句之前。下面是修改过的作业流,我们在第5行加上了JOBLIB语句,再次提交,就不会出现上面的错误信息了。
000001 //IBMUSERG JOB ACCT#, 000002 // IBMUSER, 000003 // NOTIFY=IBMUSER, 000004 // MSGLEVEL=(1,1) 000005 //JOBLIB DD DSN=IBMUSER.TEST.LOAD,DISP=SHR 000006 //STEP1 EXEC PGM=PARM1,PARM='CHINA' 000007 //SYSPRINT DD SYSOUT=(*)
如果作业流中要执行的大部分程序存放在SYS1.LINKLIB或LINKLIST中,而只有少数程序存放在私有库中,这种情况下,让系统根据作业步的不同情况搜索私有库就更合理一些。因为这样系统可以节省不必要的搜索时间。
使用特别的DD语句STEPLIB就可以按照作业步的要求搜索私有程序库。DD句STEPLIB可以放到作业步的任何地方,但通常来说,大家都习惯将其放到EXEC语句的下面。下面是使用STEPLIB语句的例子,这样STEP1和STEP2搜索的是系统库,因为它们都是系统例程,而作业步STEP3则在我们的私有程序库IBMUSER.TEST.LOAD中搜索程序PARM1。
000001 //IBMUSERG JOB ACCT#, 000002 // IBMUSER, 000003 // NOTIFY=IBMUSER, 000004 // MSGLEVEL=(1,1) 000005 //STEP1 EXEC PGM=IEBCOPY 000006 //STEP2 EXEC PGM=IEBGENER 000007 //STEP3 EXEC PGM=PARM1,PARM='CHINA' 000008 //STEPLIB DD DSN=IBMUSER.TEST.LOAD,DISP=SHR 000009 //SYSPRINT DD SYSOUT=(*)
像JOBLIB语句一样,STEPLIB语句也是搜索私有程序库的。但是,STEPLIB语句只对其所在的作业步有效,而JOBLIB则对所有的作业步有效。
在下面的例子中,STEPLIB语句和JOBLIB语句同时出现在作业中。这时,系统就会忽略JOBLIB且对该作业步不检索JOBLIB对应的程序库。系统只检索STEPLIB指定的程序库。
如果系统在STEPLIB语句指定的程序库IBMUSER.TEST.LOAD中不能找到所需要的程序PARM1,它就会检索系统程序库SYS1LINKLIB和LINKLIST,如果也没有找到需要的程序,作业就会异常终止。
000001 //IBMUSERG JOB ACCT#, 000002 // IBMUSER, 000003 // NOTIFY=IBMUSER, 000004 // MSGLEVEL=(1,1) 000005 //JOBLIB DD DSN=IBMUSER.DB2.LOAD,DISP=SHR 000006 //STEP1 EXEC PGM=PARM1,PARM='CHINA' 000007 //STEPLIB DD DSN=IBMUSER.TEST.LOAD,DISP=SHR 000008 //SYSPRINT DD SYSOUT=(*)
作业中用到的数据集或新建立的数据集都需要一个对应的DD语句。在下面的例子中,牵涉到3个数据集,这样,在我们的作业流中就必须有3条DD语句与它们对应。
000001 //IBMUSERA JOB CLASS=A,MSGCLASS=A,MSGLEVEL=(1,1),NOTIFY=&SYSUID, 000002 // REGION=4M 000003 //GOBKUPD EXEC PGM=BKUPD1 000004 //STEPLIB DD DSN=IBMUSER.TEST.LOAD,DISP=SHR 000005 //SYSOUT DD SYSOUT=* 000006 //SYSPRINT DD SYSOUT=* 000007 //SORTOUT DD SYSOUT=* 000008 //SYSUDUMP DD SYSOUT=* 000009 //BKTRAN DD DSN=IBMUSER.TEST.BKTRAN,DISP=SHR 000010 //BKMASTO DD DSN=IBMUSER.TEST.BKMASTO,DISP=SHR 000011 //BKMASTN DD DSN=IBMUSER.TEST.BKMASTN, 000012 // DISP=(NEW,CATLG), 000013 // SPACE=(TRK,(5,5),RLSE), 000014 // DCB=(RECFM=FB,LRECL=100,DSORG=PS)
本作业流要执行的程序是我们在本书后面的COBOL部分要介绍的平衡线算法程序BKUPD1,用来完成银行批处理主文件的更新。该程序有两个输入文件,一个是银行交易文件(BKTRAN),另一个是旧主文件(BKMASTO),程序执行中会产生一个新主文件(BKMASTN),记录主文件更新后的结果。
数据定义语句(DD语句)用于定义一个数据集及该数据集所需的输入输出资源。其格式为:
//ddname DD [位置参数][,关键字参数]…[注释]
其中,ddname是DD语句定义的名字,它由1~8个字母或通配符开头的字符数字构成。在一个作业步内可以有多个DD语句,但每个DD语句的ddname在本作业步中应该是唯一确定的。ddname可以由系统定义,也可以由用户自己定义,当用户需要调用公用程序时,需根据公用程序的具体要求选用系统定义的ddname。用户自定义的ddname不可与系统定义ddname重复。
系统定义的ddname有如下几个。
● JOBCAT、SYSCHK、JOBLIB、SYSCKEOV、STEPCAT、SYSIN、STEPLIB、SYSMDUMP、SYSBEND、SYSDUMP等。
● JES2子系统中定义的有:JESJCLIN、JESMSGLG、JESJCL、JESYSMSG等。
● JES3子系统中定义的有:JCBIN、JESJCL、JS3CATLG、JCBLOCK、JESMSGLG、J3JBINFO、JCBTAB、JOURNAL、J3SCINFO、JESJCLIN、JOURNAL、J3STINFO、JESInnnn、JESYSMSG、STCINRD、TSOINRDR等。
DD语句说明数据集的基本信息,包括:
● ddname用来供作业步调用的程序访问。
● 数据集的名字。
● 数据集存放的位置。
● 数据集存取的方式。
在上面的平衡线算法作业流中,程序BKUPD1使用DDNAME BKTRAN指向银行交易文件,它是程序BKUPD1的输入文件。类似地,BKMASTO指向旧主文件,而BKMASTN则指向新主文件。
作业流中使用DD语句的优点有:
● 即使程序访问的数据集改变了,也不用重新编译程序,只要修改对应的DD语句即可。
● 增加了程序的重用性。
● 增加了系统的适应性。
2.4.1 DD语句参数
DD语句的参数也分为位置参数及关键字参数,这些参数都是可选的。每个DD语句只能有一个位置参数,但根据需要可以有多个关键字参数。
DD语句的参数有下面的属性。
● 在DD语句中使用参数,可以用来说明数据集的特性。
● DD语句中的关键字参数可以按任意顺序编写。
DD语句的位置参数有下列几个,一个DD语句只可以包含下列位置参数中的一个,这些位置参数必须位于所有关键字参数的前面。
● * 或DATA:代表内部数据流(In-stream Data)的开始。
● DUMMY:代表处理时分配的空间为空。
DD语句可以包含下列关键字参数,在参数区域内,它们可以按任何顺序,位于位置参数后的任何位置上。
● DCB:设定数据控制块的信息,例如记录长度、记录格式、块的大小等。
● DDNAME:DD的名字。
● DISP:描述数据集的状态和设定作业步正常或异常终止时数据集的处置方法。
● DSNAME:分配数据集名。
● DSNTYPE:告知系统分配的是哪一类型的数据集。
● LRECL:设定数据集记录的长度。
● RECFM:定义数据集记录的格式和特性,例如它们是定长或变长等。
● RETPD:设定数据集要保留的天数。
● SPACE:指定新数据集所需的空间。
● SYSOUT:分配输出级别。
● UNIT:分配所需的特殊设备,以及设备的类型或组别。
● VOLUME:命名数据集所在的卷标。
● 下列参数只用于SMS,如果没有安装SMS或SMS没有激活,系统进行语法检查,并忽略掉这些参数。
✧ AVGREC:在SPACE参数中设定主分配空间和次分配空间的大小。
✧ DATACLAS:指定分配新数据集的数据类别。
✧ LIKE:指定分配的新数据集与模板数据集的属性相同。
✧ MGMTCLAS:设定新数据集的管理级别。
✧ RECORG:设定VSAM数据集的类型,如KSDS、ESDS、RRDS或LDS。
✧ REFDD:根据前面DD语句的设定指定新数据集的属性。
✧ STORCLAS:指定新数据集存储类别。
2.4.2 DD语句的位置参数
DD语句的位置参数有* 或DATA,它们代表内部数据流(in-stream data)的开始。而位置参数DUMMY则代表处理时分配给文件的空间为空。
2.4.2.1 流内数据
位置参数*和DATA用于开始一个流内数据集。其格式为:
//ddnameDD { * |DATA}[,DLM=XX]
DD语句在作业流中供给作业可能的输入数据,这类数据被称为内部数据流。一个DD语句用*代表包含数据流,或DATA作为位置参数表示数据集流的开始。这些数据记录跟随在DD *或DD DATA语句的后面。
当使用DD * 时,下列情况下数据记录结束。
● 在输入数据里碰到/*。
● 碰到另一个JCL语句的开始符号//。
● JCL结束。
● 在DD语句中通过DLM参数定义任何两个分隔符,通常只用于DD DATA。
下面是使用DD *描述内部数据流的例子。
000001 //MYJOB1 JOB 168,NEWMAN,CLASS=B,MSGCLASS=H 000002 //STEP1 EXEC PGM=PGM1 000003 //INPUT1 DD * → 标识内部流数据的开始 000004 INPUT DATA OF LINE 1 FOR DATA SET INPUT1 000005 //OUTPUT1 DD DSN=IBMUSER.TEST.JCL,DISP=SHR → 新JCL语句标识内部流数据的结束 000006 //INPUT2 DD * → 标识内部流数据的开始 000007 INPUT DATA OF LINE1 FOR DATA SET INPUT2 000008123456 ABCDEF 000009789012 HUAHUA 000010 /* → /*标识内部流的结束 000011 //INPUT2 DD * → 标识内部流数据的开始 000012 INPUT DATA OF LINE1 FOR DATA SET INPUT3 000013123456 ABCDEF 000014789012 NEWMAN → JCL结束标识内部流的结束
当使用DD DATA时,数据记录的结束以下列形式表示。
✧ /* 在输入数据里。
✧ JCL结束。
✧ 在这个DD语句中通过DLM参数定义任何两个分隔符。
值得注意的是,当使用DD DATA语句时,//并不表示数据的结束,这只能激活数据流中的JCL语句,如果在输入数据里需要在第1列和第2列输入/*,就必须使用分隔符(DLM子参数)。DLM指定一个字符串作为数据流的分隔符,如果不指定,系统使用(/*)作为分隔符。当内部数据需以//开始时,就必须使用DATA参数来代替*参数。
下面是使用DATA和分隔符(DLM)的例子。
000001 //MYJOB1 JOB 168,NEWMAN,CLASS=B,MSGCLASS=H 000002 //STEP1 EXEC PGM=PGM1 000003 //INPUT1 DD DATA → 标识内部流数据的开始 000004 THIS IN-STREAM DATA CONTAIN JCL STATEMENTS 000005 //OUTPUT1 DD DSN=IBMUSER.TEST.JCL,DISP=SHR → 新JCL语句不能标识内部流数据的结束 000006 //INPUT2 DD * 000007 /* → 默认分隔符/*能标识内部流的结束
在上面的例子中,内部数据流由DD DATA开始,由默认分隔符/*结束,新的JCL语句的开始符//不能作为内部数据流结束的标志。如果你的内部数据流中需要有/*,就必须使用分隔符(DLM),下面是一个例子。
000001 //MYJOB1 JOB 168,NEWMAN,CLASS=B,MSGCLASS=H 000002 //STEP1 EXEC PGM=PGM1 000003 //INPUT2 DD DATA,DLM=$$ → 指定分隔符,标识内部流数据的开始 000004 INPUT DATA OF LINE1 FOR DATA SET INPUT2 000005 /*123456 ABCDEF 000006 //789012 NEWMAN 000007 $$ → 只有分隔符$$才能结束内部数据流 000008 //*
2.4.2.2 哑(Dummy)数据集
每个程序需要访问的数据集在作业流中都必须有一个ddname与其对应。如果程序需要的文件在作业流中没有对应的DD语句的定义,程序运行时就会异常结束(ABEND)。当输入数据集对程序来说是可选的或程序要输出的数据集不是必需的时候,我们就可以使用哑(Dummy)数据集。
哑数据集指的是所有输入、输出(I/O)操作都跳过的数据集,换句话说,哑数据集没有输入、输出操作。在JCL中,系统提供了一个特殊的DD语句,即DD DUMMY说明哑数据集,这样,程序运行时就可以跳过对它的所有输入、输出操作了。
当数据集指定为哑数据集时,它的所有I/O操作都会跳过,系统也会忽略对该数据集的设备分配、空间分配和对数据集的处置(Disposition)管理。在JCL中,你可以在DD语句中使用下面两种方法说明哑数据集。
● 编写DUMMY作为DD语句的第1个位置参数,语法格式为:
//ddnameDD DUMMY
● 编写DSN=NULLFILE,语法格式为:
//ddnameDD DSN=NULLFILE
NULLFILE是保留字,专门用来表示哑数据集的,你自己的数据集不能使用名字NULLFILE。
在我们前面提到的银行主文件更新程序中,为了检查程序是否能够处理白天没有交易,即交易文件BKTRAN为空的情况,我们就可以在下面的作业流中将BKTRAN定义为哑文件,如下面第9行所示的那样,这种方法对于测试程序处理文件的边界条件是非常有用的。
000001 //IBMUSERA JOB CLASS=A,MSGCLASS=A,MSGLEVEL=(1,1),NOTIFY=&SYSUID, 000002 // REGION=4M 000003 //GOBKUPD EXEC PGM=BKUPD1 000004 //STEPLIB DD DSN=IBMUSER.TEST.LOAD,DISP=SHR 000005 //SYSOUT DD SYSOUT=* 000006 //SYSPRINT DD SYSOUT=* 000007 //SORTOUT DD SYSOUT=* 000008 //SYSUDUMP DD SYSOUT=* 000009 //BKTRAN DD DUMMY 000010 //BKMASTO DD DSN=IBMUSER.TEST.BKMASTO,DISP=SHR 000011 //BKMASTN DD DSN=IBMUSER.TEST.BKMASTN, 000012 // DISP=(NEW,CATLG), 000013 // SPACE=(TRK,(5,5),RLSE), 000014 // DCB=(RECFM=FB,LRECL=100,DSORG=PS)
DUMMY位置参数用于说明:
● 没有设备或外存空间分配给该数据集。
● 对该数据集不进行状态处理。
● 不对该数据集做输入、输出操作。
用户通常使用DUMMY参数对程序进行测试。当测试完成时,如果用户希望恢复对数据集的输入、输出操作时,只需将DD DUMMY参数替换成完整的数据集定义DD语句即可。
2.4.3 DD语句的关键字参数
DD语句常用的关键字参数有下面几个,我们现在对它们做详细的说明。
DSN、DISP、UNIT、VOL、SPACE、LABEL、DCB和SYSOUT
2.4.3.1 DSN
DSN参数的特性是:
● 数据集名字(DSN)参数指定数据集的名字。
● 数据集分为临时数据集和非临时数据集。
我们在前面已经看到很多DSN参数的例子,如:
//BKTRAN DD DSN=IBMUSER.TEST.BKTRAN等。
临时数据集具备下面的特性。
● 临时数据集只在作业的生命周期内有效,换句话说,作业结束后,临时数据集也就不存在了。
● 临时数据集的名字可有可无,即临时数据集的名字不是必需的。
● 如果要给临时数据集命名,就必须在名字前面加上两个&符号(&&),如果不想给临时数据集命名,就可以直接省略DSN参数。
● 编写临时数据集的语法格式是:
//BKTRAN DD DSN=&&BKTRAN或 //BKTRAN DD(省略DSN参数,其他参数都是需要的)
当省略DSN参数时,系统会按照事先定好的命名规则为临时数据集分配一个唯一的名字。
非临时数据集具有下面的特性。
● 作业执行完后,非临时数据集可以保持并可以重复使用。
● 非临时数据集必须命名。
● 编写非临时数据集的语法格式是:
//BKTRAN DD DSN=IBMUSER.TEST.BKTRAN
DSNAME参数用来标识数据集的名字。对于永久性数据集,DSNAME参数总是需要的,它指定数据集的名字及在编目中的位置标号;对于临时数据集,DSNAME参数是可选的,即不是必需的。下面是一些例子。
例1:对永久数据集即非临时数据集:
//BKMAST DD DSNAME=IBMUSER.TEST.BKMAST,DISP=SHR
例2:对临时数据集:
//WORK01 DD UNIT=SYSDA,VOL=SER=DMTP01,SPACE=(CYL,(2,3))
2.4.3.2 DISP
数据集处置(DISP)参数指定数据集当前的状态及告诉系统当作业步执行完后处置数据集的方法。数据集处置(DISP)参数语法格式为:
DISP=(Disp1,Disp2,Disp3)
其中,Disp1、disp2和disp3分别对应于文件的当前状态、作业正常结束的处置方法和作业异常结束的处置方法。
● 数据集的当前状态(disp1)可以为:
✧ NEW:当前数据集不存在,作业执行完后,系统会分配新的数据集。
✧ SHR:当前数据集已存在,作业可以共享该数据集。这是默认值。
✧ OLD:当前数据集已存在,作业将独占该数据集,即作业运行完之前,其他作业不可以使用。
✧ MOD:如果当前数据集已存在,系统就会独占该数据集并将新记录追加在该数据集的末尾;如果该数据集当前不存在,系统就会建立一个新数据集。
● 作业正常结束后处置数据集的选项(disp2)有:
✧ DELETE:删除该数据集。
✧ KEEP:保留该数据集。
✧ PASS:传递给后续作业步。
✧ CATLG:将该数据集登目到系统或用户目录中。这是默认值。
✧ UNCATLG:取消该数据集在系统或用户目录中的编目。
● 作业异常结束后处置数据集的选项(disp3)有:
✧ DELETE:删除该数据集。这是默认值。
✧ KEEP:保留该数据集。
✧ CATLG:将该数据集登目到系统或用户目录中。
✧ UNCATLG:取消该数据集在系统或用户目录中的编目。
下面是DISP参数的例子。
000010 //BKMASTO DD DSN=IBMUSER.TEST.BKMASTO,DISP=SHR 000011 //BKMASTN DD DSN=IBMUSER.TEST.BKMASTN, 000012 // DISP=(NEW,CATLG,DELETE),
2.4.3.3 UNIT
UNIT参数用来标识数据集存储的设备(地址)。输入、输出设备(I/O UNIT)参数用来申请存取数据集的硬件设备的类型。你可以通过下面的3种方法指定你所需要的设备。
● 硬件地址,比如UIT=OC4,其中,0C4就是磁盘设备的地址。
● 设备类型,比如UNIT=3390,其中,3390就是一种磁盘设备。
● 设备组名,比如UNIT=SYSDA,其中,SYSDA是你的系统程序员定义的磁盘设备的组别,可能包括所有的设备类型,比如,3380、3390等。
2.4.3.4 VOLUME
VOLUME或简写为VOL参数,用来指定数据集的介质卷号或卷组的名字。介质(Media)卷可以是指定的磁带卷名TAPE01或指定的磁盘组的名字DMTP01等。下面是VOLUME参数的例子。
000011 //BKMASTN DD DSN=IBMUSER.TEST.BKMASTN, 000012 // DISP=(NEW,CATLG), 000013 // SPACE=(TRK,(5,5),RLSE), 000014 // UNIT=SYSDA, 000015 // VOL=SER=DMTP01 000014 // DCB=(RECFM=FB,LRECL=100,DSORG=PS)
2.4.3.5 SPACE
SPACE参数用来说明建立数据集所需要的存储空间的大小。SPACE参数的特性为:
● 用来指定行分配数据集所需要的DASD空间的大小。
● 数据集所需要的空间大小可以使用块(Blocks)、扇区(Tracks)或柱面(Cylinders)为单位。SPACE参数的格式为:
BLKS SPACE=(TRK,(PRIMARY,SECONDARY),DIRECTORY))) CYL
其中,
● 第1个参数指定要分配的空间的单位,其取值为:
✧ BLKS:指明以块为基本单位分配数据集存储空间。
✧ TRK:指明以磁道扇区为基本单位分配数据集存储空间。
✧ CYL:指明以磁道柱面为基本单位分配数据集存储空间。
● 第2个参数PRIMARY,指定一个新数据集生成时申请的基本存储空间,以前面指定的BLKS/TRK/CYL为单位。
● 第3个参数SECONDARY,指定第1次分配的存储空间用完以后,每个增加的存储空间数量。也是以前面指定的BLKS/TRK/CYL为单位的。注意,一个数据集最多可以扩充15次,加上第1次分配的空间,你就可以估计该文件总的分配空间的大小了。
● 第4个参数DIRECTORY,指定分配给目录空间的大小。对于顺序数据集,该值为零。只有是分区数据集(PDS)才需要指定此参数,用来存储PDS数据集成员的成员名所需存储空间的分配,以系统规定的,大小为256Byte的块为单位(目录块)。如果PDS用做程序库,每一个目录块大约可以存放5个成员。
2.4.3.6 LABEL
LABEL参数说明数据集所在的磁带或磁盘卷的标号,它也指定磁带或磁盘卷是否为写保护的。下面是一个使用LABEL参数的例子。
//BKTRAN DD LABEL=RETPD=30
这里说明的是标准的IBM保存期方法。它说明在磁带上面的数据集要保留30天,如果要指定每个要保留的天数,系统就会取默认值,比如14天作为保留的期限。
2.4.3.7 DCB
数据控制块(Data Control Block——DCB)参数用来说明程序操作的数据集的控制块信息。DCB参数可以说明数据集的类型、块大小及逻辑记录长度等。
数据控制块(DCB)的格式为:
DCB=(DSORG=PS,RECFM=FORMAT, LRECL=NUM1,BLKSIZE=NUM2)
DCB参数有:
● DSORG:指定数据集的组织形式,其值可以如下。
✧ PS:物理顺序文件(Physical Sequential)。
✧ PO:分区数据集(Partitioned)。
✧ DA:直接存取数据集(Direct)。
✧ IS:索引顺序数据集(Indexed Sequential)。
● RECFM:记录格式(Record Format),其取值可以为F、V、FB、VB、FBA、VBA等。
● LRECL:逻辑记录长度(Logical Record Length)。
● BLKSIZE:记录块的大小(Block Size)。
● RECFM、LRECL和BLKSIZE之间的关系如下。
✧ RECFM=F,定长记录,LRECL=BLKSIZE,即一个块只包含一条逻辑记录。
✧ RECFM=FB,定长记录,BLKSIZE=n*LRECL,即一个块包含多条逻辑记录。
✧ RECFM=V,变长记录,LRECL=最长的数据记录+4 Byte,BLKSIZE=LRECL+4 Byte。
✧ RECFM=VB,变长记录,LRECL=最长的数据记录+4 Byte,BLKSIZE=最长的数据块+4 Byte并至少=LRECL+4。
✧ RECFM=FA/FBA,定长打印记录,LRECL=实际记录长度+1个打印控制字符。
✧ RECFM=VB/VBA,变长打印记录,LRECL=最长数据记录+4 Byte+1个打印控制字符。
下面是一个DCB的例子。
//DATA1 DD DCB=(RECFM=FB,LRECL=80,BLKSIZE=6400)
2.4.3.8 SYSOUT
SYSOUT数据集用来存放输出数据流,即作业运行后结果,通常需要输出到打印机。SYSOUT参数用来指定系统输出数据集及它的输出类别(class)。
该DD语句将作业运行后的输出发送到指定的输出类别对应的打印机上。输出类别可以是字母数字字符(A~Z或0~9),也可以是星号(*)。星号告诉系统使用与JOB语句中的MSGCLASS参数相同的类别处理SYSOUT。下面是一个SYSOUT参数的例子。
//DATA1 DD SYSOUT=A
2.4.3.9 数据集串联(Concatenation)
程序员可以将多个不同的数据集串联起来对应到同一个DD名字中。这项功能使你可以将多个物理文件当成一个逻辑文件处理。
在我们前面的银行主文件维护程序中,就可能会用到数据集的串联。我们知道,银行的业务有很多种,比如,大家比较熟悉的就有存款、取款、贷款和信用卡等。假定每种业务类型都有自己的交易文件,这样,我们在晚上更新主文件时,如果不讲这些交易文件串联起来一起处理而是逐个交易文件分别处理,那样对主文件的处理次数就明显增加了。为了提高系统的效率,我们可以将各种交易文件串联在一起,让系统当成一个交易文件来处理,在只扫描一次主文件的情况下,完成主文件的更新。
操作系统按照数据集出现的顺序逐个抽取数据集中的记录,将它们当成一个逻辑文件处理。你可以按照下面的步骤将数据集串联起来。
● 只在第1个数据集的前面编写DD语句。
● 为每一个要串联的数据集增加一个DD语句,但将ddname栏留空。
● 按照串联数据集的处理顺序排列它们。
下面是将银行存款、取款、贷款和信用卡交易文件串联成一个逻辑文件的例子。
000010 //BKTRAN DD DSN=IBMUSER.TEST.BKTRAN.DEPOSIT,DISP=SHR 000011 // DD DSN=IBMUSER.TEST.BKTRAN.WITHDRW,DISP=SHR 000012 // DD DSN=IBMUSER.TEST.BKTRAN.LOANS,DISP=SHR 000013 // DD DSN=IBMUSER.TEST.BKTRAN.CREDIT,DISP=SHR
使用数据集串联,你只需要修改DD语句就可以使得程序处理一个或多个数据集。数据集串联时必须要考虑下面几点。
● 串联在一起的数据集必须有相同的DCB子参数。换句话说,它们的记录格式(RECFM)、逻辑记录长度(LRECL)和块大小(BLKSIZE)必须一样。
● 串联数据集最多可以有255个顺序文件或16个分区数据集。
2.4.3.10 存储管理子系统(SMS)
很多公司已经在使用存储管理子系统(Storage Management Subsystem——SMS)。当安装并使用SMS,你的公司就会有好多数据集由SMS管理。当使用SMS后,你的存储空间管理员就可以决定哪些数据集要由SMS管理。SMS具有下面这些特性。
● SMS可以管理:
✧ 顺序数据集(Physical Sequential——PS)。
✧ 分区数据集(Partitioned Data Sets——PDS)。✧ 扩充的分区数据集(Partitioned Data Sets Extended——PDSE)。
✧ VSAM(Virtual Storage Access Method)数据集。
● SMS不管理磁带或SYSOUT数据集。
● SMS对其管理的数据集提供默认的信息和简化的JCL代码。
一般来说,即使安装和使用SMS系统,现存的JCL仍然可以正确运行。在不用修改现存JCL的情况下,SMS为你提供了数据类(Data Class)、管理类(Management Class)和存储类(Storage Class)结构。
公司定义的自动类别选择(Automatic Class Selection——ACS)例程可以用来为数据集选择合适的数据特性,以减少编写某些不必要的JCL参数。下面的例子建立一个新的有SMS管理的数据集,你可以看到,JCL简化了许多,原来需要的如VOL、UNIT等参数都可以省略了。
//SMSFILE DD DSN=IBMUSER.TEST.SMSFILE, // DISP=(NEW,KEEP)