Leveraging REXX with JCL for powerful automation, dynamic job generation, and advanced mainframe processing
REXX (Restructured Extended Executor) is a versatile scripting and programming language designed for ease of use and readability. When combined with JCL, REXX creates a powerful toolkit for automating complex mainframe tasks that would be difficult or impossible with JCL alone.
REXX serves as a perfect complement to JCL, filling in the programming capabilities that JCL lacks while allowing JCL to handle the areas where it excels, such as job control and resource allocation.
One of the most common ways to integrate REXX with JCL is to use REXX as a preprocessing step before the main job execution. This allows for dynamic setup, validation, or preparation of resources needed by subsequent steps.
Preprocessing Task | REXX Implementation | Benefit |
---|---|---|
Date-based processing | Determine processing dates, fiscal periods, or reporting windows | Eliminates need for manual date calculations or parameter updates |
Environment validation | Check if required resources exist and are accessible | Prevents job failures due to missing prerequisites |
Data preparation | Reformat, filter, or transform input data | Simplifies main processing by standardizing inputs |
Parameter validation | Verify and normalize job parameters | Catches errors before resource-intensive steps begin |
Dynamic resource allocation | Calculate space requirements or determine file attributes | Optimizes resource usage based on actual requirements |
1234567891011121314151617181920//PREJOB JOB (ACCT),'REXX PREPROCESS',CLASS=A,MSGCLASS=X //******************************************************************** //* THIS JOB USES REXX TO DETERMINE PROCESSING PARAMETERS * //* BEFORE EXECUTING THE MAIN PROCESSING STEP * //******************************************************************** //* //REXXPREP EXEC PGM=IKJEFT01,DYNAMNBR=20 //SYSTSPRT DD SYSOUT=* //SYSTSIN DD * %DATECALC /* //SYSEXEC DD DISP=SHR,DSN=REXX.EXEC.LIBRARY //OUTPARMS DD DSN=&&PARAMS,DISP=(NEW,PASS), // SPACE=(TRK,(1,1)),DCB=(RECFM=FB,LRECL=80,BLKSIZE=0) //* //MAINPROC EXEC PGM=BILLING //INPARMS DD DSN=&&PARAMS,DISP=(OLD,DELETE) //INDATA DD DISP=SHR,DSN=CUSTOMER.BILLING.DATA //OUTFILE DD SYSOUT=* //SYSPRINT DD SYSOUT=*
In this example, the REXXPREP step executes the DATECALC REXX procedure, which might calculate date ranges for billing periods and write them to a temporary dataset (&&PARAMS). The MAINPROC step then uses these parameters for processing.
One of the most powerful applications of REXX is generating JCL dynamically. This approach allows for jobs to be tailored based on conditions, data, or system state, enabling a level of flexibility not possible with static JCL.
The process typically follows these steps:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778/* DYNJCL - DYNAMICALLY GENERATE JCL BASED ON CONDITIONS */ /* --------------------------------------------------- */ /* Get current date for processing */ current_date = DATE('S') /* Format: YYYYMMDD */ current_year = SUBSTR(current_date, 1, 4) current_month = SUBSTR(current_date, 5, 2) current_day = SUBSTR(current_date, 7, 2) /* Determine processing type based on day of month */ IF current_day <= '05' THEN process_type = 'MONTHLY' ELSE IF current_day <= '15' THEN process_type = 'BIWEEKLY' ELSE process_type = 'DAILY' /* Begin constructing JCL */ jcl. = '' /* Initialize JCL stem variable */ line = 0 /* Line counter */ /* Build JOB statement */ line = line + 1 jcl.line = '//DYNAJOB JOB (ACCT),''DYNAMIC JOB'',CLASS=A,MSGCLASS=X' /* Add job documentation */ line = line + 1 jcl.line = '//*' line = line + 1 jcl.line = '//* DYNAMICALLY GENERATED JOB FOR ' || process_type || ' PROCESSING' line = line + 1 jcl.line = '//* GENERATED ON: ' || DATE() || ' AT: ' || TIME() line = line + 1 jcl.line = '//*' /* Add different steps based on process type */ SELECT WHEN process_type = 'MONTHLY' THEN DO line = line + 1 jcl.line = '//STEP01 EXEC PGM=MONTHLY,REGION=0M' line = line + 1 jcl.line = '//INFILE DD DISP=SHR,DSN=PROD.MONTHLY.DATA' END WHEN process_type = 'BIWEEKLY' THEN DO line = line + 1 jcl.line = '//STEP01 EXEC PGM=BIWEEK,REGION=0M' line = line + 1 jcl.line = '//INFILE DD DISP=SHR,DSN=PROD.BIWEEKLY.DATA' END OTHERWISE DO line = line + 1 jcl.line = '//STEP01 EXEC PGM=DAILY,REGION=0M' line = line + 1 jcl.line = '//INFILE DD DISP=SHR,DSN=PROD.DAILY.DATA' END END /* Complete the JCL with common statements */ line = line + 1 jcl.line = '//OUTFILE DD DISP=(NEW,CATLG,DELETE),' line = line + 1 jcl.line = '// DSN=PROD.OUTPUT.' || current_date || ',' line = line + 1 jcl.line = '// SPACE=(CYL,(50,20),RLSE),' line = line + 1 jcl.line = '// DCB=(RECFM=FB,LRECL=80,BLKSIZE=0)' line = line + 1 jcl.line = '//SYSPRINT DD SYSOUT=*' /* Write the JCL to a dataset */ "ALLOC F(JCLOUT) DA('USER.JCLLIB(DYNAMIC)') SHR REUSE" "EXECIO * DISKW JCLOUT (STEM jcl. FINIS" "FREE F(JCLOUT)" /* Submit the JCL */ "SUBMIT 'USER.JCLLIB(DYNAMIC)'" EXIT
This REXX example determines the type of processing needed based on the current date, then generates appropriate JCL with different programs and datasets. It writes the JCL to a PDS member and submits it for execution.
Best Practice: When generating JCL dynamically, always include identifying comments that show when the JCL was generated and by which process. This is invaluable for troubleshooting.
Effective communication between JCL and REXX is essential for integration. There are several methods for passing parameters and data between JCL and REXX procedures.
The most common method is using the PARM parameter on the EXEC statement when invoking TSO/REXX:
12//STEP1 EXEC PGM=IKJEFT01, // PARM='MYRXPGM arg1 arg2 "argument with spaces"'
In your REXX procedure, you can access these parameters using:
123456/* MYRXPGM - Sample REXX procedure */ PARSE ARG arg1 arg2 arg3 SAY 'First argument:' arg1 SAY 'Second argument:' arg2 SAY 'Third argument:' arg3
For more complex or larger data, using datasets is preferable:
1234567891011//STEP1 EXEC PGM=IKJEFT01,PARM='READPARM' //SYSTSPRT DD SYSOUT=* //SYSTSIN DD DUMMY //SYSEXEC DD DISP=SHR,DSN=REXX.EXEC.LIB //INPARMS DD * CUSTID=12345 REGION=EAST DATE=20230315 PROCESS=MONTHLY DEBUG=YES /*
The corresponding REXX procedure to read these parameters:
12345678910111213141516171819202122232425262728293031323334353637383940/* READPARM - Read parameters from a dataset */ /* Initialize parameter variables with defaults */ custid = '' region = '' date = '' process = 'DAILY' /* Default value */ debug = 'NO' /* Default value */ /* Read parameter file */ "EXECIO * DISKR INPARMS (STEM parm. FINIS" /* Process each parameter line */ DO i = 1 TO parm.0 /* Parse name=value pairs */ PARSE VAR parm.i name '=' value /* Remove any leading/trailing spaces */ name = STRIP(name) value = STRIP(value) /* Assign to appropriate variable */ SELECT WHEN TRANSLATE(name) = 'CUSTID' THEN custid = value WHEN TRANSLATE(name) = 'REGION' THEN region = value WHEN TRANSLATE(name) = 'DATE' THEN date = value WHEN TRANSLATE(name) = 'PROCESS' THEN process = value WHEN TRANSLATE(name) = 'DEBUG' THEN debug = value OTHERWISE NOP /* Ignore unrecognized parameters */ END END /* Display the parameter values */ SAY 'Parameters loaded:' SAY ' Customer ID:' custid SAY ' Region:' region SAY ' Date:' date SAY ' Process Type:' process SAY ' Debug Mode:' debug EXIT
REXX can pass information back to JCL through:
While JCL symbolic parameters cannot be directly modified by REXX at runtime, using intermediate datasets with SET statements is a common pattern for achieving similar functionality.
Debugging REXX procedures running within JCL jobs presents unique challenges compared to interactive debugging in TSO/ISPF. However, several techniques can make the process more manageable.
Technique | Implementation | Advantages / Disadvantages |
---|---|---|
TRACE options | Add TRACE R or TRACE I to your REXX code | + Detailed execution tracing - Can produce overwhelming output |
SAY statements | Add strategic SAY commands at key points | + Simple implementation - Requires code modification |
Separate log files | Use EXECIO to write debug info to a separate dataset | + Isolated debugging output - Requires additional setup |
Conditional debugging | Use parameter to enable/disable debug mode | + Flexible control - Requires preparation |
Testing in TSO first | Test REXX interactively before running in batch | + Interactive feedback - May miss batch-specific issues |
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374/* PROCDATA - Sample REXX with debugging features */ /* Get runtime parameters */ PARSE ARG dataset debug /* Initialize debugging based on parameter */ debug_mode = (TRANSLATE(debug) = 'DEBUG') /* Set up debug log if in debug mode */ IF debug_mode THEN DO "ALLOC F(DEBUGLOG) DA('USER.DEBUG.LOG') MOD REUSE" CALL log_debug "PROCDATA starting at" TIME() "on" DATE() CALL log_debug "Processing dataset:" dataset END /* Main processing */ CALL log_debug "Reading input dataset" "EXECIO * DISKR INDATA (STEM data. FINIS" CALL log_debug "Read" data.0 "records from input" total_value = 0 valid_records = 0 error_records = 0 /* Process each record */ DO i = 1 TO data.0 CALL log_debug "Processing record" i":" CALL log_debug " Record data:" data.i /* Simple validation - record should have 3 comma-separated fields */ PARSE VAR data.i field1 ',' field2 ',' field3 IF field3 = '' THEN DO CALL log_debug " ERROR: Invalid record format" error_records = error_records + 1 ITERATE END /* Convert third field to numeric value */ IF DATATYPE(field3, 'N') THEN DO total_value = total_value + field3 valid_records = valid_records + 1 CALL log_debug " Valid record, value:" field3 END ELSE DO CALL log_debug " ERROR: Non-numeric value:" field3 error_records = error_records + 1 END END /* Output results */ SAY "Processing complete" SAY "Valid records processed:" valid_records SAY "Error records found:" error_records SAY "Total value:" total_value /* Close debugging */ IF debug_mode THEN DO CALL log_debug "PROCDATA completed at" TIME() CALL log_debug "Valid:" valid_records "Errors:" error_records "Total:" total_value "EXECIO 0 DISKW DEBUGLOG (FINIS" "FREE F(DEBUGLOG)" END EXIT /* Debug logging subroutine */ log_debug: IF debug_mode THEN DO PARSE ARG msg QUEUE msg "EXECIO 1 DISKW DEBUGLOG" SAY "DEBUG:" msg END RETURN
In this example, the REXX procedure includes a debugging mode that can be enabled with a parameter. When debug mode is active, detailed logging is written to both the job output and a separate debug log file for later analysis.
Best Practice: Design your REXX procedures with debugging in mind from the beginning. Including a debug parameter and logging infrastructure makes troubleshooting much easier when issues arise in production.
Test your understanding of JCL and REXX integration with this quick quiz.
1. What is the primary benefit of using REXX with JCL?
2. How can REXX access JCL parameters passed to it?
3. Which statement correctly executes a REXX procedure from JCL?
4. Which technique allows REXX to create JCL dynamically?
5. What is a common challenge when debugging REXX in a JCL environment?
Create a JCL job that executes a REXX procedure to:
Create a REXX procedure that reads parameters from an input dataset in JCL:
Write a REXX procedure that generates JCL dynamically: