IBM Mainframe Forum Index
 
Log In
 
IBM Mainframe Forum Index Mainframe: Search IBM Mainframe Forum: FAQ Register
 

Substitution of JCL variables


IBM Mainframe Forums -> CLIST & REXX
Post new topic   Reply to topic
View previous topic :: View next topic  
Author Message
sergeyken

Senior Member


Joined: 29 Apr 2008
Posts: 2264
Location: USA

PostPosted: Thu Aug 07, 2025 9:22 pm
Reply with quote

Very unexpectedly I received a private(??) message from a forum member.

Quote:
Hello there,

We have many JCL which are using symbolics like
Code:

Code:
//DBPTEMP JOB (CS-P-CG-CAD-0),'DBA-DB2-JOB',CLASS=C,
//** RESTART=STEP####, <<< REMOVE * AND POINT TO RESTART STEP
// MSGCLASS=N,MSGLEVEL=(1,1),REGION=0M ,PERFORM=216
//*
//*********************************************************************
//* RESTART INSTRUCTION :
//* DO NOT RESTART IN CASE OF ABENDS, CONTACT DB2 DBA ONCALL
//*
//*********************************************************************
//* THIS JOB BUILDS
//*********************************************************************
//*

//PROCSTMT JCLLIB ORDER=(DBP.SUP.DBA.PROC,
// CCP.BASE.PROCLIB)
//*
// EXPORT SYMLIST=(DB2SS,EPFIX,SI,JBNM)
// SET SHLQ='SYSGA' <<HLQ OF SYSTEM FILES - LPAR DEPENDENT
// SET DB2SS='A' <<SET DB2 SUBSYSTEM
// SET SI='A0P' <<SCHEMA IDENTIFIER
// SET EPFIX='DBQ' <<ENVIRONMENT LEVEL
// SET JBNM='D2PDR00' <<JOBNAME FOR EMAIL STEPS
// SET RNDT='YYYY-MM-DD' << RUN DATE, INVALID DATES USE CURRENT DATE
//* << USE TO TEST A SPECIFIC DATE
// SET LRC=' ' << FORCE LIST OF REORG_CONTROL BPOOL REPORT
//* << DDNAME IS BPCOLLFL IF THE VALUE IS 'R'
// SET TF=' ' << TRACE FLAG - FOR TESTING THE PROGRAM
// SET FC='4' << FUTURE CHECK - TODAY + 4 DAYS
// SET CHKPGM=D6REOCHK << NEW VERSION STORED PROC REBINDS 04/23/24
//* PARM='&RNDT.&SI.&FC.&LRC.&TF.'
//**
//JOBLIB DD DISP=SHR,DSN=&SHLQ..DB2&DB2SS..SDSNEXIT
// DD DISP=SHR,DSN=&SHLQ..DB2&DB2SS..SDSNLOAD
..




Now what we are planning to change jobs to remove symbolics and use actual value... I have started working with REXX but with that single line of jcl is had to read multiple time to change value

I think it cna be done through using symname like passing symname as parm but not able to proceed further...

Kindly advise.

_________________
Cheers
Ekta


After my suggestion to post any request as a regular forum's topic, there was no response received from this author...

Though the final goal of this request looks a little bit senseless, the process itself may be interesting.

I mentioned to the author, that such conversion hardly can be done using SORT features, but quite easily can be implemented in REXX.

I don't want to give the complete solution, especially after the rude and illiterate message, but I can present the hi-level approach here, while hiding the more detailed code from this post. I hope that either the author, or others would be interested, and can suggest their own implementation of currently hidden low-level REXX procedures.

