Using ISPF edit macros to displaying the junk in a catalog

You can use IDCAMS DCOLLECT to collect SMS information about data sets on your z/OS system. This gives lots of information about a dataset, size, creation date, SMS attributes etc.

With processing you can get reports on dataset, volumes, and what is using all the space. This allows you to delete dataset which are no longer needed.

This does not help when you are trying to clean out your catalogs, and removing stuff which should not be in that catalog. For example there are usually entries in a catalog which should really be in user catalogs.

I could not find tools to help me with this. I fell back to using and ISPF edit macro to process a LISTCAT listing and extracting relevant data. It is not difficult (once you know) and it is quick and easy.

This blog post gives some examples of how you can use ISPF edit macros to process data in data sets or spool.

The output from the short Rexx exec is

TCPIP.ETC.SERVICES             1998.284 B3SYS1
SYS1.RACFDS 1999.288 B3CFG1
SYS1.IPLPARM 1999.288 B3SYS1
...
LOG.MISC 2025.107 USER04
IBMUSER.S0W1.SPFTEMP3.CNTL 2026.002 USER07
IBMUSER.S0W1.SPFLOG1.LIST 2026.013 USER04
IBMUSER.SMF 2026.013 USER07

With this I asked What is LOG.MISC 2025.107 doing in the catalog? It is there because I did not have the controls in place to stop people putting datasets into the catalog.

Instead of just displaying the information, I could have had the exec create IDCAMS statements, for example to get it recataloged, or deleted; based on creating date or other information.

Get your LISTCAT listing

I used

//IBMLISC JOB 1,MSGCLASS=H 
// EXPORT SYMLIST=(*)
// SET CAT=&SYSVER.
//S1 EXEC PGM=IDCAMS
//SYSPRINT DD SYSOUT=*
//SYSIN DD *,SYMBOLS=JCLONLY
LISTCAT NONVSAM CATALOG(CATALOG.&CAT..MASTER) ALL
/*
  • The // SET CAT=&SYSVER. gets a local copy of the system symbol &SYSVER. You can use the operator command D SYMBOLS to list all the system symbols defined. On my system &SYSVER is Z31B
  • In //SYSIN DD *,SYMBOLS=JCLONLY the SYMBOLS=JCLONLY says substitute variables in the following SYSIN data, and substitute from the JCL symbols. &CAT is Z31B, and so the catalog name becomes CATALOG.Z31B.MASTER. You cannot use &SYMVER directly in the SYSIN data.

Edit the listing

I used SDSF and the SE line command on the output of the LISTCAT. You get an ISPF edit session with the spool data.

Run the exec

I have a Rexx exec called LISTCATN in USER.Z31B.CLIST. I’ll describe it in sections below

Standard Rexx starting code

/* REXX */ 
/*
exec to Nonvsam records from a catalog listing
*/
ADDRESS ISPEXEC
'ISREDIT MACRO (parms) '

Use MACRO(parms) to get the parameters passed to the macro

Define parsing arguments

I define search arguments in a variable stem. This separates the data from the logic, and makes it easy to change or extend.

  data.1 = "NONVSAM 2 10" 
fcol.1 = 18
flen.1 = 48

data.2 = "CREATION 38 48 "
fcol.2 = 54
flen.2 = 8

data.3 = "VOLSER 8 15 "
fcol.3 = 27
flen.3 = 8

data.0 = 3
sortcols = "50 60"

Later there is code

  • do I = 1 to data.0 this processes each section in the stems
  • There a find data.1 which substitutes to “find NONVSAM 2 10”. This says Find the string NONVSAM in columns 2 to 10
  • If the find locates the string, the code retrieves the line. The code does a substring from fcol.1 for length flen.1 and saves the value
  • data.0 = 3 says there are three data sections.
  • sortcols = “50 60” is used at the end sort the file by the date column.

Remove uninteresting records

  "ISREDIT autosave off     " 
"ISREDIT exclude all"
"ISREDIT find NONVSAM 2 10 ALL "
"ISREDIT find CREATION ALL "
"ISREDIT find VOLSER ALL "
if rc != 0 then data.0 = 2 /* ignore the volser */
"ISREDIT delete all x "
  • “ISREDIT autosave off ” I have this as standard in ISPF edit macros, basically it says do not save the data if I press PF3.
  • “ISREDIT exclude all” –
  • “ISREDIT find NONVSAM 2 10 ALL ” find these lines
  • “ISREDIT find CREATION ALL ”
  • “ISREDIT find VOLSER ALL ”
  • If volser was not found, then listcat wasn’t specified with the right statement, so do not try to process any VOLSER records
    • if rc != 0 then data.0 = 2 /* ignore the volser */
  • “ISREDIT delete all x ” delete all the records which are still excluded leaving only the records I searched for.

