You are on page 1of 34

REXX Programming For TSO: Advanced Concepts

Dan ODea September 2, 2004

Table of Contents
Introduction . . . . . . . . . . . . . . . . . Goals of the course. . . . . . . . . . . . . More on syntax Comments . . . . . . . . . . . . . . . . . . Handling Hexadecimals. . . . . . . . . . . . More on If/Then Using words to indicate success or failure . Boolean operators. . . . . . . . . . . . . . Passing Parameters Using keyword parameters . . . . . . . . . . Parsing Using Using PARSE PARSE columns. literals VALUE. . SOURCE . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 3 4 4 4 5 5 7 8 8

9 . 9 . 10 . 10 . 10 12 12 13 14 15

Trace Output Line Codes OutTrap Restrictions Arithmetic and verifying expressions Addressing Environments Stem Variables

System Information 16 SYSVAR . . . . . . . . . . . . . . . . . . . 16 MVSVAR . . . . . . . . . . . . . . . . . . . 18 Other Performance Concerns Putting it all together: Appendix A, Page 26: Appendix B, Page 28: real-world examples 19 20

a few simple edit macros solutions to exercises

The first part of this course provided you with basic learning needed to code REXX execs. Some topics were glossed over, or skipped entirely. REXX performance was not discussed. Finally, some REXX features can be used to make your program more readable, such as word tests (something like the COBOL 88 level variable). This document corrects those oversights. While the first document often used simplified examples, this document uses real-world examples wherever possible.

Goals of the Course

When you complete the course you will be able to do the following. Do arithmetic on hexadecimal values. Use nouns in IF statements rather than comparisons. Handle both positional and keyword parameters. Parse anything into anything. Understand trace output. Verify arithmetic expressions. Use multi-dimensional stem variables (matrixes). Code more efficient REXX execs.

More on REXX Syntax

Comments are started by /* and ended with */. They can go anywhere in the program. For example, this is a wing comment:

Say Hello

/* instruction displays Hello */

This is a box comment. Note each line is not closed. Only the first and last lines have slashes; the first slash starts the comment, and the last finishes it.

/* multiple-line comments are more efficient than many single-line comments. When using like this, use one pair of delimiters. */
Longer comments are more efficient because the REXX interpreter opens comment processing each time it encounters a /*. By coding only one set around multiple comment lines, the interpreter does less work. This is especially important in loops. Make a box comment above a loop to explain everything the loop does. If the comments are in the loop, they are scanned each pass through the loop. To avoid unnecessary loop processing, dont comment within the loop.

Hexadecimal Values
You may recall that hexadecimal numbers may be used. Here is a piece of code showing how interpreting hex characters is used to determine what LPAR youre running on. How it works: First, convert the 4 bytes of system storage area at location x10 to hex. Convert that to decimal for math, then add the offset to the storage location of the system name (in this case, hex 154 / decimal 340). Convert the result back to hex. Then get the 8 bytes of storage at the resulting address and strip the spaces from it.

/*------------* * What LPAR? * *------------*/ CVT = C2X(STORAGE(10,4)) CVT_154 = D2X(X2D(CVT) + X2D(154)) SID = STRIP(STORAGE(CVT_154,8))

Conditional Operators
As we know, the IF statement follows a standard format:

If expression Then one instruction Else one instruction

DO WHILE and DO UNTIL statements are considered IF statements. Internally, any IF statement operates by testing the outcome of a binary choice (yes or no). Weve seen the results of IF comparisons can be displayed as 1 (true) or 0 (false). We can use this feature to our advantage. In REXX, almost any statement can contain a variable to be resolved. Consider the following:

Dan = 0 Say Enter name Pull Name If Name = Dan Then Dan = 1 If Dan Then Say Hi, Mr. ODea! Else Say Hi, Name.
REXX resolves the expression If Dan as either If 0 or If 1 depending on the value in the variable Dan. Dan is called a noun switch. Its COBOL equivalent is the 88 level. There are two things to keep in mind when using this method: 1. You must be careful to set the switch to only zero or one. If the IF statement finds any other value, your exec terminates with a syntax error. 2. You cannot negate the switch. To test for a negative condition, its a good idea to code the statement positively, and use the NOP command to skip the THEN condition. For example,

KSDS = 0 If <index> Then KSDS = 1 If KSDS Then NOP /* Do nothing for KSDS */ Else Say ESDS /* Do ESDS stuff */

Conditional Operators Noun Switch Example

Here is part of the code from the stack example above. First, set the switch to 0 (off/false). Then, check if the dataset has an index component. If the index component exists, turn the switch on. You can then check the switch in the IF.