Code:
/* REXX */                                                             
 /*===================================================================*/
 /* Substitute JCL variables                                          */
 /*===================================================================*/
                                                                       
 "EXECIO 0 DISKR SYSIN (OPEN"                                           
 "EXECIO 0 DISKW SYSOUT (OPEN"                                         
                                                                       
 VarList = ''         /* list of found JCL variable names */           
 Do #REC = 0 BY 1                                                       
    "EXECIO 1 DISKR SYSIN"                                             
    If RC > 0 Then Leave #REC                                           
                                                                       
    Parse Pull 1 Record 72 RecEnd 81 .                                 
    Say Record           /* source line to trace and debug */           
                                                                       
    Call TryScanVar      /* verify all possible substitutions &XXX. */ 
                                                                       
    Call CheckSetVar  /* verify possible // SET XXX='1234' statement */
                                                                       
    /* push cnahged/unchanged record back to stack */                   
    Push Substr( Record, 1, 71 ) || RecEnd                             
    "EXECIO 1 DISKW SYSOUT"                                             
 End #REC                                                               
                                                                       
 "EXECIO 0 DISKR SYSIN (FINIS"                                         
 "EXECIO 0 DISKW SYSOUT (FINIS"                                         
                                                                       
 Say #REC "records processed"                                           
 Say "VarList = '" || VarList || "'"                                   
 Call ListFoundVars                                                     
                                                                       
 Exit                                                                   
                                                                       
 /*===================================================================*/
 TryScanVar:   /* try to detect ...&NAME... and to put it's value */     
 . . . . . . . . . . to be continued . . . . . . . . . .
 Return
 /*===================================================================*/
 CheckSetVar: /* try to find "//... SET NAME='VALUE' or NAME=VALUE   */ 
 . . . . . . . . . . to be continued . . . . . . . . . .
 Return
 /*===================================================================*/
 ListFoundVars:                                                         
                                                                         
 Do I = 1 To Words(VarList)                                             
    VarName = Word( VarList, I )                                         
    Say VarName || "='" || VarValue.VarName || "'"                       
 End I                                                                   
                                                                         
 Return                                                                 
 /*===================================================================*/
Back to top
View user's profile Send private message
sergeyken

Senior Member


Joined: 29 Apr 2008
Posts: 2264
Location: USA

PostPosted: Sat Aug 09, 2025 1:15 am
Reply with quote

Since no reaction received, I just post here my solution, slightly improved.
Maybe there is someone to add some comments, or suggestions...