Process the records

do j = 1  by 1 
string = ""
do i = 1 to data.0
"ISREDIT find "data.i
if rc <> 0 then leave
"ISREDIT (f) = LINENUM .ZCSR "
"ISREDIT (d) = LINE " f
name = substr(d,fcol.i,flen.i) /* from col and length */
string = string || " " || name
end

if rc <> 0 then leave
out.j = string
end

This code uses the data in the variable stems defined higher up. It keeps the logic separate from the search data.

  • do j = 1 by 1 iterate through the whole file until the end of file
  • string = “” preset the output string
  • do i = 1 to data.0 for the records we specified
  • “ISREDIT find “data.i find it
  • if rc <> 0 then leave if not found then leave
  • “ISREDIT (f) = LINENUM .ZCSR ” Get the line number where the find found the data.
  • “ISREDIT ( d ) = LINE ” f get the line contents – getting the line number found in the previous step
  • name = substr(d,fcol.i,flen.i) /* from col and length */ extract the field of interest from the line
  • string = string || ” ” || name build up a string of the values found
  • end
  • if rc <> 0 then leave we got a not found, so end of file,
  • out.j = string save the data in a stem for processing below
  • end

Do something with the records

You can do processing on the data, for example create JCL to delete the dataset.

In this example I delete all records from the file, and insert the saved records

  "ISREDIT exclude all" 
"ISREDIT delete all x "
do i = 1 to j -1
v = out.i
"ISREDIT LINE_after .zcsr = (v)"
end
"ISREDIT sort " sortcols
exit
  • “ISREDIT delete all “ delete all processed the lines in the file
  • do i = 1 to j -1 we have a stem of the records we processed iterate over them
  • v = out.i make a copy of the data, make it easy for ISPF. ISPF only does simple substitutions
  • “ISREDIT LINE_after .zcsr = (v)” insert after the current (last) line the value from v, which is the saved string.
  • end
  • “ISREDIT sort ” sortcols sort on the creation date
  • exit

The output

The output from this is the dataset name, the create date, and the volume it is on.

TCPIP.ETC.SERVICES             1998.284 B3SYS1
SYS1.RACFDS 1999.288 B3CFG1
SYS1.IPLPARM 1999.288 B3SYS1
...
IBMUSER.S0W1.SPFTEMP3.CNTL 2026.002 USER07
IBMUSER.S0W1.SPFLOG1.LIST 2026.013 USER04
IBMUSER.SMF 2026.013 USER07

From the data information I can see which entries were due to me – because they were all after the Jan 2025.

Different ways of processing records

Not every dataset has the same information. For example, deleting uninteresting rows

NONVSAM ------- ADCD.DYNISPF.ISPPLIB 
DATASET-OWNER-----(NULL) CREATION--------2016.236
VOLSER------------B3SYS1 DEVTYPE------X'3010200F' FSEQN------------------0
NONVSAM ------- ADCD.WLM
DATASET-OWNER-----(NULL) CREATION--------2023.010
STORAGECLASS -----SCBASE MANAGEMENTCLASS---(NULL)
DATACLASS --------(NULL) LBACKUP ---0000.000.0000
VOLSER------------B3USR1 DEVTYPE------X'3010200F' FSEQN------------------0

The second dataset ADCD.WLM has SMS information, Storage Class, Management Class, and Data Class, which are not present with the first dataset ADCD.DYNISPF.ISPPLIB.

You could process this sequentially and have logic like…

If the row starts with

  • NONVSAM – then write out the previous information, get the dataset name, and start again
  • VOLSER – then parse the volser value
  • DATASET – then parse the creation date
  • STORAGECLASS – then parse the SC and MC values
  • DATACLASS – then parse the DC value

For example

"ISREDIT     (last)  = LINENUM .ZLAST" 
do j = 1  by 1  to last 
  "ISREDIT      ( d )  = LINE   " j 
  if substr(d,2,7) = "NONVSAM" then 
  do 
      count = count + 1 
      string =  dsn cd vol sc mc dc 
      sc = "        " 
      mc = "        " 
      dc = "        " 
      vol= "      " 
      dsn= "      " 
      cd = "      " 
      out.count = string 
      say string 
      /* do the next */ 
      dsn = substr(d,18,48) 
  end 
  else 
  if substr(d,9,7) = "DATASET" then cd = substr(d,54,8) 
  else 
  if substr(d,9,6) = "VOLSER" then vol = substr(d,27,6) 
  else 
  if substr(d,9,6) = "STORAG" then 
  do 
     sc = substr(d,27,8) 
     mc = substr(d,56,8) 
  end 
  else 
  if substr(d,9,6) = "DATACL" then vol = substr(d,27,8) 
end 

This gives output like

NFS.CNTL                       2000.336 B3SYS1 
SYS1.RACFDS.BACKUP 2001.164 B3CFG1
SYS1.UADS 2003.137 B3CFG1
NETVIEW.ADCD.NTVTABS 2009.027 B3USR1 SCBASE (NULL)
SYT1.ZOS.CNTL 2012.013 B3USR1 SCBASE (NULL)
TCPIP.PROFILE.TCPIP 2016.236 B3SYS1

So not difficult at all.

ISRDDN – That’s wrong – no, that’s new!

I’ve just started using a new system, and I’ve been looking around.

I used ISRDDN to display the allocations for ISPF. It gives

if I put B in front of USER.CLIST I get

When I browse the top member, I get member data set SYS1.DGTCLIB(ACBQVAO4). This is not in USER.CLIST. What is going on ?

If you look at the output of USER.CLIST is has Lib:8. This means the 8th library in the list of SYSPROC.

Member ADDVOL is in library 1 which really is USER.CLIST

if you browse any other data set under the SYSPROC it has

without the Lib.

Now I understand what is happening, I think it is pretty nifty!

Wow3 ISPF cut and paste can do so much more

You can use the ISPF cut command

EDIT       COLIN.PDSE2(AA) - 01.07
Command ===> cut
****** ********************************* Top of D
000100 ddd
000200 bbbb
cc0300 44444
000400 5555
cc0500 666
000600 777

and this copies the lines into a working area, then use the paste command to insert the text, in the same or a different file.

What is in the clipboard?

The command cut display gave me

┌────────────────────────────────────────────────────────────────┐
│ Clipboard manager │
│ │
│ B - Browse C - Clear O - Toggle Read-only │
│ E - Edit R - Rename D - Delete │
│ │
│ Name Lines User Comment │
│ │
│ _ DEFAULT 3 ISPF Default Clipboard │
│ │

You can now enter B into the line command before DEFAULT to display the contents. This gave me

 BROWSE    CLIPBOARD:DEFAULT
Command ===>
********************************
44444
5555
666

Multiple clipboards

The command cut AAAA followed by CUT DISPLAY showed all the clipboards I have. This shows the clip board AAA I just created

 ┌────────────────────────────────────────────────────────────────┐
│ Clipboard manager │
│ │
│ B - Browse C - Clear O - Toggle Read-only │
│ E - Edit R - Rename D - Delete │
│ │
│ Name Lines User Comment │
│ │
│ _ DEFAULT 3 ISPF Default Clipboard │
│ _ AAA 2 │
│ │

You can have up to 11 clip boards.

Other clever things

  • You can append or replace the data.
  • You can have the data converted to ASCII, EBCDIC or UTF8 as part of the copy.
  • You can select eXcluded or Not eXcluded (x or NX) lines.

Paste

You can use the PASTE , or the PASTE AA command to put the value from the specified (or defaulted) clipboard into your data.

You could use

paste AA After .zlast

to paste the data after the end of the file.

Wow I can have member generations

We have had the capability of having multiple generations of data sets on z/OS for years.
For example with three generations you can have

  • the current data set
  • the one before that
  • and the one before that.

If you create a new data set the oldest gets deleted, and they all move along one.

This has been around for years.
What I found recently was you can have this with members within a V2 PDSE (not a PDS) since 2015.

System wide set up

When you create the data set, the number of generations is limited by MAXGENS_LIMIT in the IGDSMSxx member of PARMLIB.

Use the command

D SMS,OPTIONS

This displays information like

ACDS     = SYS1.S0W1.ACDS               
COMMDS = SYS1.S0W1.COMMDS
ACDS LEVEL = z/OS V3.1
SMS PARMLIB MEMBER NAME = IGDSMS00
...
HONOR_DSNTYPE_PDSE = NO PDSE_VERSION = 2
USER_ACSVAR = ( , , ) BYPASS_CLUSTER_PREFERENCING = NO
USE_MOST_FREE_SPACE = NO MAXGENS_LIMIT = 0