KSDS = 0 CKIX = INDSN.INDEX If SysDSN(CKIX) = OK Then KSDS = 1 :: If KSDS Then Do Queue " DATA (NAME("NewDSN".DATA)) -" Queue " INDEX (NAME("NewDSN".INDEX))" End Else Queue " DATA (NAME("NewDSN".DATA))"
Heres another piece of code used to scan a LISTC ENT() ALL output for the index CI size:

If EndOfData Then NOP Else Do Do Until LCA.I = "ATTRIBUTES"; I = I + 1; End I = I + 1 ICIS = Strip(Right(Word(LCA.I,4),6),"L","-") End
Notes on the above: 1. The Do Until scans the output from the previous CISIZE field (the one in the data component section) until the index attributes section to skip over stuff we dont want. The index I must point to the next line after ATTRIBUTES to get the CISZ. 2. The last line gets the fourth word of the output line, takes the last 6 characters of that word, then removes leading dashes from it to give the CI size of the index.

Exercise #1
Modify the exec you wrote in Exercise #28 of the first class to use a noun switch instead of expression tests when deciding what to do with the dataset.

Passing Information to the Exec

The ARG statement parses values passed to the program based on spaces or commas. ARG is thus a positional operation selector. Heres one way to code the ARG to enable you to process passed parameters as keywords.

/* REXX Keyword Parms, LC accepts up to three parms. More than that are ignored. */ ARG PARM.1 PARM.2 PARM.3 . Lall = ; LGDG = Do I = 1 to 3 Parse Var Parm.I Opt 4 . Select When Opt = Then NOP When Opt = ENT Then Do Parse Var Parm.I . ( LCADSN LCADSN = Strip(LCADSN,T,)) End When Opt = ALL Then LAll = ALL When Opt = GDG Then LGDG = GDG Otherwise Say Parm.I is invalid. End End LCADSN = $FixDSN(LCADSN) LISTC ENT(LCADSN) LAll LGDG
This is designed to perform a LISTC ENT() on without having to code ALL, GDG, or other argument in any order. All these calls give the same result:



Modify the exec you wrote in Exercise #16 from the first course to accept the unit and amount values as keywords. Only accept two arguments. If one argument is bad, display the syntax and exit.

Separating Data: Parsing

In the introduction we talked about parsing data by word or literal. You can also parse data in other ways. Some of them are very useful. You can parse by column number. It can get complicated because column numbers in PARSE are not intuitive, but for some things it performs very well. The PARSE command is very fast. It is faster than SUBSTR or the LEFT and RIGHT functions. In loops running many times, PARSE using columns can save you some CPU. When specifying column delimiters, the number to the left of the variable name is the start column, while the number on the right is the end column, minus one. As you can see in the example, sometimes this can be misleading:


In this example, the variables are stored as: VAR1 = ABCD; VAR2 = EFGH; VAR3 = IJKL

When using both column numbers everywhere, it can be worse:


In this example, the variables are stored as: VAR1 = ABCD; VAR2 = GH; VAR3 = KL Please dont give up on this just because it looks complicated. Youll see why below.

PARSE using Literals

When parsing using a literal, you can use any literal. Consider this:


After the PARSE the variable DSN contains YOUR.DSN. However, you can skip the LINE1 assignment statement by using PARSE VALUE and the literal itself.

PARSE VALUE needs the keyword WITH to tell where the template begins. This can be used with functions as well as variables.

PARSE VALUE This is a sample With A B C D PARSE VALUE TIME() With HRS : MIN : SEC

PARSE SOURCE asks TSO for information about the way the exec was run.

where: OP_SYSTEM is TSO HOW_CALLED is either COMMAND, SUBROUTINE, or FUNCTION EXEC_NAME is the name of the exec in upper case DD_NAME the exec was found in SYSEXEC or SYSPROC DATASET_NAME is the DSN the exec was in when called implicitly AS_CALLED is the name the exec was invoked by. May be in lower case when called implicitly DEFAULT_ADDRESS is the initial address environment. This is usually TSO, MVS, ISPEXEC (i. e. ISPF), or ISREDIT (the ISPF Editor) NAME_OF_ADDRESS_SPACE is one of MVS, TSO, or ISPF.

Parsing continued
Here are several examples of using PARSE rather than a built-in functions or many assignments. Take the third word from the variable INVAR and call it WORD3. The PARSE instructions are much faster, and if you dont need the previous two words use the .. 1. 2. 3. 4. WORD3 WORD3 PARSE PARSE = WORD(INVAR,3) = SUBWORD(INVAR,1,3) VAR INVAR JUNK1 JUNK2 WORD3 . VAR INVAR . . WORD3 .