Code:
/* REXX */                                                             
 /*===================================================================*/
 /* Substitute JCL variables                                          */
 /*===================================================================*/
                                                                       
 "EXECIO * DISKR SYSIN (FINIS"   /* read whole input into stack */     
                                                                       
 VarList = ''         /* list of found JCL variable names */           
 TotalLines = Queued()                                                 
 Do TotalLines           /* process stack from top to bottom */         
                                                                       
    Parse Pull 1 Record 72 RecEnd 81 .  /* unused part from pos 72 */   
    Say Record           /* source line to trace and debug */           
                                                                       
    Record = TryScanVar(Record)  /* make all substitutions &XXX. */     
                                                                       
    Call CheckSetVar(Record)  /* verify // SET XXX='1234' statement */ 
                                                                       
    /* queue changed/unchanged record at the stack end */               
    Queue Substr( Record, 1, 71 ) || RecEnd  /* append the tail back */
                                                                       
 End                                                                   
                                                                       
 "EXECIO * DISKW SYSOUT (FINIS"   /* write whole output from stack */   
                                                                       
 Say TotalLines "lines processed"                                       
 Say "VarList = '" || VarList || "'"                                   
 Call ListFoundVars                                                     
                                                                       
 Exit                                                                   
                                                                       
 /*===================================================================*/
 TryScanVar: Procedure Expose VarList VarValue.                         
    /* try to detect ...&NAME... and to put it's value */               
 Parse Arg LineToScan      /* keep upper/lower case as is */           
                                                                       
    If Words(VarList) = 0  /* no known variables? */                   
    Then Return LineToScan /* nothing to change yet */                 
                                                                       
    NewLine = ""           /* prepare to collect updates */             
    Do #Var = 0 By 1                                                   
       /* split "...AAA&XXX..." into "...AAA" and "XXX..." */           
       Parse Value LineToScan ,                                         
             With 1 LeftPart '&' LineToScan                             
       NewLine = NewLine || LeftPart /* left part is unchanged */       
       If LineToScan = " " Then Leave #Var  /* no more &NAME */         
                                                                       
       KnownNames = Words(VarList)                                     
       Do I = 1 To KnownNames    /* check known vars, one by one */               
          OldName = Word( VarList, I )                               
          LenName = Length( OldName )                               
          NameCandidate = Left( LineToScan, LenName )               
          If NameCandidate == OldName     /* with matching case */   
          Then Do /* try to check matching variable name */         
                                                                     
             NextChar = Substr( LineToScan, LenName + 1, 1 )         
             /* check if this variable name is longer               
                than the one to verify?                             
                Like: ...&X123... instead of ...&X1... ? */         
             If 0 < Pos( NextChar,                    ,             
                        "ABCDEFGHIJKLMNOPQRSTUVWXYZ_$#@0123456789" )
             Then Iterate I     /* ignore longer variable name */   
                                                                     
             /* if variable found, substitute its value */           
             LineToScan = Substr( LineToScan, LenName + 1)           
             If Left( LineToScan, 1) = '.'   /* "&NAME." - ? */     
             Then LineToScan = Substr( LineToScan, 2)               
                                                                     
             Say "Found" OldName || "='" || VarValue.OldName || "'" 
             NewLine = NewLine || VarValue.OldName                   
             Leave I     /* no more NAME verification needed here */
          End                                                       
       End I                                                         
       If I > KnownNames     /* if the variable has not been set, */
       Then NewLine = NewLine || '&' /* then leave it as is */         
    End #Var                                                           
                                                                       
 Return NewLine                                                         
 /*===================================================================*/
 CheckSetVar: Procedure Expose VarList VarValue.                       
    /* try to find "//... SET NAME='VALUE' or NAME=VALUE"  */           
 Parse Arg LineToParse /* keep upper/lower case as is */               
                                                                       
    Parse Value LineToParse ,                                           
          With 1 Prefix OpCode JCLParams                               
    If  Left( Prefix, 2)  = "//"  ,                                     
      & Left( Prefix, 3) /= "//*" ,                                     
      & OpCode == "SET"              /* "set" is not "SET" - JCL rule */
    Then Do     /* process JCL SET operator */                         
         Parse Value JCLParams With NewName '=' NewParam               
         If NewName /= Translate(NewName)                               
         Then Return       /* ignore not uppercase names */             
                                                                       
         If WordPos( NewName, VarList ) = 0                             
         Then VarList = VarList NewName  /* keep non-duplicated name */
                                                                       
         If Left( NewParam, 1) = "'"                                   
         Then Parse Value NewParam With "'" RealValue "'" .             
         Else Parse Value NewParam With     RealValue     .             
                                                                         
         Say "Set" NewName || "=" || RealValue                           
         VarValue.NewName = RealValue   /* save or replace the value */ 
    End                                                                 
                                                                         
 Return                                                                 
 /*===================================================================*/
 ListFoundVars:                                                         
                                                                         
 Do I = 1 To Words(VarList)                                             
    VarName = Word( VarList, I )                                         
    Say VarName || "='" || VarValue.VarName || "'"                       
 End I                                                                   
                                                                         
 Return                                                                 
 /*===================================================================*/
Back to top
View user's profile Send private message
Willy Jensen

Active Member


Joined: 01 Sep 2015
Posts: 772
Location: Denmark

PostPosted: Mon Aug 11, 2025 1:49 am
Reply with quote