To change the value of MAXGENS_LIMIT you need to change the parmlib member and use T SMS=nn (or just wait till the next IPL).

I used Which parmlib/proclib library has my member? to find the member and added

 MAXGENS_LIMIT(3)        

I then used

set sms=00

to activate it

Using the support

For example to allocate a dataset to support this.

Example JCL

//SAM00001 DD DISP=(NEW,CATLG),DSN=IBMUSER.TEST1.PDSE00,
// DSNTYPE=(LIBRARY,2),LRECL=80,BLKSIZE=8000,RECFM=FB,
// MAXGENS=3

Where

  • dsntype=(LIBRARY,2) says this a LIBRARY ( also known as PDSE) type 2
  • MAXGENS=3 this will support up to 3 generation

Using ISPF

This works in z/OS 3.1, I do not know if earlier releases have the ISPF support.

I used ISPF 3.2 and specified

──────────────────────────────────────────────────────────────────────────────
Allocate New Data Set
Command ===>

Data Set Name . . . : COLIN.PDSE2

Management class . . . (Blank for default management class)
...
Data set name type LIBRARY (LIBRARY, PDS, LARGE, BASIC, *
EXTREQ, EXTPREF or blank)
Data set version . : 2
Num of generations : 3
Extended Attributes (NO, OPT or blank)
Expiration date . . . (YY/MM/DD, YYYY/MM/DD
YY.DDD, YYYY.DDD in Julian form
DDDD for retention period in days
or blank)

I then edited a member, saved it, and then reedited it several times.

ISPF 3;4 member list gave

DSLIST            COLIN.PDSE2                           Row 0000001 of 0000001
Command ===> Scroll ===> CSR
Name Prompt Size Created Changed ID
_________ AA 2 2025/11/09 2025/11/09 09:22:35 COLIN

Using the line command b to browse the data set showed the latest content.

Using the line command N gave me

GENLIST           (AA)COLIN.PDSE2                       Row 0000001 of 0000004
Command ===> Scroll ===> CSR
RGEN Prompt Size Created Changed ID
_ 00000000 5 2025/11/09 2025/11/09 09:31:44 COLIN
_ 00000001 4 2025/11/09 2025/11/09 09:31:32 COLIN
_ 00000002 3 2025/11/09 2025/11/09 09:31:17 COLIN
_ 00000003 2 2025/11/09 2025/11/09 09:22:35 COLIN

There is

  • (AA)COLIN.PDSE2 showing member AA of data set ( library) COLIN.PDSE2
  • RGEN showing the generations
  • Generation 3 is the oldest

In the line command you can type / which lists all of the valid commands

           Action for Generation 0              

Generation Action
1. Edit
2. View
3. Browse
4. Delete
5. Info
6. Print

Prompt Action . . (For prompt field)

Select a choice and press ENTER to continue

Info gave me

   Menu  Functions  Confirm  Utilities  Help  
─────────────────────────────────────────────
EDIT USER.Z31B.PARMLIB
. . . . . . . . . . . . . . .
Member Informat
Command ===>

Data Set Name . . . : COLIN.PDSE2

General Data
Member name . . . . . . . . : AA
Concatenation number . . . . : 1
Version . Modification . . . : 01.07
...

Non-current Generations
Maximum . . . . : 3
Saved . . . . . : 3
Newest Absolute : 7
Oldest Absolute : 5

See Version and modification level numbers. You can use the commands

To set these values

Deleting a member

Using the D line command against the oldest member gave the prompt

           Confirm Member Generation Delete          

Data Set Name:
COLIN.PDSE2

Member Name:
AA

Generation to be Deleted:
-3

__Set member generation delete confirmation off

Only the specified generation will be deleted.

Press ENTER to confirm delete.
Press CANCEL or EXIT to cancel delete.

Editing a member

When you edit a member the screen is like

   File  Edit  Edit_Settings  Menu  Utilities  Compilers  Test  Help
────────────────────────────────────────────────────────────────────
EDIT COLIN.PDSE2(AA) - 01.04
Command ===>
****** ********************************* Top of Data ***************
000100 ddd
000200 bbbb
000300 44444

with the version. release at the top.

How do I logon to ISPF and allocate my data sets?

Yes, I know you do not logon to ISPF, but the title is shorter than how do I logon to TSO, and start ISPF so my data sets are allocated as I want them.
I wrote this blog post because I was trying to use ISMF and save information into ISPF tables, but I could not use the information in the tables because my table data set was not in the ISPTLIB concatenation.

