Skip to main content

More REXX and TSO functions

In this primer in the REXX programming language you’ll get back to actually writing code, rather than dealing with environmental requirements.

In the final primer in the REXX programming language you’ll get back to actually writing code, rather than dealing with environmental requirements, and add some logic to the REXX exec you’ve been working on.

It would be useful if the grep program could tell the difference between a Sequential or Partitioned data set, and act appropriately. So, for a Partitioned data set, it would make sense to have the REXX ask for the member name to search, perhaps even support a wildcard for member name matches. For Sequential data sets the behavior would essentially remain unchanged.

How to Discover Data Set Types

There is more than one method to determine the type of data set you’re searching. In this example, you’ll first use the REXX Time Sharing Option (TSO) external function LISTDSI to determine the type, and then use the TSO command DSLIST to retrieve the list of members, should the dataset turn out to be Partitioned Organization (PO).

Of course, it’s possible the user has entered a member name for you to parse in error, and the data set is really Physical Sequential (PS). To test for this, the first few lines of code then become:

/* REXX this is a REXX program */
parse UPPER arg dsn search
member = ''
parse var dsn dataset '(' member ')'
dataset = strip(dataset,'B',"'")

You now have two variables with which to work, data set and member. The following code would be inserted after the ADDRESS TSO, and prior to allocating the input data set:

cmdrc = LISTDSI(“’”dataset”’” DIRECTORY)
say cmdrc
say SYSDSORG
say SYSMEMBERS

Note the use of single quotes, surrounded by double quotes, in order to generate a fully qualified data set name for the LISTDSI function.

The variable ‘x’ contains the LISTDSI return code, and of course this should be checked for validity prior to any further processing.

The SYSDSORG and SYSMEMBERS are amongst the many variables populated by the call to LISTDSI. If the data set was sequential, then SYSMEMBERS is zero; otherwise, it contains a count of the members in the partitioned data set (PDS). In our scenario SYSDSORG is expected to contain either PS or PO, although other values are available.

Now you know what you’re dealing with, you can add some logic to the code for each scenario. In the case of a PDS, it would be nice to ask the user what he or she wanted to do if a member name or mask wasn’t already specified. You have to deal with member or member mask being both specified on the command line call to grep, and those unspecified until the user is prompted.

if SYSMEMBERS Â= 0 & member = '' then do /* PDS and no member passed */
say 'You have entered a PDS and no member name - would you like to:'
say '1) process all members - just hit enter'
say '2) process a member - please enter member name'
say '3) process a range of like named members - please enter a mask',
'followed by an asterisk such as: MEM*'
parse UPPER PULL member /* at this point we have a */
end /* member/mask or a blank */

Note the six embedded white-space characters in option three. These help the message display neatly on an 80-column screen.

If the DSORG is partitioned, you’ll need to parse the output of the LISTDS. The output from a LISTDS command looks something like this:

IBMUSER.TEST.REXX
--RECFM-LRECL-BLKSIZE-DSORG
FB 80 27920 PO
--VOLUMES--
TIVOLI
--MEMBERS--
MEM1
MEM2
MEMBER3
MEMBER4

You can use the following code to retrieve the list of members from the output above:

See Code Sample 1, below:

if SYSDSORG = 'PO' then do                                              
   x = outtrap(trap.)                                                   
   count = 0                                                            
   memlist. = ''                                                        
   cmd="LISTDS '"dataset"' MEMBERS"       /* need to get list of mems */
   cmd                                                                  
   i = 1                                                                
   do while i <= trap.0                                                 
      if index(trap.i,'--MEMBERS--') > 0 then do                        
         i = i + 1                        /* next record              */
         count = count + 1                /* increment member count   */
         do while i <= trap.0                                           
            memlist.count = trap.i                                      
            say memlist.count                                           
            count = count + 1                                           
            i = i + 1                                                   
         end                                                            
         memlist.0 = count - 1                                          
      end                                                               
      i = i + 1                           /* next record              */
   end                                                                  