"Since no reaction received," Ok, I'll bite. Here is my suggestion.
It may not cross all the 'T's, but works for the sample given.
Data is read using my own program, RXExecIo, just substitute with whatever input means that you fancy. The generated records are just show, again replace with the write feature that you like.
The program stores the SET values in a stem, which is reset when a JOB statement is encountered, thus enabling change of a series of jobs unloaded to a single dataset.
Code:
/*----------------------------------------------------------------rexx         
  JCL variable substitution                                                   
  Temp dsns (&&name) are ignored.                                             
  &VAR where value isn't set, is marked.                                       
 --------------------------------------------------------------------*/       
 set.=''                                                                       
 "delstack"                                                                   
 Call RXExecIo 'get dsn(AA.TEST.JCL.DS) stem(jcl.)' /* my READ pgm           */
 say 'JCL records read:' 0+jcl.0                                               
                                                                               
 do j#=1 to jcl.0                                /* over JCL input           */
   if left(jcl.j#,2)='//' & substr(jcl.j#,3,1)<>'*' then do                   
     if word(jcl.j#,2)='JOB' then do                                           
       say 'New job' substr(word(jcl.j#,1),3)                                 
       set.=''                                                                 
       queue jcl.j#                                                           
     end                                                                       
     else if word(jcl.j#,2)='SET' then Call GetSet strip(left(jcl.j#,72))     
     else if pos('&',jcl.j#)>0    then Call Subst  strip(left(jcl.j#,72))     
     else queue jcl.j#                                                         
   end                                                                         
   else queue jcl.j#                                                           
 end                                                                           
                                                                               
 say ''                                                                       
 say 'Generated...'                                                           
 do queued()                                                                   
   parse pull r                                                               
   say r                                                                       
 end                                                                           
 "delstack"                                      /* not really neccessary    */
 Exit 0                                                                       
                                                                               
GetSet:   /* locate var defs, save values                                    */
 parse value arg(1) with r . 'SET' n . '=' v                                   
 if left(v,1)="'" then parse var v =2 v "'" .                                 
 else parse var v v .                                                         
 set.n=v                                                                       
 queue insert('*',arg(1),2)                      /* make SET a coment        */
 return 0
                                                                               
Subst:    /* locate variables in JCL, replace with stored value              */
 ir=arg(1)' '                                                                 
 say 'subst in: ' arg(1)                                                       
 or=''                                                                         
 do forever                                                                   
   p=pos('&',ir)                                                               
   if p=0 then leave                                                           
   if substr(ir,p+1,1)='&' then do               /* temp dsn                 */
     or=or'&&'                                                                 
     ir=substr(ir,3)                                                           
     iterate                                                                   
   end                                                                         
   parse var ir f '&' ir                         /* get front, name and back */
   or=or || f                                                                 
   p=Verify(ir,'.,() ','m')                      /* pos at first of list     */
   parse var ir v =(p) ir                                                     
   if left(ir,2)='..' then ir=substr(ir,2)                                     
   if set.v<>'' then v=set.v                                                   
   else do                                                                     
     say '*' v 'is not SET'                                                   
     v='?'v'?'                                                                 
   end                                                                         
   or=or || v                                                                 
 end                                                                           
 or=or || ir                                      /* add whats left          */
 say 'subst out:' or                                                           
 return qjcl(or)                                                               
                                                                               
QJcl:     /* queue JCL, possible split into multiple records                 */
 parse arg r                                                                   
 if length(r)>70 then do                                                       
   p=lastpos(',',r,70)                                                         
   parse var r f =(p) +1 r                                                     
   queue f','                                                                 
   queue '// 'r                                                               
 end                                                                           
 else queue r                                                                 
 return 0   
Back to top
View user's profile Send private message
sergeyken

Senior Member


Joined: 29 Apr 2008
Posts: 2264
Location: USA

PostPosted: Mon Aug 11, 2025 3:54 am
Reply with quote

Willy Jensen wrote:
"Since no reaction received," Ok, I'll bite. Here is my suggestion.
It may not cross all the 'T's, but works for the sample given.
Data is read using my own program, RXExecIo, just substitute with whatever input means that you fancy. The generated records are just show, again replace with the write feature that you like.

One thing is critical: the call to Subst() must go before call to GetSet(), because some previously defined variables may be used in the SET statement itself.
Back to top
View user's profile Send private message
Willy Jensen

Active Member


Joined: 01 Sep 2015
Posts: 772
Location: Denmark

PostPosted: Mon Aug 11, 2025 12:02 pm
Reply with quote

Good point.
Back to top
View user's profile Send private message
Willy Jensen

Active Member


Joined: 01 Sep 2015
Posts: 772
Location: Denmark

PostPosted: Mon Aug 11, 2025 12:34 pm
Reply with quote

Slightly modified pgm:
Code:
 set.=''                                                                       
 "delstack"                                                                   
 Call RXExecIo 'get dsn(TEST.SEQ.F080) stem(jcl.)' /* my READ pgm     */   
 say 'JCL records read:' 0+jcl.0                                               
                                                                               
 do j#=1 to jcl.0                                /* over JCL input           */
   jclrec=jcl.j#                                                               
   if left(jclrec,2)='//' & substr(jclrec,3,1)<>'*' then do                   
     if word(jclrec,2)='JOB' then do                                           
       say 'New job' substr(word(jclrec,1),3)                                 
       set.=''                                                                 
     end                                                                       
     if pos('&',jclrec)>0    then jclrec=Subst(strip(left(jclrec,72)))         
     if word(jclrec,2)='SET' then jclrec=GetSet(strip(left(jclrec,72)))       
     if length(jclrec)>70 then do                /* long line                */
       p=lastpos(',',jclrec,70)                                               
       parse var jclrec f =(p) +1 jclrec                                       
       queue f','                                                             
       queue '// 'jclrec                                                       
     end                                                                       
     else queue jclrec                                                         
   end                                                                         
   else queue jclrec                                                           
 end                                                                           
                                                                               
 say ''                                                                       
 say 'Generated...'                                                           
 do queued()                                                                   
   parse pull r                                                               
   say r                                                                       
 end                                                                           
 "delstack"                                      /* not really neccessary    */
 Exit 0                                                                       
                                                                               
GetSet:   /* locate var defs, save values                                    */
 parse value arg(1) with r . 'SET' n . '=' v                                   
 if left(v,1)="'" then parse var v =2 v "'" .                                 
 else parse var v v .                                                         
 set.n=v                                                                       
 return insert('*',arg(1),2)                     /* make SET a coment        */
                                                                               
Subst:    /* locate variables in JCL, replace with stored value              */
 ir=arg(1)' '                                                                 
 say 'subst in: ' arg(1)                                                       
 or=''                                                                         
 do forever                                                                   
   p=pos('&',ir)                                                               
   if p=0 then leave                                                           
   if substr(ir,p+1,1)='&' then do               /* temp dsn                 */
     or=or'&&'                                                                 
     ir=substr(ir,3)                                                           
     iterate                                                                   
   end                                                                         
   parse var ir f '&' ir                         /* get front, name and back */
   or=or || f                                                                 
   p=Verify(ir,'.,() ','m')                      /* pos at first of list     */
   parse var ir v =(p) ir                                                     
   if left(ir,2)='..' then ir=substr(ir,2)                                     
   if set.v<>'' then v=set.v                                                   
   else do                                                                     
     say '*' v 'is not SET'                                                   
     v='?'v'?'                                                                 
   end                                                                         
   or=or || v                                                                 
 end                                                                           
 or=or || ir                                      /* add whats left          */
 say 'subst out:' or                                                           
 return or                                                                     
Back to top
View user's profile Send private message
sergeyken

Senior Member


Joined: 29 Apr 2008
Posts: 2264
Location: USA

PostPosted: Mon Aug 11, 2025 9:21 pm
Reply with quote

Generated:
Code:
//DBPTEMP JOB (CS-P-CG-CAD-0),'DBA-DB2-JOB',CLASS=C,                   
//                                                                     
//** RESTART=STEP####, <<< REMOVE * AND POINT TO RESTART STEP         
// MSGCLASS=N,MSGLEVEL=(1,1),REGION=0M ,                               
// PERFORM=216                                                         
//*           

Must be:
Code:
//DBPTEMP JOB (CS-P-CG-CAD-0),'DBA-DB2-JOB',CLASS=C,                     
//** RESTART=STEP####, <<< REMOVE * AND POINT TO RESTART STEP           
// MSGCLASS=N,MSGLEVEL=(1,1),REGION=0M ,PERFORM=216                     
//*

Generated:
Code:
//PROCSTMT JCLLIB ORDER=(DBP.SUP.DBA.PROC,                             
//                                                                     
// CCP.BASE.PROCLIB)                                                   
// / CCP.BASE.PROCLIB)                                                 

Must be:
Code:
//PROCSTMT JCLLIB ORDER=(DBP.SUP.DBA.PROC,                               
// CCP.BASE.PROCLIB)                                                     

Generated:
Code:
//* SET PREFC='4'   
//* SET FC='?PREFC'? << FUTURE CHECK - TODAY + 4 DAYS

Must be:
Code:
// SET PREFC='4'
// SET FC='4' << FUTURE CHECK - TODAY + 4 DAYS
Back to top
View user's profile Send private message
Willy Jensen

Active Member


Joined: 01 Sep 2015
Posts: 772
Location: Denmark

PostPosted: Tue Aug 12, 2025 1:24 am
Reply with quote

I admit to be confused. When I run the originators JCL through my latest code I get:
Code:
Generated...                                                             
//DBPTEMP JOB (CS-P-CG-CAD-0),'DBA-DB2-JOB',CLASS=C,                     
//** RESTART=STEP####, <<< REMOVE * AND POINT TO RESTART STEP           
// MSGCLASS=N,MSGLEVEL=(1,1),REGION=0M ,PERFORM=216                     
//*                                                                     
//********************************************************************* 
//* RESTART INSTRUCTION :                                               
//* DO NOT RESTART IN CASE OF ABENDS, CONTACT DB2 DBA ONCALL             
//*                                                                     
//********************************************************************* 
//* THIS JOB BUILDS                                                     
//********************************************************************* 
//*                                                                     
                                                                         
//PROCSTMT JCLLIB ORDER=(DBP.SUP.DBA.PROC,                               
// CCP.BASE.PROCLIB)                                                     
//*                                                                     
// EXPORT SYMLIST=(DB2SS,EPFIX,SI,JBNM)                                 
//* SET SHLQ='SYSGA' <<HLQ OF SYSTEM FILES - LPAR DEPENDENT             
//* SET DB2SS='A' <<SET DB2 SUBSYSTEM                                   
//* SET SI='A0P' <<SCHEMA IDENTIFIER                                     
//* SET EPFIX='DBQ' <<ENVIRONMENT LEVEL                                 
//* SET JBNM='D2PDR00' <<JOBNAME FOR EMAIL STEPS                         
//* SET RNDT='YYYY-MM-DD' << RUN DATE, INVALID DATES USE CURRENT DATE   
//* << USE TO TEST A SPECIFIC DATE                                       
//* SET LRC=' ' << FORCE LIST OF REORG_CONTROL BPOOL REPORT             
//* << DDNAME IS BPCOLLFL IF THE VALUE IS 'R'                           
//* SET TF=' ' << TRACE FLAG - FOR TESTING THE PROGRAM                   
//* SET FC='4' << FUTURE CHECK - TODAY + 4 DAYS                         
//* SET CHKPGM=D6REOCHK << NEW VERSION STORED PROC REBINDS 04/23/24     
//* PARM='&RNDT.&SI.&FC.&LRC.&TF.'                                       
//**                                                                     
//JOBLIB DD DISP=SHR,DSN=SYSGA.DB2A.SDSNEXIT                             
// DD DISP=SHR,DSN=SYSGA.DB2A.SDSNLOAD   

Which looks ok to me. The blank line is also in the original. Then you mention SET PREFC='4' , which I cannot find anywhere in that original JCL sample. So what am I missing (possible apart from new glasses)?
You wish to retain the SET statements, no poblem, but I would have thought it better to dummy them out as that should result in JCL errors for any mistakingly left uses of the variables. Each to his/her own.
Back to top
View user's profile Send private message
Willy Jensen

Active Member


Joined: 01 Sep 2015
Posts: 772
Location: Denmark

PostPosted: Tue Aug 12, 2025 2:57 am
Reply with quote

Just noticed the EXPORT SYMLIST statement, which indicates that at least some SET values may be used in SYSIN data, so yes the SET statements should be left unmodified. And the statement
Code:
v='?'v'?'

should instead be
Code:
v='&'v'.'
Back to top
View user's profile Send private message
sergeyken

Senior Member


Joined: 29 Apr 2008
Posts: 2264
Location: USA

PostPosted: Tue Aug 12, 2025 9:45 pm
Reply with quote

OK, no problem.

I started this topic just for fun.
Back to top
View user's profile Send private message
Willy Jensen

Active Member


Joined: 01 Sep 2015
Posts: 772
Location: Denmark

PostPosted: Tue Aug 12, 2025 11:12 pm
Reply with quote

icon_wink.gif
Back to top
View user's profile Send private message
View previous topic : : View next topic  
Post new topic   Reply to topic All times are GMT + 6 Hours
Forum Index -> CLIST & REXX

 


Similar Topics
Topic Forum Replies
No new posts Repeat n times by substuting SEQNUM i... DFSORT/ICETOOL 2
No new posts Preserve changes to ISPF Panel Variables TSO/ISPF 5
No new posts disable /*XEQ substitution in TWS IBM Tools 4
No new posts JCL with variables JCL & VSAM 1
No new posts JCL Variables JCL & VSAM 1
Search our Forums:


Back to Top