When I used TSO ISRDDN to display the data sets allocated to my TSO session I had

ISPTABL -> COLIN.S0W1.ISPF.ISPPROF
ISPTLIB -> ISP.SISPTENU
-> SYS1.DGTTLIB
-> SYS1.SBLSTBL0
...

COLIN.S0W1.ISPF.ISPPROF was not in the list of data sets in the ISPTLIB concatenation.

This lead me to the question – how do I add COLIN.S0W1.ISPF.ISPPROF to the ISPTLIB concatenation?

How do I allocate my datasets to ISPF

When I logon to ISPF I get

------------------------------- TSO/E LOGON -----------------------------------


Enter LOGON parameters below: RACF LOGON parameters:
Userid ===> COLIN
Password ===>
Procedure ===> ISPFPROC Group Ident ===>
Acct Nmbr ===> ACCT#
Size ===> 2096128
Perform ===>
Command ===> ex 'colin.zlogon.clist'

You can influence what happens by specifying a different Procedure, or specifying a command in Command.

The PROCEDURE ===> ISPFPROC is JCL to start a TSO address space and allocate system wide datasets.

Once ISPF has started, you can issue the command TSO ISRDDN to display all of the datasets allocated to TSO.
The ISRDDN command member ISPFPROC will find and show you which of the allocated data sets contain the member.
it gave me

                           Current Data Set Allocation         Member was found
Command ===> Scroll ===> PAGE

Message Act DDname Data Set Name Actions: B E V M F C I Q
Member: ISPFPROC >_ SYSPROC ADCD.Z31B.PROCLIB

You can enter the B command in the >_ field to browse the member directly

Aside:

The Actions: B E V M F C I Q are commands for

  • B Browse the first sixteen data sets or a single data set.
  • E Edit the first sixteen data sets or a single data set.
  • V View the first sixteen data sets or a single data set.
  • M Show an enhanced member list for the first sixteen data sets or a single data set.
  • F Free the entire DDNAME.
  • C Compress a PDS using the existing allocation.
  • I Provide additional data set information.
  • Q Display list of users or jobs using a data set.

Browse the member

This member has

//********************************************************************    
//*
//* ISPF FULL-FUNCTION LOGON PROC
//*
//*********************************************************************
//ISPFPROC PROC ROOT='/usr/lpp/zosmf' /* ZOSMF INSTALL ROOT */
// EXPORT SYMLIST=(XX)
// SET QT=''''
// SET XX=&QT.&ROOT.&QT.
//ISPFPROC EXEC PGM=IKJEFT01,REGION=0M,DYNAMNBR=200,
// PARM='%ISPFCL'
//CEEOPTS DD *,SYMBOLS=JCLONLY
ENVAR("PATH=/bin:&XX./bin")
//SYSUADS DD DISP=SHR,DSN=SYS1.UADS
//SYSLBC DD DISP=SHR,DSN=SYS1.BRODCAST
//SYSPROC DD DISP=SHR,DSN=USER.&SYSVER..CLIST
// DD DISP=SHR,DSN=FEU.&SYSVER..CLIST
// DD DISP=SHR,DSN=ADCD.&SYSVER..CLIST
// DD DISP=SHR,DSN=ISP.SISPCLIB
...
//ISPTLIB DD DISP=SHR,DSN=ISP.SISPTENU
// DD DISP=SHR,DSN=SYS1.DGTTLIB
...
//SDSFMENU DD DSN=ISF.SISFPLIB,DISP=SHR
//ISPTABL DD DSN=SYS1.SMP.OTABLES,DISP=SHR

This JCL

  • creates the environment PATH=/bin/:/usr/lpp/zosmf/bin
  • Allocates lots of data sets, for example SYSPROC has USER…..CLIST depending on the value of the global symbol &SYSVER (Z31B at the moment). If I IPL a different level of z/OS it may have a different level, such as Z24C
  • Allocates fixed name data sets such as ISP.SISPCLIB
  • Allocates lots of ISPF tables for input
  • Allocates an SDSF menu data set
  • Allocates a table ISPTABL for ISPF
  • But does not allocate an ISPTABL for my personal tables.

In the JCL it has

//ISPFPROC EXEC PGM=IKJEFT01,REGION=0M,DYNAMNBR=200,          
// PARM='%ISPFCL'

Which says invoke TSO (IKJEFT01) and execute the %ISPFCL Clist (or REXX).

Use PF3 to return from ISRDDN.

Where is ISPFCL?