Take the left five bytes from the variable AMBI and call it LEFTY. LEFT is faster then SUBSTR. Again, the PARSE instruction are much faster than either. 1. LEFTY = Left(AMBI,5) 2. LEFTY = SUBSTR(AMBI,1,5) 3. PARSE VAR AMBI 1 LEFTY 6 . Take the right five bytes from the variable AMBI and call it RT. Note there is an extra step for the SUBSTR example, and no PARSE example. Parse could be used if you knew, when you coded, how long AMBI would be. The single function call is faster. 1. RT = Right(AMBI,5) 2. ST = Length(AMBI) 4; RT = SUBSTR(AMBI,ST,5) Take the middle five bytes from the variable AMBI, where the middle is known before coding. The PARSE instruction is faster. 1. MID = SUBSTR(AMBI,5,5) 2. PARSE VAR AMBI . 5 MID 10 . Set A, B, C, and D to null. The PARSE instruction is faster. You can also use this method to set all variables to zero. 1. A = ; B = ; C = ; D = 2. PARSE VALUE WITH A B C D 3. PARSE VALUE 0 0 0 0 WITH A B C D


Exercise #3
Write an exec to parse the date into month, day, and year. Use Parse Value. Redisplay the date in the following formats: MM.DD.YY (USA), DD.MM.YY (European), YY.DD.MM, and YY.MM.DD (ordered). The command to get the USA-style date (MM/DD/YY) is DATE(U).


Trace Output Line Codes

When running a program in trace mode, the REXX interpreter prefixes each line of trace output with a three character symbol. These symbols tell you what is happening at each stage of REXXs handling of the statement. The three-character codes displayed during tracing are:

*-* +++ >>> >.> >C> >F> >L> >O> >P> >V>

original program line trace message result of an expression during TRACE R value assigned to a placeholder (period) during parsing resolved name of a compound variable result of a function call a literal result of an operation on two terms result of a prefix operation contents of a variable OutTrap Restrictions

Not all output from TSO commands can be trapped by REXX. Whether you can trap output depends on how the command sends its output to the screen. Outtrap cannot capture lines produced by the following. TPUT WTO macro Messages issued by REXX (IRX*) Trace output messages An example of a command you cannot capture the output for is CONCAT. Try it.


Arithmetic Operations in REXX

As you know, there are certain characters you can use in any arithmetic statement, and some you cant. In the first half of the class we saw the basic arithmetic operators. Parentheses can be used, as well as spaces to separate terms, periods for decimals, and the letters A F for hexadecimal numbers. Here is a clever way to make sure your expression (EXPR) contains only valid characters:

If Verify(EXPR,'1234567890ABCDEF+-*/% ()') = 0
The VERIFY function checks to see if all the characters in the first string are contained in the second string.

An Unusual Type of DO loop

On a DO from X to Y loop, the FOR clause enforces a specific count:

Do I = 1.234 To 9943.2323 by .3203 For 22 Say I is I End

The FOR tells REXX to run this loop 22 times no matter what else happens.


Talking to the Environment

For each ADDRESS command, REXX opens a command processor link. To improve performance, then, it is good practice to use as few ADDRESS commands as possible. For example, the following code is a subroutine to format and submit a piece of JCL.

This is the same code, with the ADDRESS commands as part of the code.

While both produce the same results, the first one uses less CPU and runs slightly faster because theres no address switching except for the SUBMIT command passed to TSO. For short execs the difference is so small its almost meaningless, but for many consecutive calls, or calls within a loop, the difference is measurable.


More on Stem Variables

So far weve only seen stem variables with a single index thats always a number. Stem variables are more flexible than that. For example, although a stem variable has a stem and an index, a single entry of a stem variable is just like any other, non-stem variable. We can set any single entry of a stem variable to be a stem variable using a second index. Here is a piece of code to initializes a chessboard, a two-dimensional array of eight ranks and eight files.

Do R = 1 To 8 Do F = 1 To 8 ChessBoard.R.F = "." End End

The stem index does not have to be a number. REXX understands the use of a letter rather than a number. For example, try this.

/* REXX */ Do Forever Say What is your name (END to stop)? Pull NAME If NAME = END Then Leave Say Thanks, NAME , how old are you? Pull HOW_OLD If DataType(HOW_OLD) <> NUM Then Say Not valid age, rejected. Else AGE.NAME = HOW_OLD End Do Forever Say Whose age would you like, (END to stop)? Pull NAME If NAME = END Then Leave If SYMBOL(AGE.NAME) <> VAR Then Say Name not in memory. Else Say NAME is AGE.NAME years old. End


