A Beginner’s Guide to the REXX Programming Language on z/OS
How to read and write files in the REXX programming language on z/OS.
In this series of articles, I’ll provide a beginner’s guide to the REXX* programming language on z/OS*. Let’s head straight into reading and writing files.
Getting Started
Reading z/OS data sets in REXX requires two steps. First, allocate the data set—this is a Time Sharing Option (TSO) command. Second, process the records in the data set—this is implemented as a TSO REXX command. The commands are, in sequence:
“alloc shr file(input) dataset(‘MY.MAINFRAME.
DATASET’)”
“execio * diskr input (stem input. finis)”
The first command uses the syntax of the TSO ALLOCATE command. Under TSO, try the command HELP ALLOCATE SYNTAX for a quick overview of the command parameters.
The second command is a little trickier. See Code Sample 1 for a quick description of the full command syntax I’ve used here. For any REXX-platform-specific implementation, there’s a dedicated I/O processing capability. Under TSO, this is implemented with the “execio” command. There are many variations on this command, some more of which I’ll explore in future articles. In this case, you’ve read the entire file into a “buffer” in one pass. The buffer is technically referred to as a “stem variable,” and I’ll use this term from now on. The stem variable specified in the above execio statement is named “input.” The trailing dot character, while not essential, is highly recommended. Process the contents of the stem variable separately. Close the file automatically after reading it so, if desired, you can now issue the TSO FREE command like this:
“free file(input)”
Since you read the entire file into a buffer, you now need some way of processing each record in sequence from the stem variable. To do this, take advantage of the fact that reading the file in this manner has automatically updated a special stem variable with the number of records read. This is the stem “zero” variable and is addressed as “stem.0” in REXX. The value of the input.0 variable is used in this case to determine the upper limit for a “do while” loop to process all records in the stem variable. Records in the stem variable are addressed by record number using the syntax stem.record_num, or input.count in the code below.
To bring these code snippets together into a functional program, you need a little bit of glue around them. I’ve added a simple loop that demonstrates processing each record from the stem variable in sequence:
/* REXX */
address TSO
alloc shr file(input) dataset(‘MY.MAINFRAME.
DATASET’)
"execio * diskr input (stem input. finis)"
"free file(input)"
count = 1
do while count <= input.0
say input.count
count = count + 1
end
exit
By “glue,” I mean the first two lines of code in the sample above. Any z/OS REXX exec must have the word REXX in a comment block as its first line. This is reminiscent of the UNIX* scripting commands language convention where the first line of a script describes the location of the program to be used to execute/interpret the script.
The second line tells the REXX interpreter to pass any commands that aren’t valid REXX programming statements to the TSO processor for action. In the example, this means the following three double-quoted commands aren’t processed by REXX, but by TSO. The REXX address statement is useful when working in z/OS, as it lets you prepare commands for various z/OS subsystems and pass them to those subsystems for processing.
Making it Friendly
This simple example program needs a few enhancements, primarily in the area of return code checking. Code Sample 2 adds these tests. This kind of checking should be performed in all REXX programs you create to assist in problem diagnosis and to generally make your code more robust and reliable for future programmers to maintain. I’ve also added some REXX comments to illustrate these points.
Now you have a program that reads a data set and writes the output to a screen—not much use. What might be useful is if it could be used to write only certain records from the input data set to the screen, filtering the records in a manner similar to the UNIX program “grep.”
To achieve this, make two changes: Pass the program a string, just like grep accepts, and search each line in the input data set for the value of the string, reporting only those lines containing the target string. I’ll address the second of these requirements first.
Making it Useful
This section introduces two of my favorite REXX language constructs, the “parse” and “interpret” commands. When used properly, these commands take this relatively sparse programming language and introduce a world of power and flexibility to your code.
You can use parse to process each line of the input file and conditionally only write the line to the screen if your search string is found. For testing purposes, start by updating the loop with a hard-coded reference to your search string “XXXX” as follows:
count = 1 /* initialize record counter */
do while count <= input.0 /* loop through records */
after = ‘ ‘
parse UPPER var input.count . ‘XXXX’ after
if after ^= ‘ ‘ then,
say input.count /* list the record */
count = count + 1 /* increment the record counter*/
end /* end of loop */
This helps test the parse command. Now, insert the search string as a variable instead of hard coded in the parse command. Use the interpret command, replacing the parse command with the following two lines:
cmd = "parse UPPER var input.count before ‘"search"’
after"
interpret cmd
The first of these commands actually builds the REXX parse command you want to use. This is necessary, because the search string is a variable—you don’t know its value when you code the parse command, but it must be coded in the parse command as a quoted literal for parse to split the line on. This technique allows you to have a variable—“cmd” in the example—that’s inserted into a command and then used as a literal when the command executes. The second command, “interpret cmd,” executes the parse command you just built. This parse command is functionally equivalent to the first parse command tested, except that it uses the variable “search” in place of the hard-coded sample where you used “XXXX.”
Now, add a line of code to tell the REXX program to expect and process a variable. Use a form of the parse command for this again, adding the following first line to the exec:
/* REXX this is a REXX program */
parse UPPER arg search
You should now be able to execute your REXX program with the following TSO command:
TSO EXEC ‘your.rexx.exec(GREP)’ ‘search-string’
Existing Problems
Keen testers of this program should notice a couple of problems. The program doesn’t return lines where the search string is the only data in the line. Despite crafting a solution using parse and interpret instructions, a much more efficient and effective method is to use one of the many string searching functions available in REXX, such as:
upper test
if index(test,search) > 0 then,
say input.count /* list the record */
A major limitation remains: The data set being searched is still hard coded. In the next issue, I’ll address this and several other enhancements.
A word of warning: When you cut and paste this program into a 3270 emulator, watch out for code page character translation issues. The caret character “05”x is used in REXX as a logical “not” operator; you can use / or for this too. In this code I used “not equal to,” i.e. “^=” several times—this character is often translated incorrectly, causing an obvious syntax error. You can correct this using the hex editor.