The above JCL uses CLIST/REXX ISPFCL as a profile to do additional processing, such as allocating additional data sets.

You could allocate datasets in the ISPF JCL instead of through the CLIST – but the CLIST allows conditional processing, such as if the ISPFPROF data set does not exist, then allocate it.

You can use TSO ISRDDN again and specify member ISPFCL . The member was found, in four places (see the Member: below)

                           Current Data Set Allocations           Row 98 of 118
Command ===> _____________________ Scroll ===> PAGE

Message Act DDname Data Set Name Actions: B E V M F C I Q
Member: ISPFCL >_ SYSPROC USER.Z31B.CLIST
>_ FEU.Z31B.CLIST
Member: ISPFCL >_ ADCD.Z31B.CLIST
>_ ISP.SISPCLIB
Member: ISPFCL >_ USER.Z31B.PROCLIB
>_ FEU.Z31B.PROCLIB
Member: ISPFPROC >_ ADCD.Z31B.PROCLIB
>_ ISM403.SFMNEXEC
>_ AUT430.SINGREXX
>_ SYSUADS SYS1.UADS
>_ SYSUDUMP ---------- JES2 Subsystem file -------------

The member is found in 4 places. You can browse a member by entering B in the >_

The first ISPFCL member has

PROC 0 VOL(B3SYS1)                                                       
CONTROL NOMSG NOFLUSH ASIS
PROFILE NOMODE MSGID PROMPT INTERCOM WTPMSG
WRITE *****************************************************************
...
FREE FILE(ISPPROF ISPTABL)
SET &SDSFTAB= &STR(&SYSUID..SDSF.ISFTABL)
ALLOC DA('&SDSFTAB') SHR FILE(ISFTABL)

SET &DSNAME = &STR(&SYSUID..&SYSNAME..ISPF.ISPPROF)
ALLOC DA('&DSNAME') SHR FILE(ISPPROF)
ALLOC DA('&DSNAME') SHR FILE(ISPTABL)
IF &LASTCC ¬= 0 THEN DO
/* Allocate the ISPF Prof dataset */
...
END
  • The FREE FILE(ISPPROF ISPTABL) says drop (ignore) the existing definitions for ISPPROF and ISPTABL. The CLIST will reallocate them.
  • The ALLOC DA(‘&DSNAME’) SHR FILE(ISPTABL) allocates my dataset to the ISPTABL ddname.
  • The problem is that you cannot easily concatenate my data sets to the ISPTLIB concatenation. You can use the TSO ALLOCate command to allocate a list of data sets to a DDNAME, but not just to add one data set to an existing allocated DDNAME. See Adding a data set to an existing DDNAME in TSO.

Starting ISPF

When you logon to the TSO Logon panel it has

Command   ===> ex 'colin.zlogon.clist'       

The command (if specified) will be processed after any command found in the PARM field of the EXEC JCL statement in your logon procedure.

You can specify ISPF, a clist, or other command.
If you want to invoke ISPF from your clist you will need to invoke the ISPF command for example

/* Rexx */                                                        
trace r
say "in colin.zlogon.clist"
address TSO

"alloc fi(ISPTLIB) DA('COLIN.S0W1.ISPF.ISPPROF') SHR "
zl =userid.SDSF.isftabl /* so we get colin.zlogon.clist */
if SYSDSN(zl) = OK then
do
"alloc fi(isftabl) da('"zl"') shr reus"
end
req = "ALLOC FI(tmp) DA('COLIN.S0W1.ISPF.ISPPROF') SHR "
if bpxwdyn(req ) =0 then
call bpxwdyn "concat ddlist(ISPTLIB,tmp) "
"ispf"

With this, ISPF starts with my data sets allocated as I want them!

Adding a data set to an existing DDNAME in TSO.

I wanted to add a data set to the already allocated ISPTLIB concatenation. You can use the TSO ALLOCate command to allocate a list of data sets, but not to add a data set to an existing definition.

Lionel B. Dyck pointed me to the TSO function bpxwdyn.

When I logon to TSO I invoke a userid.ZLOGON.REXX data set

/* Rexx */                                                              

address TSO
userid = userid()
dsn= userid".S0W1.ISPF.ISPPROF"
req = "ALLOC FI(tmp) DA('"dsn"') SHR "
if bpxwdyn(req ) =0 then
call bpxwdyn "concat ddlist(ISPTLIB,tmp) "

"ispf"
  • The bpxwdyn(req ) allocates the dataset to the DDNAME TMP.
  • The call bpxwdyn “concat ddlist(ISPTLIB,tmp) copies the data set(s) in the tmp DDNAME to the end of the ISPTLIB DDNAME
  • ispf starts ISPF.