System Variables
REXX provides two sets of system variables. The first set is SYSVAR. 1. 2. 3. 4. SYSPREF: the prefix assigned to not-fully-qualified dataset names. SYSPROC: the logon procedure for the current session. SYSUID: the user ID of the person logged on. SYSLTERM and SYSWTERM: the length and width of the current terminal screen. In batch, SYSLTERM returns 0 and SYSWTERM returns 132. 5. SYSENV: returns FORE or BACK. 6. SYSICMD: returns the name of the running, implicitly called exec. If the call was explicit, SYSICMD returns null. 7. SYSISPF: returns ACTIVE or NOT ACTIVE. 8. SYSNEST: if the current program was called from another, SYSNEST returns YES; otherwise it returns NO. 9. SYSPCMD: the most recently processed TSO-E command processor. The initial value of SYSPCMD may be EXEC (if the EXEC command was used) or EDIT (if the EXEC subcommand of EDIT was used). 10. SYSSCMD: the most recently processed TSO-E subcommand processor. The initial value of SYSPCMD may be null (if the EXEC command was used) or EXEC (if the EXEC subcommand of EDIT was used). 11. SYSCPU: the number of CPU seconds used this session. 12. SYSSRV: the number of service units used this session. 13. SYSHSM: the status of DFHSM. Returns AVAILABLE or null. 14. SYSJES: the name and level of JES. 15. SYSLRACF: the level of RACF. If RACF is not installed, this is null. 16. SYSRACF: returns AVAILABLE, NOT AVAILABLE, or NOT INSTALLED. 17. SYSNODE: the JES node name. This returns either the JES node name, the string -INACTIVE-, or the string -DOWNLEVEL- if the subsystem is neither JES2 SP4.3 or later, nor JES3 SP5.1.1 or later. 18. SYSTERMID: the terminal ID, or null if batch. 19. SYSTSOE: the level of TSO installed. For OS/390 Version 2, Release 4 and later, SYSTSOE returns 2060. 20. SYSDTERM: If double-byte character set (DBCS) is enabled, returns YES. 21. SYSKTERM: if Katakana character set is enabled, returns YES. 22. SYSPLANG and SYSSLANG: returns the 3-byte primary and secondary language settings. 23. SOLDISP and UNSDISP: show solicited (operator replies) or unsolicited (operator messages) messages on the users terminal. YES or NO. 24. SOLNUM and UNSNUM: the size of the message table. 25. MFTIME: show a time stamp with each message. YES or NO. 26. MFOSNM: show originating system name with each message. YES or NO. 27. MFJOB: display the originating job name. YES or NO. 28. MFSNMJBX: did the user request to NOT show the originating job and system names of the message. YES means to NOT show the names; NO means to show them.


System Variables: SYSVAR notes

1. SYSVAR can only be used in TSO/E environments (i. e. not VM, OS/2, etc.). 2. SYSPROC has three different responses, depending on where it was called: o Foreground: returns the logon procedure name. o Batch: returns INIT because the job has an initiator. o Started Task: returns the name of the started task. 3. SYSPCMD and SYSSCMD are connected. For example, if youre running the TEST TSO command (debugging a program), SYSSCMD might return EQUATE (the last TEST command run) while SYSPCMD would return TEST. 4. MFSNMJBX is meant to override MFJOB and MFOSNM. It should not be consistent with them. 5. Some CLIST control variables do not apply to REXX. REXX has replacements for these variables. SYSDATE ===> DATE(U) SYSJDATE ===> DATE(J) SYSSDATE ===> DATE(O) SYSSTIME ===> SUBSTR(TIME(),1,5) SYSTIME ===> TIME() Examples: Am I running in the foreground? If SYSVAR(SYSENV) = FORE Am I in ISPF? If SYSVAR(SYSISPF) = NOT ACTIVE Then Exit Who is using the command? SYSVAR(SYSUID) List your JES node name. SYSVAR(SYSNODE) How large is my display screen? TL = Strip(SysVar(SYSLTERM)) TW = Strip(SysVar(SYSWTERM)) "Your LTERM displays" TL "lines of" TW "bytes each."


System Variables: MVSVAR

The second set of system variables are the MVS variables. SYSAPPCLU: the APPC/MVS logical unit (LU) name. SYSDFP: the level of MVS/Data Facility Product (MVS/DFP). SYSMVS: the level of the base control program (BCP) component of z/OS. SYSNAME: the name of the system your REXX exec is running on, as specified in the SYSNAME statement in SYS1.PARMLIB member IEASYSxx. SYSOPSYS: the z/OS name, version, release, modification level, and FMID. SYSSECLAB: the security label (SECLABEL) name of the TSO/E session. SYSSMFID: identification of the system on which System Management Facilities (SMF) is active. SYSSMS: indicator whether DFSMS/MVS is available to your REXX exec. SYSCLONE: MVS system symbol representing its system name. SYSPLEX: the MVS sysplex name as found in the COUPLExx or LOADxx member of SYS1.PARMLIB. SYMDEF: symbolic variables of your MVS system.

When using SYMDEF, you must supply both the word SYMDEF and the symbolic variable name. Variables must be defined in the SYS1.PARMLIB member IEASYMxx.

Exercise #4
Write an exec to print a calendar month, given the name of the month and the name of the first day of the month. For example, CALPRT JANUARY MONDAY.