end   /* now we have a list of all members - need to decide what to do*/

This code reads the output, trapped in the variable ‘trap.i’, searching for the string ‘—MEMBERS—’ Once found, it starts keeping a list of the members until the end of the output is reached.

Now you have to deal with four potential cases. REXX has a ‘case’ statement just perfect for this, the ‘select/when’ construct. The cases are:

DSORG = PS – no members present
Member is a mask
Member is blank (all members)
Member is fully specified.

The tests for each of these cases can be accomplished with the following code.

select
when SYSDSORG = 'PS' then do
some code
end
when index(member,'*') > 0 then do
some code
end
when member = '' then do
some code
end
otherwise do
some code
end
end /* of select */

You now need to decide the actions for each test. Before doing that, it’ll be useful to turn our already developed search process into a REXX function. Encapsulate the previously written search code with this snippet:

exit /* the main routine */
search_dsn: procedure expose search
parse arg dsn member
and
return /* the REXX function search_dsn */


The code to execute for cases where the member name is fully specified or the dataset is not partitioned is fairly straightforward:

call search_dsn dataset
and
call search_dsn dataset member


Where the member name is blank, this will suffice:

i = 1
do while i <= memlist.0
call search_dsn dataset memlist.i
i = i + 1
end
if substr(member,1,mask) = substr(memlist.i,1,mask) then,

With a few further minor modifications, including tidying up comments and formatting, your entire program now looks like this:

See Code Sample 2, below:

/* REXX                                 this is a REXX program      */  
parse UPPER arg dsn search                                              
member = ''                          /* initialise member name      */  
parse var dsn dataset '(' member ')' /* was a member specified?     */  
dataset = strip(dataset,'B',"'")     /* if so, remove blanks        */  
address TSO                          /* send cmds to TSO by default */  
cmdrc = LISTDSI("'"dataset"'" DIRECTORY) /* is this a PDS?          */  
if cmdrc ¬= 0 then do                /* check the return code       */  
   say 'bad RC = 'cmdrc' from LISTDSI cmd' /* error message         */  
   exit                              /* go home quickly             */  
end                                                                     
if SYSMEMBERS ¬= 0 & member = '' then do /* PDS and no member name  */  
   say 'You have entered a PDS and no member name - would you like to:' 
   say '1) process all members - just hit enter'                        
   say '2) process a member - please enter member name'                 
   say '3) process a range of like named members - please enter a mask',
           'followed by an      asterisk such as: MEM*'                 
   parse UPPER PULL member           /* at this point we have a     */  
end                                  /* member/mask or a blank      */  
if SYSDSORG = 'PO' & ((index(member,'*') > 0) | member = '') then do   
   x = outtrap(trap.)                /* grab TSO command output     */ 
   count = 0                         /* initialise output counter   */ 
   memlist. = ''                     /* initialise var for members  */ 

It’s likely there are still a few bugs to be ironed out in this program. For instance, I haven’t verified it works if the members being searched are either the first or last instances in the PDS member list–this is known in the programming trade as ‘boundary conditions,’ and is often a cause of basic bugs when not tested thoroughly during development.

Also, the code itself could be much more efficient. If you test and interrogate PDS members and status, regardless of initial identification of the dataset type, this extra processing could be avoided. You could likely reduce processing time by more refined use of procedure calls for member level processing; in some cases you don’t really need to process or store a list of all members. I’ll leave it to you to further fine tune this example program, and hope you all enjoyed watching its development.

This will be my last contribution as your technical editor and Tips and Techniques columnist. I have to thank all my readers for the wonderful feedback I have received during the past three years, and look forward to continuing to write technical articles for mainframe professionals in other journals. 


Key Enterprises LLC is committed to ensuring digital accessibility for techchannel.com for people with disabilities. We are continually improving the user experience for everyone, and applying the relevant accessibility standards.