The TSO ISRDDN command gave me

                          Current Data Set Allocations           Row 68 of 122
Command ===> Scroll ===> CSR

Volume Disposition Act DDname Data Set Name Actions: B E V M F C I Q
B3RES1 SHR,KEEP > ISPTLIB ISP.SISPTENU
...
A4USR1 SHR,KEEP > COLIN.S0W1.ISPF.ISPPROF

Easy once you know how.

On the CBTAPE are KONCAT and CONCAT which do a similar function.

Tailoring ISPF on a guest machine

I’ve got access to a “production” z/OS machine, and I want to customise ISPF to include my clists, and ISPF panels. How do I do this?

I covered some of the details in Configuring ISPF for new applications.

The first step

The most useful command is ALTLIB

"altlib activate application(exec) dataset ('COLIN.CLIST')" 

This allows you to insert your data set ‘COLIN.CLIST’ at the front of the search chain for EXEC (REXX) commands.

You can use the command ALTLIB DISPLAY. This gave me

Current search order (by DDNAME) is:
Application-level EXEC DDNAME=SYS00053
System-level EXEC DDNAME=SYSEXEC
System-level CLIST DDNAME=SYSPROC

You can now issue commands from the specified dataset.

Note: The ALTLIB only applies to the current ISPF session. If you have multiple ISPF sessions, you will need to do it in each session.
If you create new sessions ( START ) it will inherit from the the current session.

Can I do this automatically?

I can manually issue the ALTLIB command. If the systems programmer adds the following to the TSO logon procedure

/* REXX */ 
trace r
/* your userid is prefixed to any data set unless you double quote it */
if (sysdsn('CLIST') = 'OK')
then
do
"altlib activate application(exec) dataset (CLIST)"
if (sysdsn("CLIST(USERPROF)") = 'OK' )
then "USERPROF"
end
return 0

It will automatically issue the ALTLIB command, if the userid.CLIST data set exists, and it there is a member USERPROF in the data set, it will execute that.

To configure ISPF applications you can use the LIBDEF command. See Configuring ISPF for new applications.

How do I change all members of a PDS? Easy – use an edit macro

I looked on the internet, to find something that would allow me to make the same change to all members of a PDS, and there seemed to be several options, IPOUPDTE, CPPUPDTE, PDSUPDTE, written in assembler many years ago.

I had written an equivalent program in C, but I no longer have the source. I found it as quick to write a solution using Rexx and an ISPF edit, than to find a solution on the internet. The solution is much more flexible, and can do so much more.

The processing has two stages

  • Code to iterate over each member of the PDS, and invoke ISPF EDIT on each member
  • An edit macro to make the changes.

Code to iterate over each member

ISPF 3.4 DSLIST displays data sets

DSLIST - Data Sets Matching COLIN.J*       
Command ===>

Command - Enter "/" to select action
-------------------------------------------
AA COLIN.JCL
COLIN.JCL.DCOLLECT.OUTPUT

You can enter local commands at the front of each line. The commands can be ISPF special (D for Delete, C for Catalog, R for Rename), or TSO commands, where a TSO command can be a Rexx exec in the ISPEXEC concatenation. (You can use the TSO ISRDDN command to display the data sets allocated to your session)

I have a Rexx exec called AA.

Access the parameters passed to the rexx

/* rexx */                                           
ADDRESS ISPEXEC "VGET (ZDLDSN) SHARED"
ADDRESS ISPEXEC "VGET (ZDLCMD) SHARED"
ADDRESS ISPEXEC "VGET (ZDLLCMD) SHARED"
say "ZDLDSN " ZDLDSN
say "ZDLCMD " ZDLCMD
say "ZDLLCMD " ZDLLCMD

With the line command

Command - Enter "/" to select action  
--------------------------------------
AA 99 COLIN.JCL

The Rexx produces

ZDLDSN    COLIN.JCL
ZDLCMD AA 99
ZDLLCMD AA 99 'COLIN.JCL'
  • ZDLDSN is the data set name
  • ZDLCMD is the line command and any data
  • ZDLLCMD is the (Long) combined command and the data set

With the line command

aa 99 / Zyx

The dataset name is substituted for /, and the output is

ZDLDSN    COLIN.JCL
ZDLCMD AA 99 / Z
ZDLLCMD AA 99 'COLIN.JCL' Zyx

This means you can pass parameters to your Rexx.