Exercise #5
Write an exec to fill a 3X3 array with # by having the user enter a row name (letter) and column number. If the row and column are equal, fill the slot with a $ instead. Print the array at the end. The array should look like this: A B C 1 $ # # 2 # $ # 3 # # $


Other Performance Concerns

Here are some other performance tips. They are not in any particular order. Remember, you get more bang for your buck in a loop than in a single instruction. Dont sacrifice readability for speed unless you absolutely need to. o Shorter variable names are faster. Use abbreviated names if theyre clear enough. For example, Social_Security_Number is valid, but SSN is just as clear and more efficient. o Drop large stem arrays when youre done with them. o When known ahead of time, use literals or constants rather than variables. o Dont use external subroutines in loops. If a subroutine is necessary, code it in the exec. o Code subroutines as close to the call as possible (without sacrificing readability). Use SIGNAL to get around the code, if necessary. o When grouping subroutines, place the most often used routine first. o Consider a subroutine when a large number of statements are needed for something that is often NOT executed. o When calling subroutines or functions dont specify the defaults, and pass literals rather than variables when possible. o Avoid using functions within functions. For example, a) is more efficient than b) despite using more lines of code:

a)PT = right(strip(cmddata),3) b)PL = strip(cmddata); PT = right(PL,3)

o Avoid DATE and TIME functions in loops and subroutines if possible. o Instructions are about twice as fast as built-in functions. o Instructions are about 100 times as fast as external commands.


Putting It All Together

You can combine functions to manage records. Here are some examples. Suppose you want to scan records in JCL to remove a now-defunct symbolic parameter. For this example, the symbolic parameter to remove is CVOL. We dont care what the value given is, we just want to remove it. In EDIT you can do a FIND on CVOL, then delete characters up to the next comma. Heres a way to do it automatically in REXX.

1.MCVLOC = Pos("MODCVOL",SYMB) 2.LHLen = MCVLOC - 1 3.RHLen = 71 - MCVLOC 4.LHalf = Substr(Source.SOURCEI,1,LHLen) 5.RHalf = Substr(Source.SOURCEI,MCVLOC,RHLen)) 6.Parse Var RHalf MODCVOL "," RRHalf 7.If Right(Strip(Rhalf),1) = "," 8. Then SYMB = LHalf","RRHalf 9. Else SYMB = LHalf||RRHalf 10. FLen = Length(SYMB) 11. If Right(SYMB,2) = ",," 12. Then SYMB = Substr(SYMB,1,FLen-1)
1. Get the location of CVOL on the line. 2. The length of the line up to CVOL is the location of CVOL minus 1 3. The length of the line from CVOL to the end is 71 minus the location of CVOL because column 72 is the continuation column. 4. The left half of the line starts at Byte 1 and continues for the length as found in Step 2. 5. Start the right half of the line from CVOL through the end. 6. Break up the right half into the part containing CVOL and the rest by looking for the comma after the CVOL parameter. 7. If the last non-blank character in the right half is a comma: 8. Yes, it was, add the comma when concatenating the halves together; 9. No comma, concatenate without one. 10. Just in case CVOL was the last thing on the line, get the length of the entire line. 11. If CVOL was the last thing on the line, the added comma for the concatenation was wrong. Remove it by taking all the characters up to, but not including, the comma (see 12 also).


Combinations of Functions continued

Suppose you want neater JCL. Your idea is to force the EXEC or DD to always make room for an 8-byte step name or DD name, followed by one space, followed by either DD or EXEC. All other arguments follow the DD or EXEC after one space. 1. NLine = "//"Copies(" ",70) 2. Select 3. When WordPos("EXEC",Source.SOURCEI) = 0 Then Do 4. Parse Var Source.SOURCEI SNX EXECS PROCS 5. NLine = Overlay(SNX,NLine,1) 6. NLine = Overlay("EXEC",NLine,12) 7. NLine = Overlay(PROCS,NLine,17) 8. End 9. When WordPos("DD",Source.SOURCEI) = 0 Then Do 10. Parse Var Source.SOURCEI DDNX DDS DDARGS 11. NLine = Overlay(DDNX,NLine,1) 12. NLine = Overlay("DD",NLine,12) 13. NLine = Overlay(DDARGS,NLine,15) 14. End 15. End 16. Source.SourceI = NLine 1. 2. 3. 4. Create a default JCL line of // followed by 70 blanks. Start a Select group. If this is an EXEC statement (EXEC surrounded by blanks), do steps 4 7. Parse the line into //<stepname>, the word EXEC, and anything following EXEC. 5. Put the //<stepname> on the new line beginning in Column 1. 6. Put the word EXEC on the line in position 12. This leaves room for the // plus an 8-character step name. 7. Put the remainder of the EXEC line one space after the word EXEC. 8. End the EXEC group. 9. If this is a DD statement (DD surrounded by blanks), do steps 10 13. 10. Parse the LINE variable into the //<DD name>, the word DD, and anything following DD. DD name can be all blanks. 11. Put the //<DD name> on the new line beginning in Column 1. 12. Put the word DD on the line in position 12. This leaves room for the // plus an 8-character DD name. 13. Put the remainder of the DD line one space after the word DD. 14. End the DD group. 15. End the Select group. 16. Set the JCL line to its new, formatted version.


