This document provides instructions for using a REXX program called cobdfsym to generate DFSORT symbol definitions from a COBOL copybook. It includes an example of running cobdfsym on a sample COBOL copybook to produce DFSORT symbols that can be used in DFSORT or ICETOOL jobs.
This document provides instructions for using a REXX program called cobdfsym to generate DFSORT symbol definitions from a COBOL copybook. It includes an example of running cobdfsym on a sample COBOL copybook to produce DFSORT symbols that can be used in DFSORT or ICETOOL jobs.
This document provides instructions for using a REXX program called cobdfsym to generate DFSORT symbol definitions from a COBOL copybook. It includes an example of running cobdfsym on a sample COBOL copybook to produce DFSORT symbols that can be used in DFSORT or ICETOOL jobs.
This document provides instructions for using a REXX program called cobdfsym to generate DFSORT symbol definitions from a COBOL copybook. It includes an example of running cobdfsym on a sample COBOL copybook to produce DFSORT symbols that can be used in DFSORT or ICETOOL jobs.
The cobdfsym REXX program shown below allows you to create DFSORT Symbols from a COBOL copybook. Here's how: Wrap the COBOL copybook in a dummy program and compile the program to get a compiled listing. Use the cobdfsym REXX program to process the compiled listing and create a DFSORT symbols data set. Use that DFSORT symbols data set in a SYMNAMES DD statement for your DFSORT or ICETOOL jobs. When you upload the cobdfsym program, watch out for the following: Make sure the cobdfsym statements were not converted to uppercase. The cobdfsym statements must be mixed case as shown below. Make sure the REXX operators (for example, || for concatenation) were not translated to other characters. REXX must be able to recognize the operators. cobdfsym translates COBOL data types to DFSORT formats according to the table shown here. The actual translations performed can be summarized as follows: 'Group' to CH 'Grp-VarLen' to CH 'Display' to CH 'Disp-Num' without 'LEADING' and without 'SEPARATE' to ZD 'Disp-Num' with 'LEADING' and without 'SEPARATE' to CLO 'Disp-Num' with 'SEPARATE' and without 'LEADING' to CST 'Disp-Num' with 'LEADING' and with 'SEPARATE' to FS 'Packed-Dec' to PD 'Binary' with S9 to FI 'Binary' without S9 to BI 'Comp-1' to FL 'Comp-2' to FL Anything else to CH cobdfsym converts COBOL 88 values to DFSORT symbols as follows: 'literal' to 'literal' (for example, 'APPROVED'). When a COBOL statement sets more than one literal, cobdfsym generates a DFSORT Symbols statement that sets the symbol to the first literal. decimal number to decimal number (for example, 14). When a COBOL statement sets more than one decimal number, cobdfsym generates a DFSORT Symbols statement that sets the symbol to the first decimal number. SPACES to ' ' ( blank). ZERO to 0 (decimal zero). LOW-VALUE to X'00' (binary zero). If any of the translated symbols do not meet your needs, you can fine-tune them by editing the DFSORT Symbols data set produced by cobdfsym. Here's an example of a job to do the COBOL compile and REXX processing. //SYSLIB must point to the library that contains your COBOL copybook. //SYSPROC must point to the library in which you've Page 1 of 22 5/2/2006 file://C:\DOCUME~1\gelewyc\LOCALS~1\Temp\DZKO70W6.htm stored cobdfsym. //SYMNAMES must point to the output data set or member for the DFSORT Symbols data set to be produced by cobdfsym; this data set must have, or will be given, RECFM=FB and LRECL=80. Be sure to specify MAP in the PARM field. //***************************************************************** //* GENERATE SYMBOLIC NAMES FOR DFSORT USING COBOL COPYBOOK //***************************************************************** //COBCOMP EXEC PGM=IGYCRCTL, // PARM=('APOST,RENT,NOSEQ,MAP', // 'BUF(20K),OPT(STD),TERM,LIB') //SYSLIB DD DSN=CLIB,DISP=SHR -- library with COBOL copybook //SYSPRINT DD DSN=&&COBLIST,DISP=(,PASS),UNIT=SYSDA, // SPACE=(TRK,(10,10)) //SYSTERM DD SYSOUT=* //SYSIN DD * IDENTIFICATION DIVISION. PROGRAM-ID. DUMMYPGM. ENVIRONMENT DIVISION. DATA DIVISION. WORKING-STORAGE SECTION. COPY DCLSYM01. -- COBOL COPY statement PROCEDURE DIVISION. GOBACK. //SYSLIN DD DUMMY //SYSUT1 DD SPACE=(CYL,(10),,,ROUND),UNIT=SYSDA //SYSUT2 DD SPACE=(CYL,(10),,,ROUND),UNIT=SYSDA //SYSUT3 DD SPACE=(CYL,(10),,,ROUND),UNIT=SYSDA //SYSUT4 DD SPACE=(CYL,(10),,,ROUND),UNIT=SYSDA //SYSUT5 DD SPACE=(CYL,(10),,,ROUND),UNIT=SYSDA //SYSUT6 DD SPACE=(CYL,(10),,,ROUND),UNIT=SYSDA //SYSUT7 DD SPACE=(CYL,(10),,,ROUND),UNIT=SYSDA //**************************************************************** //* INTERPRET COBOL LISTING AND CREATE DFSORT SYMNAMES DATA SET //**************************************************************** //SYMNAMES EXEC PGM=IKJEFT1A, // COND=(04,LT), // PARM='%COBDFSYM' //SYSPROC DD DSN=RLIB,DISP=SHR -- library with cobdfsym REXX program //SYSTSPRT DD SYSOUT=* //SYSTSIN DD DUMMY //COBLIST DD DSN=&&COBLIST,DISP=(OLD,PASS) //SYMNAMES DD DSN=DFSORT.SYMBOLS(DCLSYM01),DISP=SHR -- DFSORT symbols As an example, if the DCLSYM01 copybook looked like this: 01 PACKAGE-RECORD. 05 PACKAGE-HEADER. 10 PACKAGE-HEADER-1 PIC X(13). 10 FILLER PIC X. 10 PACKAGE-HEADER-2 PIC X(01). 10 FILLER PIC X. 10 PACKAGE-SEQUENCE PIC 9(08) COMP-3. 05 CUSTOMER-GROUP. 10 CG-NAME PIC X(30). Page 2 of 22 5/2/2006 file://C:\DOCUME~1\gelewyc\LOCALS~1\Temp\DZKO70W6.htm 10 CG-COUNT PIC 9(10) COMP-3. 10 CG-DATE PIC 9(06) COMP. 10 CG-TIME PIC 9(08) COMP. 10 CG-TYPE PIC S9(02) COMP. 10 CG-LIMIT PIC S9(07). 10 CG-STATUS PIC X(08). 88 APPROVED VALUE 'APPROVED'. 88 DENIED VALUE 'DENIED '. 88 PENDING VALUE SPACES. 10 CG-COUNTY-NO PIC 99. 88 DUTCHESS VALUE 14. 88 KINGS VALUE 24. 88 NOCOUNTY VALUE ZERO. the DFSORT Symbols created in DFSORT.SYMBOLS(DCLSYM01) would look like this: PACKAGE-RECORD,1,84,CH PACKAGE-HEADER,1,21,CH PACKAGE-HEADER-1,1,13,CH PACKAGE-HEADER-2,15,1,CH PACKAGE-SEQUENCE,17,5,PD CUSTOMER-GROUP,22,63,CH CG-NAME,22,30,CH CG-COUNT,52,6,PD CG-DATE,58,4,BI CG-TIME,62,4,BI CG-TYPE,66,2,FI CG-LIMIT,68,7,ZD CG-STATUS,75,8,CH APPROVED,'APPROVED' DENIED,'DENIED ' PENDING,' ' CG-COUNTY-NO,83,2,ZD DUTCHESS,14 KINGS,24 NOCOUNTY,0 You could use these symbols in a DFSORT job by specifying: //S1 EXEC PGM=ICEMAN //SYMNAMES DD DSN=DFSORT.SYMBOLS(DCLSYM01),DISP=SHR ... You could use these symbols in an ICETOOL job by specifying: //S2 EXEC PGM=ICETOOL //SYMNAMES DD DSN=DFSORT.SYMBOLS(DCLSYM01),DISP=SHR ... Here's the cobdfsym REXX program: Page 3 of 22 5/2/2006 file://C:\DOCUME~1\gelewyc\LOCALS~1\Temp\DZKO70W6.htm /*REXX - COBDFSYM : Create DFSORT symbols from COBOL listing *** Freeware courtesy of SEB IT Partner and IBM *** trace r */ call read_coblist call fix_duplicates call put_symnames exit Put_symnames: /* Write generated symbol definitions */ do i = 1 to nf queue dnam.i','dval.i say dnam.i','dval.i end /* Write appended symbol definitions */ do i = 1 to na queue dapp.i say dapp.i end queue '' 'EXECIO * DISKW SYMNAMES (FINIS' return Put_line: /* Analyze Data Division Map line */ parse var line linenr level dataname . parse var dataname dataname '.' . if dataname = 'FILLER' then Return if level = 'PROGRAM-ID' then Return if level = 88 then Do nf = nf + 1 dnam.nf = dataname dval.nf = d88.linenr dlvl.nf = lev Return end blk = substr(line,64,4) if level = 1 then nf = 0 hexoff = substr(line,79,3) || substr(line,83,3) if hexoff = ' ' then hexoff = '000000' parse var line 92 asmdef datatyp . if datatyp = 'Group' | datatyp = 'Grp-VarLen' then parse var asmdef . 'CL' len else do len = left(asmdef,length(asmdef)-1) if right(asmdef,2) = '1H' then len = 2 if right(asmdef,2) = '1F' then len = 4 if right(asmdef,2) = '2F' then len = 8 end select when datatyp = 'Group' then typ = 'CH' when datatyp = 'Grp-VarLen' then typ = 'CH' when datatyp = 'Display' then typ = 'CH' when datatyp = 'Disp-Num' then typ = 'ZD' when datatyp = 'Packed-Dec' then typ = 'PD' when datatyp = 'Binary' then typ = 'FI' when datatyp = 'Comp-1' then typ = 'FL' Page 4 of 22 5/2/2006 file://C:\DOCUME~1\gelewyc\LOCALS~1\Temp\DZKO70W6.htm when datatyp = 'Comp-2' then typ = 'FL' otherwise typ = 'CH' end if typ = 'FI' then do if s9.linenr /= 'Y' then typ = 'BI' end else do if typ = 'ZD' then if sp.linenr = 'Y' then if ld.linenr = 'Y' then typ = 'FS' else typ = 'CST' else if ld.linenr = 'Y' then typ = 'CLO' end off = 1 + x2d(hexoff) nf = nf + 1 dnam.nf = dataname dval.nf = off','len','typ dlvl.nf = lev Return Read_COBLIST: l88 = 0 lx = 0 na = 0 'EXECIO * DISKR COBLIST (FINIS' parse pull line do until substr(line,2,16) = ' LineID PL SL ' parse pull line end /* Process program text lines */ do until substr(line,2,16) /= ' LineID PL SL ' parse pull line do until left(line,1) = '1' call Check_Code_line parse pull line end parse pull line end /* Skip lines */ do until substr(line,2,18) = 'LineID Data Name' parse pull line end /* Process Data Division Map lines */ do until substr(line,2,18) /= 'LineID Data Name' parse pull line do until left(line,1) = '1' call Put_line parse pull line end parse pull line parse pull line end /* Skip rest */ do until queued() = 0 parse pull line end Return Page 5 of 22 5/2/2006 file://C:\DOCUME~1\gelewyc\LOCALS~1\Temp\DZKO70W6.htm Fix_Duplicates: /* Append _n to any duplicate data names */ nd = 0 tdup. = '' Do i = 1 to nf nam = dnam.i parse var tdup.nam flag i1 if flag = '' then do tdup.nam = '0' i iterate end if flag = '0' then do nd = nd + 1 td1.nd = i1 i tdup.nam = '1' nd iterate end td1.i1 = td1.i1 i End Do id = 1 to nd parse var td1.id i tail n = 0 Do while i /= '' n = n + 1 dnam.i = dnam.i || '_' || n parse var tail i tail End End Return Check_code_line: /* Analyze program text line , capture 88 VALUE clauses */ /* Capture S9, LEADING, SEPARATE parameters */ /* Make append lines from *+ comments */ parse var line 4 linenr 10 flag . 19 . 25 stmt 91 if linenr = '' then return linenr = linenr + 0 if left(stmt,2) = '*+' then do na = na + 1 dapp.na = substr(stmt,3) return end if left(stmt,1) = '*' then return if left(stmt,1) = '/' then return if lastpos('.',stmt) = 0 then do parse pull line if left(line,1) = '1' then parse pull line if substr(line,2,16) = ' LineID PL SL ' then parse pull line parse var line 4 x1 10 x2 . 19 . 25 stmt2 91 stmt = stmt||stmt2 end parse var stmt w1 . if w1 = '88' then do l88 = linenr if l88 /= 0 then do parse var stmt . 'VALUE' tail if tail /= 0 then do parse var tail value '.' . Page 6 of 22 5/2/2006 file://C:\DOCUME~1\gelewyc\LOCALS~1\Temp\DZKO70W6.htm d88.l88 = strip(value) if left(d88.l88,6) = 'SPACES' then d88.l88 = ''' ''' if left(d88.l88,4) = 'ZERO' then d88.l88 = '0' if left(d88.l88,9) = 'LOW-VALUE' then d88.l88 = 'X''00''' l88 = 0 end end return end else do lx = linenr if lx /= 0 then do parse var stmt x1 x2 x3 if pos(' S9',x3) /=0 then s9.lx = 'Y' if pos(' LEADING',x3) /=0 then ld.lx ='Y' if pos(' SEPARATE',x3) /=0 then sp.lx = 'Y' lx = 0 end end Return Back to top Keep the last n records Keeping the first n records of an input data set with DFSORT is pretty easy. You just use the STOPAFT=n operand of OPTION or the ENDREC operand of OUTFIL. For example, to keep the first 10 records, you can use either: OPTION COPY,STOPAFT=10 or: OPTION COPY OUTFIL ENDREC=10 But keeping the last n records of an input data set is a bit more challenging since DFSORT doesn't have any built-in functions to do that. The trick is to attach a sequence number to the records and sort them in descending order by the sequence number. That way, the last records end up at the front of the file and you can use STOPAFT or ENDREC to select the last n records. Unfortunately, those last n records will be in reverse order, so you have to sort them again in ascending order by the sequence number to get them back in their original order. It's less complicated then it sounds as illustrated by this DFSORT/ICETOOL job to get the last 10 records for an input data set with RECFM=FB and LRECL=80: //S1 EXEC PGM=ICETOOL //TOOLMSG DD SYSOUT=* //DFSMSG DD SYSOUT=* Page 7 of 22 5/2/2006 file://C:\DOCUME~1\gelewyc\LOCALS~1\Temp\DZKO70W6.htm //IN DD DSN=... FB input file //TEMP DD DSN=&T1,UNIT=SYSDA,SPACE=(CYL,(5,5)),DISP=(,PASS) //OUT DD DSN=... FB output data set //TOOLIN DD * SORT FROM(IN) USING(CTL1) SORT FROM(TEMP) TO(OUT) USING(CTL2) /* //CTL1CNTL DD * * Add sequence numbers. Use them to reverse the order of * the records. INREC FIELDS=(1,80,SEQNUM,8,BI) SORT FIELDS=(81,8,BI,D) * Get the last 10 records OUTFIL FNAMES=TEMP,ENDREC=10 /* //CTL2CNTL DD * * Use the sequence numbers to put the last 10 records back in their * original order. SORT FIELDS=(81,8,BI,A) * Remove the sequence numbers. OUTREC FIELDS=(1,80) /* Here's another example. This one gets the last 525 records from a VB input data set: //S2 EXEC PGM=ICETOOL //TOOLMSG DD SYSOUT=* //DFSMSG DD SYSOUT=* //IN DD DSN=... VB input file //TEMP DD DSN=&T1,UNIT=SYSDA,SPACE=(CYL,(5,5)),DISP=(,PASS) //OUT DD DSN=... VB output data set //TOOLIN DD * SORT FROM(IN) USING(CTL1) SORT FROM(TEMP) TO(OUT) USING(CTL2) /* //CTL1CNTL DD * * Add sequence numbers. Use them to reverse the order of * the records. INREC FIELDS=(1,4,5:SEQNUM,8,BI,13:5) SORT FIELDS=(5,8,BI,D) * Get the last 525 records OUTFIL FNAMES=TEMP,ENDREC=525 /* //CTL2CNTL DD * * Use the sequence numbers to put the last 525 records back in their * original order. SORT FIELDS=(5,8,BI,A) * Remove the sequence numbers. OUTREC FIELDS=(1,4,13) /* Back to top Set RC=12 or RC=4 if file is empty, has more than n records, etc Page 8 of 22 5/2/2006 file://C:\DOCUME~1\gelewyc\LOCALS~1\Temp\DZKO70W6.htm The following related questions have been asked by various customers: I need to check if a data set is empty or not. If it's empty I want to skip all other steps. Is there any way I can check for empty data sets? I would like to skip certain steps in my job if a file is empty. This would be easiest if a utility would generate a non-zero RC if the input file is empty. I have several datasets that I need to sort together. How can I terminate if the total count of records in these data sets is greater than 5000. I have a file that always has a header and trailer record and may or may not have data records. Is there any way to check if there are no data records in the file? ICETOOL can easily satisfy all of these requests. You can use ICETOOL's COUNT operator to set a return code of 12 or 4 if a specified data set is EMPTY, NOTEMPTY, HIGHER(n), LOWER(n), EQUAL(n) or NOTEQUAL(n), where n is a specified number of records (for example, 5000). This makes it easy to control the execution of downstream operators or steps using JCL facilities like IF or COND. If you use the RC4 operand, ICETOOL sets RC=4; otherwise it sets RC=12. For example, in the following ICETOOL job, the EMPTY operand of COUNT is used to stop STEP2 from being executed if the IN data set is empty. ICETOOL sets RC=12 if the IN data set is empty, or RC=0 if the IN data set is not empty. ICETOOL only reads one record to determine if the data set is empty or not empty, regardless of how many records there are in the data set. //STEP1 EXEC PGM=ICETOOL //TOOLMSG DD SYSOUT=* //DFSMSG DD SYSOUT=* //IN DD DSN=... //TOOLIN DD * * SET RC=12 IF THE 'IN' DATA SET IS EMPTY, OR * SET RC=0 IF THE 'IN' DATA SET IS NOT EMPTY COUNT FROM(IN) EMPTY /* // IF STEP1.RC = 0 THEN //*** STEP2 WILL RUN IF 'IN' IS NOT EMPTY //*** STEP2 WILL NOT RUN IF 'IN' IS EMPTY //STEP2 EXEC ... ... // ENDIF In this next example, the HIGHER(5000) operand of COUNT is used to skip a SORT operation if the count of records in three data sets is greater than 5000. ICETOOL sets RC=12 if the CONCAT data sets have a total record count greater than 5000, or a RC=0 if the total record count is less than or equal to 5000. MODE STOP (the default) tells ICETOOL not to execute the SORT operation if the COUNT operation sets RC=12. //SRT1 EXEC PGM=ICETOOL //TOOLMSG DD SYSOUT=* //DFSMSG DD SYSOUT=* //CONCAT DD DSN=... // DD DSN=... // DD DSN=... //OUT DD DSN=... //TOOLIN DD * * SORT THE 'CONCAT' DATA SETS ONLY IF THEY * HAVE LE 5000 RECORDS MODE STOP COUNT FROM(CONCAT) HIGHER(5000) Page 9 of 22 5/2/2006 file://C:\DOCUME~1\gelewyc\LOCALS~1\Temp\DZKO70W6.htm SORT FROM(CONCAT) TO(OUT) USING(CTL1) /* //CTL1CNTL DD * SORT FIELDS=(25,8,BI,A) /* In this last example, the EMPTY operand of COUNT is used to stop S2 from being executed if the INPUT data set doesn't have any data records between the header and trailer. An OMIT statement is used with COUNT to delete the header and trailer record, leaving only the data records as a subset of the INPUT data set. ICETOOL sets RC=4 (because the RC4 operand is specified) if the subset of records is empty, or RC=0 if the subset of records is not empty. //S2 EXEC PGM=ICETOOL //TOOLMSG DD SYSOUT=* //DFSMSG DD SYSOUT=* //INPUT DD DSN=... //TOOLIN DD * * SET RC=4 IF 'INPUT' ONLY HAS A HEADER AND TRAILER, OR * SET RC=0 IF 'INPUT' HAS DATA RECORDS. COUNT FROM(INPUT) EMPTY USING(HDTL) RC4 /* //HDTLCNTL DD * OMIT COND=(1,6,CH,EQ,C'HEADER',OR,1,7,CH,EQ,C'TRAILER') /* // IF S1.RC = 0 THEN //*** S2 WILL RUN IF 'IN' IS NOT EMPTY //*** S2 WILL NOT RUN IF 'IN' IS EMPTY //S2 EXEC ... ... // ENDIF Back to top FB to VB conversion DFSORT makes it easy to do FB to VB conversion and VB to FB conversion.. The FTOV operand of OUTFIL can be used to change fixed-length (e.g. FB) input records to variable- length (e.g. VB) output records. If FTOV is specified without OUTREC, the entire FB input record is used to build the VB output record. If FTOV is specified with OUTREC, the specified fields from the FB input record are used to build the VB output record. The VB output records will consist of a 4-byte RDW followed by the FB data. All output data sets for which FTOV is used must have or will be given variable-length record formats. Here's an example of FB to VB conversion: OUTFIL FNAMES=FBVB1,FTOV OUTFIL FNAMES=FBVB2,FTOV,OUTREC=(1,10,C'=',21,10) The VB output records for the FBVB1 data set will contain a 4-byte RDW followed by the FB input record. The VB output records for the FBVB2 data set will contain a 4-byte RDW followed by the characters Page 10 of 22 5/2/2006 file://C:\DOCUME~1\gelewyc\LOCALS~1\Temp\DZKO70W6.htm from input positions 1-10, an '=' character, and the characters from input positions 21-30. All of the VB output records created with FTOV will consist of: RDW + input fields Since all of the input fields from the FB input records are the same, all of the VB output records will be the same length. But if you have trailing characters such as blanks, asterisks, binary zeros, etc, you can create true VB output records with different lengths by using the VLTRIM=byte option of OUTFIL. VLTRIM=byte can be used with FTOV to remove trailing bytes of the specified type from the end of the VB output records. Here are some examples: OUTFIL FNAMES=FBVB3,FTOV,VLTRIM=C'*' OUTFIL FNAMES=FBVB4,FTOV,VLTRIM=X'40' OUTFIL FNAMES=FBVB5,FTOV,VLTRIM=X'00' FBVB3 will contain VB output records without trailing asterisks. FBVB4 will contain VB output records without trailing blanks. FBVB5 will contain VB output records without trailing binary zeros. To further illustrate how FTOV and VLTRIM=byte work, say you have the following 17-byte FB data records that you want to convert to VB data records: 123456*********** 0003************* ABCDEFGHIJ*****22 ***************** If you use: OUTFIL FTOV the following VB output records will be written (4-byte RDW followed by data): Length | Data 21 123456*********** 21 0003************* 21 ABCDEFGHIJ*****22 21 ***************** but if you use: OUTFIL FTOV,VLTRIM=C'*' the following VB output records will be written (4-byte RDW followed by data): Length | Data 10 123456 8 0003 21 ABCDEFGHIJ*****22 Page 11 of 22 5/2/2006 file://C:\DOCUME~1\gelewyc\LOCALS~1\Temp\DZKO70W6.htm 5 * Back to top Change all zeros in your records to spaces The following related questions have been asked by various customers: How can I replace all the X'00' bytes in a file with X'40' bytes? Is there a way to take a large file and replace the low values (X'00') interspersed throughout the file with spaces (X'40') instead? The low values can show up anywhere; they aren't in fixed positions. Can you tell me how to replace all the occurrences of one character (say 'a') with another character (say 'b') in a sequential file. Translation features of INREC, OUTREC and OUTFIL OUTREC make it easy to satisfy all of these requests. The TRAN=ALTSEQ operand can be used to change any character in an input file to another character in the output file within a specified field or throughout the entire record. The ALTSEQ statement is used to specify the from and to characters, and TRAN=ALTSEQ is used to specify which field or fields the ALTSEQ changes are to be applied to. Here's how you could change all low values (X'00') to spaces (X'40'), in an FB data set with an LRECL of 60: ALTSEQ CODE=(0040) OUTREC FIELDS=(1,60,TRAN=ALTSEQ) Here's how you could change all 'a' (X'81') and 'x' (X'A7') characters to 'b' (X'82') and 'y' (X'A8') characters, respectively, in a VB input data set with any LRECL: ALTSEQ CODE=(81A7,82A8) OUTREC FIELDS=(1,4,5,TRAN=ALTSEQ) Of course, you can make your changes to specified fields instead of to the entire record. This comes in handy when you have mixed character and numeric fields in your records and want to avoid making changes to the numeric fields. For example, if you had an FB input file that had characters in bytes 1-20, a PD field in bytes 21-25, and characters in bytes 26-80, you could changes zeros to spaces in the character fields using: ALTSEQ CODE=(0040) OUTREC FIELDS=(1,20,TRAN=ALTSEQ, CH - change zeros to spaces 21,5, PD field - no change 26,55,TRAN=ALTSEQ) CH - change zeros to spaces By not using TRAN=ALTSEQ for the PD field, we avoid changing PD values incorrectly, such as from X'000000001C' (P'1') to X'404040401C' (P'404040401'). Back to top Only include records with today's date Page 12 of 22 5/2/2006 file://C:\DOCUME~1\gelewyc\LOCALS~1\Temp\DZKO70W6.htm A customer asked the following question: Can I make use of DFSORT to sort all of the records from a VSAM file to a sequential file based on the current date? I have a C'yyyymmdd' date field starting at position 27 of each record. This job will be run every day. Each day I want only the records for that day to be copied into the sequential file from the VSAM file. You can useINCLUDE and OMIT to select records using a variety of formats for today's date like C'yyyymmdd', C'yyyy/mm/dd', +yyyymmdd, C'yyyyddd', C'yyyy/ddd', +yyyyddd, C'yymmdd' and so on, as explained here. The new DATE1 operand corresponds to a C'yyyymmdd' constant for today's date. So the following control statement will include only those records with a C'yyyymmdd' date in positions 27-34 equal to today's date: INCLUDE COND=(27,8,CH,EQ,DATE1) As some other examples: for date values in the form C'yyyy/mm/dd', you could use the DATE1(/) constant; for date values in the form C'yyyy-mm', you could use the DATE2(-) constant; for date values in the form P'yyyyddd', you could use the DATE3P constant; and for date values in the form Z'yymmdd' (2-digit year date), you could use the Y'DATE1' constant. Of course, you can use the other comparison operators (NE, GT, GE, LT, LE) as well as EQ. For example, you could use GT to select records with dates after today, or LT to select records with dates before today.. Back to top Include records using relative dates New! z/OS DFSORT V1R5 PTF UK90007 and DFSORT R14 PTF UK90006 (April, 2006) provide built-in functions for relative dates like the current date - 1 day or the current date + 30 days. See User Guide for PTFs UK90007 and UK90006 for complete details on using past date constants and future date constants. ***** Although DFSORT has built-in INCLUDE/OMIT constants for the current date in various forms, it does not have built-in functions for relative dates like the current date - 1 day or the current date + 30 days. However, if you want to use INCLUDE/OMIT with relative date constants, you can create those relative date constants as DFSORT Symbols with REXX, and then use the symbols in your INCLUDE/OMIT statements. Here's an example of a job to only include records where the 'yyyymmdd' date in positions 1-8 of the input file is equal to or greater than yesterday's date (that is, ge current date - 1 day). //TSOBATCH EXEC PGM=IKJEFT1A,DYNAMNBR=200 //SYSEXEC DD DSN=userid.REXXD.REXX,DISP=SHR //SYSPRINT DD SYSOUT=* //SYSTSPRT DD SYSOUT=* //OUTFILE DD DSN=&&S1,UNIT=SYSDA,SPACE=(TRK,(1,1)), // DISP=(,PASS),LRECL=80,RECFM=FB,DSORG=PS //SYSTSIN DD * %REXXRD //S1 EXEC PGM=ICEMAN Page 13 of 22 5/2/2006 file://C:\DOCUME~1\gelewyc\LOCALS~1\Temp\DZKO70W6.htm //SYSOUT DD SYSOUT=* //SYMNAMES DD DSN=&&S1,DISP=(OLD,PASS) //SORTIN DD DSN=.... input file //SORTOUT DD DSN=... output file //SYSIN DD * OPTION COPY INCLUDE COND=(1,8,CH,GE,RELDATE) /* The REXX code in userid.REXXD.REXX creates a DFSORT Symbol with date-1 as 'yyyymmdd' in the &&S1 data set (must have RECFM=FB and LRECL=80) as follows: RELDATE,C'yyyymmdd' and RELDATE can then be used as a constant in INCLUDE/OMIT. Here's the REXX code in userid.REXXD.REXX(REXXRD): /* REXX */ "EXECIO 0 DISKW OUTFILE(OPEN" /* OPEN FILE */ QUEUE "RELDATE,C"||"'"||DATE('S',DATE('B')-1,'B')||"'" "EXECIO 1 DISKW OUTFILE (FINIS" "FREE FI(OUTFILE)" You can change -1 to any -n or +n value you need. Back to top Insert date and time of run into records The following related questions have been asked by various customers: Is there any way to dynamically insert the current date, like 'yyyy-mm-dd', in my records? Something like: SORT FIELDS=(1,6,CH,A),FORMAT=CH OUTREC FIELDS=(1:C'2002-03-11',11:1,6) But with the current date in positions 1-10 instead of a hardcoded constant? How can I save the current system date (any format) into a sequential file, in such a way that all the file will ever contain is the said date (this will be used in another step)? You can use INREC, OUTREC and OUTFIL OUTREC to insert the current date and time in a variety of formats into your records, as explained here. Both the DATE1(c) and DATE=(4MDc) operands correspond to a C'yyyycmmcdd' constant for today's date where c is any separator character you like except blank. So either of the following pairs of control statements will sort your records on input positions 1-6 and reformat them with today's date in the form C'yyyy-mm-dd' in output positions 1-10, and input positions 1-6 in output positions 11-16. SORT FIELDS=(1,6,CH,A),FORMAT=CH OUTREC FIELDS=(1:DATE1(-),11:1,6) Page 14 of 22 5/2/2006 file://C:\DOCUME~1\gelewyc\LOCALS~1\Temp\DZKO70W6.htm or SORT FIELDS=(1,6,CH,A),FORMAT=CH OUTREC FIELDS=(1:DATE=(4MD-),11:1,6) You could insert the current time as well as the current date in your records to produce a timestamp. For example: OUTREC FIELDS=(DATE3,TIME1,1,6) would produce a character timestamp in output positions 1-12 of the form: yyyydddhhmmss Date constants can be produced in a variety of other character, zoned decimal and packed decimal formats as well such as C'yyyy-mm', Z'yyyymmdd' and P'yyddd'. Time constants can also be produced in a variety of other character, zoned decimal and packed decimal formats as well such as C'hh:mm', Z'hhmmssxx' and P'hhmmss'. If, as in the second question above, you wanted to produce just one record containing the date, you could select from a variety of date formats. For example, if you wanted to create a record with just C'dddyy', you could do it with OUTREC as follows: //DATERCD EXEC PGM=ICEMAN //SYSOUT DD SYSOUT=* //SORTIN DD * DUMMY RECORD //SORTOUT DD DSN=... //SYSIN DD * OPTION COPY OUTREC FIELDS=(YDDDNS=(DY)) /* Back to top Change uppercase to lowercase or lowercase to uppercase A customer asked the following question: Can DFSORT be used change a field from lowercase to uppercase? Translation features of INREC, OUTREC and OUTFIL OUTREC make it easy to change the case of characters in your fields. The TRAN=LTOU operand can be used to change lowercase EBCDIC letters (a-z) to uppercase EBCDIC letters (A-Z) anywhere in a field. The TRAN=UTOL operand can be used to change uppercase EBCDIC letters to lowercase EBCDIC letters anywhere in a field. Here's how you could change lowercase letters to uppercase letters in a 100-byte character field starting at position 51 and in a 40-byte character field starting in position 301, in an FB data set with an LRECL of 500: Page 15 of 22 5/2/2006 file://C:\DOCUME~1\gelewyc\LOCALS~1\Temp\DZKO70W6.htm OUTREC FIELDS=(1,50, 1-50: no change 51,100,TRAN=LTOU, 51-150: lowercase to uppercase 151,150, 151-300: no change 301,40,TRAN=LTOU, 301-340: lowercase to uppercase 341,160) 341-500: no change Of course, you could change the case in the entire record as well. For example, here's how you could change uppercase to lowercase in the records of an FB data set with an LRECL of 200: OUTREC FIELDS=(1,200,TRAN=UTOL) And here's how you could change uppercase to lowercase in the records of a VB data set with any LRECL: OUTREC FIELDS=(1,4,5,TRAN=UTOL) Back to top Generate JCL to submit to the internal reader A customer asked the following question: I have a program that generates a data set with a list of file names that can vary from run to run. I'd like to use ICEGENER to copy the records from the files in the list into one output file. For example, the generated data set might have these file names: RUN001.OUTPUT RUN001.RESULTS RUN005.OUTPUT RUN005.RESULTS so I'd want my output file to have the records from RUN001.OUTPUT, RUN001.RESULTS, RUN005.OUTPUTand RUN005.RESULTS in that order. Can DFSORT help me do this? We can copy the input data sets with an ICEGENER JOB like the following: //CPYFILES JOB (XXX,005),'PRGMR',CLASS=A,MSGCLASS=H, // MSGLEVEL=(1,1),TIME=(,15) //S1 EXEC PGM=ICEGENER //SYSPRINT DD SYSOUT=* //SYSUT2 DD DSN=&OUT,DISP=(,PASS),SPACE=(CYL,(5,5)),UNIT=SYSDA //SYSIN DD DUMMY //SYSUT1 DD DISP=SHR,DSN=file1 // DD DISP=SHR,DSN=file2 ... // DD DISP=SHR,DSN=filen But since we have to get the file1, file2, ..., filen names from the data set containing the list of file Page 16 of 22 5/2/2006 file://C:\DOCUME~1\gelewyc\LOCALS~1\Temp\DZKO70W6.htm names, we'll actually have to generate the JCL shown above and have the internal reader submit it for execution. That's the tricky part, but here's what we'll do: We'll generate all of the JCL statements, except for the SYSUT1 concatenation statements, with OUTFIL's HEADER1 operand. The HEADER1 output is written before the data records and can contain as many output records as you like (/ is used to start a new record). Since HEADER1 is a report parameter, the report data set is normally given a RECFM of FBA and a 1-byte ANSI carriage control character (for example, '1' for page eject) is placed in position 1 of the report. For our purposes, we don't want that ANSI character in position 1 because it would give us invalid JCL (e.g. '1//CPYFILES ...' instead of '//CPYFILES ...". So we'll use OUTFIL's REMOVECC operand remove the ANSI character from each JCL statement we generate. We'll generate the SYSUT1 concatenation statements with OUTFIL's OUTREC operand. We'll change the file name in positions 1-44 of each input record into a DD statement referencing that file name. But as shown in the JCL we're trying to generate above, we need 'SYSUT1' as the ddname for the first DD and blanks as the ddname for the second and subsequent DD. To make this happen, we'll use an OUTREC statement with a SEQNUM operand to add a sequence number to the input records, and then use CHANGE in OUTFIL OUTREC to determine if the sequence number is 1 or not so we can set up the correct ddname. Here's our DFSORT job to generate the ICEGENER JCL we need and have the internal reader submit it: //GENJOB EXEC PGM=ICEMAN //SYSOUT DD SYSOUT=* //SORTIN DD DSN=... list of file names //IRDR DD SYSOUT=(A,INTRDR) internal reader //SYSIN DD * OPTION COPY * Add sequence numbers to file list so we can identify the * first file in the list and use '//SYSUT1 DD' for it. * We'll use '// DD' for the second and subsequent files * in the list. OUTREC FIELDS=(1,44, file name from list 51:SEQNUM,3,ZD) sequence number * Generate the JCL for output to the internal reader OUTFIL FNAMES=IRDR, * Remove the carriage control character from the "report". REMOVECC, * Generate JOB, EXEC, SYSPRINT, SYSUT2 and SYSIN statements. HEADER1=('//CPYFILES JOB (XXX,005),''PRGMR'',', 'CLASS=A,MSGCLASS=H,',/, '// MSGLEVEL=(1,1),TIME=(,15)',/, '//S1 EXEC PGM=ICEGENER',/, '//SYSPRINT DD SYSOUT=*',/, '//SYSUT2 DD DSN=&OUT,DISP=(,PASS),SPACE=(CYL,(5,5)),', 'UNIT=SYSDA',/, '//SYSIN DD DUMMY'), * Generate //SYSUT1 DD DISP=SHR,DSN=name1 * // DD DISP=SHR,DSN=name2 * ... OUTREC=(C'//', 51,3,CHANGE=(6,C'001',C'SYSUT1'), 'SYSUT1' for first name NOMATCH=(C' '), blanks for subsequent names C' DD DISP=SHR,DSN=', 1,44, file name from list 80:X) ensure record length is 80 /* Page 17 of 22 5/2/2006 file://C:\DOCUME~1\gelewyc\LOCALS~1\Temp\DZKO70W6.htm If you'd rather not encode your JOB, etc statements as constants, you can do the same thing in several DFSORT steps that we did above in one DFSORT step, like this: //GENJCL1 EXEC PGM=ICEMAN //SYSOUT DD SYSOUT=* //SORTIN DD DATA,DLM=$$ //CPYFILES JOB (XXX,005),'PRGMR',CLASS=A,MSGCLASS=H, // MSGLEVEL=(1,1),TIME=(,15) //S1 EXEC PGM=ICEGENER //SYSPRINT DD SYSOUT=* //SYSUT2 DD DSN=&OUT,DISP=(,PASS),SPACE=(CYL,(5,5)),UNIT=SYSDA //SYSIN DD DUMMY $$ //SORTOUT DD DSN=&T1,UNIT=SYSDA,SPACE=(CYL,(1,1)),DISP=(,PASS) //SYSIN DD * * Copy JOB, EXEC, SYSPRINT, SYSUT2 and SYSIN statements. OPTION COPY /* //GENJCL2 EXEC PGM=ICEMAN //SYSOUT DD SYSOUT=* //SYSUDUMP DD SYSOUT=* //SORTIN DD DSN=... list of file names //TEMP DD DSN=&T1,DISP=(MOD,PASS) //SYSIN DD * OPTION COPY * Add sequence numbers to file list so we can identify the * first file in the list and use '//SYSUT1 DD' for it. * We'll use '// DD' for the second and subsequent files * in the list. OUTREC FIELDS=(1,44, file name from list 51:SEQNUM,3,ZD) sequence number * Generate //SYSUT1 DD DISP=SHR,DSN=name1 * // DD DISP=SHR,DSN=name2 * ... OUTFIL FNAMES=TEMP, OUTREC=(C'//', 51,3,CHANGE=(6,C'001',C'SYSUT1'), 'SYSUT1' for first name NOMATCH=(C' '), blanks for subsequent names C' DD DISP=SHR,DSN=', 1,44, file name from list 80:X) ensure record length is 80 /* //SUBJCL EXEC PGM=ICEMAN //SYSOUT DD SYSOUT=* //SORTIN DD DSN=&T1,DISP=(OLD,PASS) //SORTOUT DD SYSOUT=(A,INTRDR) internal reader //SYSIN DD * * Submit the JCL to the internal reader OPTION COPY /* Back to top Page 18 of 22 5/2/2006 file://C:\DOCUME~1\gelewyc\LOCALS~1\Temp\DZKO70W6.htm Change a C sign to an F sign in PD values New! z/OS DFSORT V1R5 PTF UK90007 and DFSORT R14 PTF UK90006 (April, 2006) provide built-in functions that allow you to select C or F for the sign of positive PD values or change from one to the other. See User Guide for PTFs UK90007 and UK90006 for complete details on the new TO=PDC and TO=PDF parameters. ***** A customer asked the following question: I have three 5-byte packed decimal fields starting in positions 1, 6 and 11. The positive values have a C sign (e.g X'123456789C'), but I need them to have an F sign. The negative values have a D sign which I don't want to change. Can DFSORT change the sign from C to F for the positive values? Translation features of INREC, OUTREC and OUTFIL OUTREC make it easy to change the C sign to an F sign in your positive PD values. The new TRAN=ALTSEQ operand can be used to change a character anywhere in a field to another character. The ALTSEQ statement is used to specify the from and to characters, and TRAN=ALTSEQ is used to specify which field or fields the ALTSEQ changes are to be applied to. In this case, we only want to apply TRAN=ALTSEQ to the sign (last) byte of each of the three PD fields, that is, positions 5, 10 and 15. Here are the control statements we'd use: OUTREC FIELDS=(1,4,5,1,TRAN=ALTSEQ, 6,4,10,1,TRAN=ALTSEQ, 11,4,15,1,TRAN=ALTSEQ) ALTSEQ CODE=(0C0F,1C1F,2C2F,3C3F,4C4F, 5C5F,6C6F,7C7F,8C8F,9C9F) Back to top Totals by key and grand totals A customer asked the following question: I have a file with ZD data for which I want totals by key and grand totals. The 10-byte CH key starts in position 1 and the 12-byte ZD field starts in position 19. Here's an example of what the input might look like: (Key) (Data) 052500ABCDGRC 00000002246K 053500ABCDGRC 00000828784K 054500ABCDGRC 00000000155H 052501ABCDGRC 02019572110B 053501ABCDGRC 00121859976B 054501ABCDGRC 00515208642F 052502ABCDGRC 02638364444R 053502ABCDGRC 00131222671P 054502ABCDGRC 00824793989R And here's what I'd want the sorted output to look like: (Key) (052 total) (053 total) (054 total) (grand total) Page 19 of 22 5/2/2006 file://C:\DOCUME~1\gelewyc\LOCALS~1\Temp\DZKO70W6.htm 500ABCDGRC 00000002246K 00000828784K 00000000155H 00000830874O 501ABCDGRC 02019572110B 00121859976B 00515208642F 02656640729{ 502ABCDGRC 02638364444R 00131222671P 00824793989R 03594381106N Note that the (headings) shown above are just for reference; they shouldn't actually appear in the output data set. I know an indirect method of doing this which will take 5 passes over the data, but is there a way to do it in a single pass with DFSORT? The trick here is to use IFTHEN clauses to reformat the records for each type of key (052, 053 and 054) with slots for all of the keys and the grand total. The actual key will go into its slot and the grand total slot, and zeros will go into the slots for the other keys. That will allow us to use SUM to produce the key totals and grand totals. Here's a DFSORT job that produces the requested output: //S1 EXEC PGM=ICEMAN //SYSOUT DD SYSOUT=* //SORTIN DD DSN=... input file //SORTOUT DD DSN=... output file //SYSIN DD * * Reformat 052 records with a slot for 052, 053, 054 and grand total. * 052 and total will have the 052 values. Others will be Z'0'. INREC IFTHEN=(WHEN=(1,3,CH,EQ,C'052'), BUILD=(1:4,10,16:19,12,30:12C'0',44:12C'0',58:19,12)), * Reformat 053 records with a slot for 052, 053, 054 and grand total. * 053 and total will have the 053 values. Others will be Z'0'. IFTHEN=(WHEN=(1,3,CH,EQ,C'053'), BUILD=(1:4,10,16:12C'0',30:19,12,44:12C'0',58:19,12)), * Reformat 054 records with a slot for 052, 053, 054 and grand total. * 054 and total will have the 054 values. Others will be Z'0'. IFTHEN=(WHEN=(1,3,CH,EQ,C'054'), BUILD=(1:4,10,16:12C'0',30:12C'0',44:19,12,58:19,12)) * Sort on the key SORT FIELDS=(1,10,CH,A) * Sum the 052, 053, 054 and grand total fields. SUM FORMAT=ZD,FIELDS=(16,12,30,12,44,12,58,12) /* Back to top Join fields from two files record-by-record A customer asked the following question: I want to merge two files laterally record by record. For example: If FILE1 contains: AAAAA BBBBB CCCCC Page 20 of 22 5/2/2006 file://C:\DOCUME~1\gelewyc\LOCALS~1\Temp\DZKO70W6.htm and FILE2 contains: 11111 22222 33333 then my output file should contain: AAAAA11111 BBBBB22222 CCCCC33333 Can DFSORT do this? Normally, we're asked how to join files on a key, but in this case there is no key; we just want to join the files record-by-record. Well, no problem, we'll just create a key we can use by adding a sequence number (1, 2, ...) to the records in file1 and a sequence number (1, 2, ...) to the records in file2. Then we can join the fields from the 1 records, from the 2 records, and so on using the same technique we used for joining files on a key. Below is an ICETOOL job that can do this join by using the SPLICE operator. SPLICE helps you perform various file join and match operations. //S1 EXEC PGM=ICETOOL //TOOLMSG DD SYSOUT=* //DFSMSG DD SYSOUT=* //IN1 DD DSN=... file1 //IN2 DD DSN=... file2 //TMP1 DD DSN=&&TEMP1,DISP=(MOD,PASS),SPACE=(TRK,(5,5)),UNIT=SYSDA //OUT DD DSN=... output file //TOOLIN DD * * Reformat the IN1 data set so it can be spliced COPY FROM(IN1) TO(TMP1) USING(CTL1) * Reformat the IN2 data set so it can be spliced COPY FROM(IN2) TO(TMP1) USING(CTL2) * Splice records with matching sequence numbers. SPLICE FROM(TMP1) TO(OUT) ON(11,8,PD) WITH(6,5) USING(CTL3) /* //CTL1CNTL DD * * Use OUTREC to create: |f1fld|blank|seqnum| OUTREC FIELDS=(1:1,5,11:SEQNUM,8,PD) /* //CTL2CNTL DD * * Use OUTREC to create: |blank|f2fld|seqnum| OUTREC FIELDS=(6:1,5,11:SEQNUM,8,PD) /* //CTL3CNTL DD * * Use OUTFIL OUTREC to remove the sequence number OUTFIL FNAMES=OUT,OUTREC=(1,10) /* Back to top Page 21 of 22 5/2/2006 file://C:\DOCUME~1\gelewyc\LOCALS~1\Temp\DZKO70W6.htm Page 22 of 22 5/2/2006 file://C:\DOCUME~1\gelewyc\LOCALS~1\Temp\DZKO70W6.htm