Process every member

The data set name is in variable ZDLDSN. When you use it, you should quote it because your userid may/may not have prefix on, which puts your userid on the front of every data set you use. Without quotes, it could not find dataset COLIN.COLIN.JCL

  • The LMINIT command returns a handle in the DATAID(handle) variable, to refer to the data set. My handle is called data1.
  • The LMOPEN command opens the dataset associated with the handle.
  • The LMMLIST command iterates through the list, starting with a blank member name which indicates start with the first.
  • The EDIT command invokes ISPF edit on the member, and passes the name of an ISPF EDIT macro to use. In my case the macro is called FOREACH.
Address ispexec "LMINIT DATAID(data1) dataset('"ZDLDSN"')" 
if rc <> 0 then
do /* report the errors */
say ZERRSM ZERRLM
return 8
end
Address ispexec 'LMOPEN DATAID('data1') OPTION(INPUT)'
member = ' '
lmrc = 0
/*********************************************************************/
/* Loop through all members in the PDS, issuing the EDIT service for */
/* each. */
/*********************************************************************/
Do i = 1 by 1
Address ispexec 'LMMLIST DATAID('data1') OPTION(LIST),
MEMBER(MEMBER) STATS(NO)'
If rc = 8 then leave /* not found */
If rc <> 0 Then
do
say ZERRSM ZERRLM
leave
end
else
do
Address ispexec 'EDIT DATAID('data1') MEMBER('member')
MACRO(FOREACH)'
end
End
/*********************************************************************/
/* Free the member list and close the dataid for the PDS. */
/*********************************************************************/
Address ispexec 'LMMLIST DATAID('data1') OPTION(FREE)'
Address ispexec 'LMCLOSE DATAID('data1')'
Exit 0

From the information passed to the Rexx exec, you could pass the edit macro as a parameter, such as

AA / MYMAC 

I was just lazy and hard coded the macro name.

An edit macro to make the changes

My basic macro just reports the member name, and the size of the file

ADDRESS ISPEXEC 'ISREDIT MACRO' 
"ISREDIT (last) = LINENUM .ZLAST"
"ISREDIT (member ) = MEMBER"
"ISREDIT (curr,orig,concat)= DATASET"
say "foreach " member last curr
"ISREDIT END"
  • The LINENUM command returns the line number of the specified line. You can create your own line labels. ISPF provides .ZFIRST and .ZLAST .ZCSR (the line where the cursor is currently.
  • The MEMBER command returns the member name
  • The DATASET command returns the current name of the dataset (and other information)

You can now do conditional processing, if the data set name starts with… then change…; if the member name starts with… then change…

You can use standard ISPF commands for example

/* */ 
/* trace e */
ADDRESS ISPEXEC 'ISREDIT MACRO'

"ISREDIT X ALL "
"ISREDIT FIND '//' ALL"
"ISREDIT CHANGE 'IBMUSER' 'COLIN' 1 10 ALL NX"
return
"ISREDIT END"

Without the return statement in the code, the code makes the changes and exits, it invisibly edits all of the members. With the return statement this gives you the opportunity to review the changes and to use end or cancel to leave the edit session.

Whoops – what happened there

To test the macros I use ‘view’ of the member, to edit it, but not save any changes. I then executed the macro.
I was trying to change a VOLSER to a symbol.

/* */ 
ADDRESS ISPEXEC 'ISREDIT MACRO'
"ISREDIT (member ) = MEMBER"
say member
"ISREDIT SCAN OFF"
if substr(member,1,6) <> "IEASYM" then
do
"ISREDIT CHANGE 'A3PRD1' '&&SYSP1.' ALL "
"ISREDIT CHANGE 'A3PRD2' '&&SYSP2.' ALL "
"ISREDIT CHANGE 'A3PRD3' '&&SYSP3.' ALL "
"ISREDIT CHANGE 'A3PRD4' '&&SYSP4.' ALL "
"ISREDIT CHANGE 'A3PRD5' '&&SYSP5.' ALL "
"ISREDIT CHANGE 'A3RES2' '&&SYSR2.' ALL "
"ISREDIT CHANGE 'A3SYS1' '&&SYSS1.' ALL "
"ISREDIT CHANGE 'A3CFG1' '&&SYSC1.' ALL "
end
return
"ISREDIT END"f

I wanted to change A3PRD1 to the symbol &SYSP1. I had use use &&, and SCAN OFF. Without these, ISPF treats &SYSP1 as an ISPF symbol, cannot find it, so replaces it with a null.