Combinations of Functions continued

At times, people doing data security need to scan their datasets to see what is and what is not protected, and what profiles are in WARNING mode (i.e. the profile exists but is not observed). Here is some code to do this check against any dataset level. Please note the code is split onto two pages, with the explanation on a third page.
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. /* REXX Checks datasets under LEVEL for RACF. */ Arg LEVEL GOODPROF = 0; WARNPROF = 0; NOPROF = 0; TotalDSN = 0 foo = outtrap(LCL.) "LISTC LEVEL("LEVEL")" foo = outtrap(Off) OutLine.1 = "The profile name, its UACC, and warning mode, is displayed." OutLine.2 = "'????' is displayed if you don't have access, to list the profile. OutLine.3 = "A warning message is displayed if there is no, profile." OutLine.4 = /* Skip 5 through 13 for final tallies, 14 is header. */ OutLine.14 = Copies(" ",79) OutLine.14 = Overlay("DSN",OutLine.14,1) OutLine.14 = Overlay("PROFILE",OutLine.14,36) OutLine.14 = Overlay("UACC",OutLine.14,64) OutLine.14 = Overlay("WARNING?",OutLine.14,72) OutIX = 14 Do L = 1 to LCL.0 Select When Word(LCL.L,1) = "NONVSAM" Then RADS = Word(LCL.L,3) When Word(LCL.L,1) = "CLUSTER" Then RADS = Word(LCL.L,3) When Word(LCL.L,1) = "AIX Then RADS = Word(LCL.L,3) Otherwise Iterate End foo = outtrap(RI.,'*') "LD DA('"RADS"') GENERIC ALL" If Word(RI.1,1) = "ICH35003I" Then Do /* No profile */ NOPROF = NOPROF + 1 OutIX = OutIX + 1 OutLine.OutIX = Dataset" RADS "not protected by RACF." Iterate L End Do I = 1 To RI.0 Parse Var RI.1 . . . PROFILE . Parse Var RI.5 . . UACC WARN . End If RI.0 = 1 Then Do PROFILE = "????"; WARN = "????"; UACC = "????" End


RIB exec Page 2

This section of the exec determines the profile type and if it is in warning. Finally, the exec creates an output report.
40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70. 71. 72. 73. 74. 75. 76. 77. 78. 79. 80. 81. 82. Select When WARN = "NO" Then Do W = "HARDCHK"; GOODPROF = GOODPROF + 1 End When WARN = "YES" Then Do W = "WARNING"; WARNPROF = WARNPROF + 1 End Otherwise W = "UNKNOWN" End OutIX = OutIX + 1 OutLine.OutIX = Copies(" ",79) OutLine.OutIX = Overlay(RACFDS,OutLine.OutIX,1) OutLine.OutIX = Overlay(PROFILE,OutLine.OutIX,36) OutLine.OutIX = Overlay(UACC,OutLine.OutIX,64) OutLine.OutIX = Overlay(W,OutLine.OutIX,72) End L /* Do group from Line 19 */ UNKNOWN = TotalDSN - GOODPROF - WARNPROF - NOPROF PROFILES = GOODPROF + WARNPROF OutLine.5 = "RACF report for dataset level" LEVEL":" OutLine.6 = "" OutLine.7 = "TOTAL DATASETS: ", Right(" "TotalDSN,6) OutLine.8 = "DATASETS WITH PROFILES: ", Right(" "PROFILES,6) OutLine.9 = "DATASETS IN WARNING: ", Right(" "WARNPROF,6) OutLine.10 = "PROTECTED DATASETS: ", Right(" "GOODPROF,6) OutLine.11 = "UNPROTECTED DATASETS: ", Right(" "NOPROF,6) OutLine.12 = "INSUFFICIENT AUTHORITY: ", Right(" "UNKNOWN,6) OutLine.0 = OutIX "ATTRIB FB80 RECFM(F B) LRECL(80)" OutDSN = LEVEL".DATA" "DELETE" OUTDSN "ALLOC DA("OUTDSN") F(OUTFILE) NEW SPACE(5 5) TRACKS, REUSE USING(FB80) RELEASE" "EXECIO " OutLine.0 " DISKW OUTFILE (STEM OutLine. FINIS" Say Say "Output written to" OutDSN"." FREE F(OUTFILE) ATTR(FB80) Exit


RIB exec Page 3

A brief explanation of some of the code follows. Line 3 initializes all counter fields for the output totals. Lines 4 6 capture LISTC LEVEL data to get the datasets to check. Lines 7 16 set the headers. Note several lines are left blank at this time. This makes room for the summary lines, whose values cannot be determined until the exec ends. The OVERLAY function lets me align the headers to specific columns. Finally, Line 17 points the output stem index to the first blank line after the dataset report header line. The select group at Line 19 keeps non-VSAM and VSAM dataset names, and skips all other lines from the LISTC. Line 26 does the RACF command LISTDSD to get the profile information. Lines 27 32 writes a note if there is no profile to list. Lines 33 36 pull the useful information from the profile. Lines 37 39 writes a note if you dont have the authority to list the profile. The select group at Line 40 notes whether the profile is in WARNING or not. Lines 49 54 format the report line for this dataset. Lines 56 72 format the summary section. This section uses the blank lines we left back on lines 7 16. The remaining lines allocate the output dataset and write the report stem to the output dataset.


This Page Intentionally Left Blank


Appendix A: A Few Simple Edit Macros

These are a few, simple edit macros to show how many macros are just a string of edit commands you might use frequently. Each is explained in some detail.

Initial Edit Macro

When starting any edit session, ISPF can run a macro on the dataset before turning control over to you. This macro is called an initial edit macro. You can specify an initial macro on the edit screen. If you want the macro to run every time, though, its just as easy to tell ISPF what it is. This is done in two easy steps. STEP 1: Code the macro. Here is an example. The code is COURIER; comments are made in Arial and underscored. Address ISREDIT "MACRO" "PROFILE UNLOCK" - unlock the edit profile of the current dataset. "BOUNDS" - Reset BOUNDS to the full LRECL. "NULLS ON" - Lets me insert (ignores spaces on end of lines). "HILITE ON" - Turns on highlighting CURMBR = "" "(CURMBR) = MEMBER" Checks if editing a PDS. If so, turn on STATS. If CURMBR <> "" Then "STATS ON" "RESET" - Clear all messages. Exit STEP 2: set the initial macro. You must use the VPUT Dialog Manager service to do this. So you dont have to remember (or know) how to do that, run this command: SETZIMAC <name of your macro> Your CLIST or REXX macro must be in the SYSPROC or SYSEXEC concatenation. If your macro is a program, it must be in the STEPLIB concatenation. Note: some ISPF dialogs run in their own application ID. Should you enter a dialog and your edit macro doesnt work, try running SETZIMAC again.


More Simple Edit Macros

These two edit macros change all upper-case characters to lower-case, or vice-versa. Note theyre just a single edit command. This could just as easily be set to a function key.

Address ISREDIT "MACRO" "C P'>' P'<' ALL" Exit Address ISREDIT "MACRO" "C P'<' P'>' ALL" Exit
This macro excludes all lines, then does a find on a passed string. The purpose is to see only what you want to see. Note the passed parameter may include quotes, column numbers, and other FIND operands (except ALL, of course). For sample calls, see below the macro.

Sample calls:


shows all occurrences of this TSO user ID. shows all EXEC lines in a JCL dataset. shows all DD statements not aligned at column 12.


Appendix B: Solutions to Exercises Exercise 1, page 6

Modify the exec you wrote in Exercise #28 to use a noun switch instead of expression tests when deciding what to do with the dataset. /* REXX passes args to LISTD with program stack. */ Say "Enter dataset name to pass to LISTD."; Pull DSN If Length(DSN) > 44 Then Do Say "DSN" DSN "longer than 44 bytes." Exit End If SYSDSN(DSN) = OK Then DSNExists = 1 Else DSNExists = 0 If DSNExists Then Do Queue DSN DUMMY = Prompt("ON") "LISTD" End Else Say DSN "does not exist."


Exercise 2, Page 7
Modify the exec you wrote in Exercise #16 from the first course to accept the unit and amount values as keywords. Only accept two arguments. If one argument is bad, display the syntax and exit.

/* REXX Metric Converter */ Arg Parm.1 Parm.2 Do I = 1 to 2 Select When Left(Parm.I,1) = "U" Then Parse Var Parm.I Junk "(" UNIT ")" When DataType(Parm.I) = "NUM" Then AMT = Parm.I Otherwise Do Say "Invalid parm entered: " Parm.I"." Say "SYNTAX : CNVT UNIT(...) and a number." Say "UNIT must be one of liter, quart, mile, or", "kilometer." Exit End End End Select When UNIT = "LITER" Then Say AMT UNIT "is" When UNIT = "QUART" Then Say AMT UNIT "is" When UNIT = "MILE" Then Say AMT UNIT "is" When UNIT = "KILOMETER" Then Say AMT UNIT "is" End

AMT*1.057 "quart." AMT*.946 "liter." AMT*1.6 "kilometer." AMT*.625 "mile."


Exercise 3, Page 11
Write an exec to parse the date into month, day, and year. Display them in the following formats: MM/DD/YY, DD/MM/YY, YY/DD/MM, and YY/MM/DD. The command to get the USA-style date (MM/DD/YY) is DATE(U).

/* REXX Date formatter */ Parse Value Date("U") With Month "/" Day "/" Year "CLRSCRN" Say "Today's date is: " Date("U")", or" Say "USA: " Month"."Day"."Year";" Say "European: " Day"."Month"."Year";" Say "Year, then day: " Year"."Day"."Month";" Say "Ordered: " Year"."Month"."Day"."


Exercise 4, Page 19
Write an exec to print a calendar month, given the name of the month and the name of the first day of the month. Assume the first day of the week is Sunday. For example, CALPRT JANUARY MONDAY should start the calendar on Monday, January 1. Use the CENTER function to display the month name. /* REXX print a calendar month. */ Arg Month Day /*-------------------------------* * Init month table with blanks. * *-------------------------------*/ Do I = 1 to 6 Do J = 1 to 7 Week.I.J = " " End J End I /*------------------------------------------* * Init fields: * * WeekDay is the day of the week; * * DayNumber is the day of the month; * *------------------------------------------*/ WeekDay = 1; DayNumber = 1 /*---------------------------------* * Set the first day of the month. * *---------------------------------*/ Select When Day = "SUNDAY" Then WeekDay When Day = "MONDAY" Then WeekDay When Day = "TUESDAY" Then WeekDay When Day = "WEDNESDAY" Then WeekDay When Day = "THURSDAY" Then WeekDay When Day = "FRIDAY" Then WeekDay When Day = "SATURDAY" Then WeekDay Otherwise Do Say "Invalid day" Day"." Exit End End

= = = = = = =

1 2 3 4 5 6 7

Continued on the next page


Exercise 4, Page 19, answer continued

/*---------------------------------------* * Load the table. Start at the current * * weekday, then reset it after Week 1. * * Skip out when at end of month. * *---------------------------------------*/ Do WeekNumber = 1 to 6 Do WeekDay = WeekDay to 7 If Length(DayNumber) = 1 Then DayWord = " "DayNumber Else DayWord = DayNumber Week.WeekNumber.WeekDay = DayWord DayNumber = DayNumber + 1 If DayNumber > DayLimit Then Signal EndOfMonth End WeekDay = 1 End EndOfMonth: "CLRSCRN" Say Say Center(Month,21) Say " S M T W T F S" Do WeekNumber = 1 to 6 Say " "Week.WeekNumber.1 Week.WeekNumber.2, Week.WeekNumber.3 Week.WeekNumber.4 Week.WeekNumber.5, Week.WeekNumber.6 Week.WeekNumber.7 End


Exercise 5, Page 19
Write an exec to fill a 3X3 array with # by having the user enter a row name (letter) and column number. If the row and column are equal, fill the slot with a $ instead. Print the array at the end. The array should look like this: A B C 1 $ # # 2 # $ # 3 # # $

HINT: you will need the TRANSLATE function to do this exec cleanly. Guidance is provided if desired. /* REXX advanced course, exercise 6 */ Do I = 1 to 3 Select When I = 1 Then Row = "A" When I = 2 Then Row = "B" When I = 3 Then Row = "C" End Do Col = 1 to 3 Array.Row.Col = " " End Col End I /*------------------------------------* * SLOTS is the number of full slots; * * ArrayFull says the array is full. * *------------------------------------*/ Slots = 0; ArrayFull = 0

Continued on the next page


Exercise 5, Page 19 continued

/*---------------------------* * Loop until array is full. * *---------------------------*/ Do Until ArrayFull Say "Enter a row (A, B, or C) and a column (1, 2, or 3)." Pull Row Col Say If Pos(Row,"ABC") = 0 Then Do Say "Row" Row "invalid, try again." Iterate End If Pos(Col,"123") = 0 Then Do Say "Column" Col "invalid, try again." Iterate End If Array.Row.Col = " " Then Do Say Row "and" Col "already used, try again." Iterate End RowNum = Translate(Row,"123","ABC") If RowNum = Col Then Array.Row.Col = "$" Else Array.Row.Col = "#" Slots = Slots + 1 If Slots = 9 Then ArrayFull = 1 End /*--------------------* * Display the array. * *--------------------*/ Row = "A" Say "A" Array.Row.1 Array.Row.2 Array.Row.3 Row = "B" Say "B" Array.Row.1 Array.Row.2 Array.Row.3 Row = "C" Say "C" Array.Row.1 Array.Row.2 Array.Row.3