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.
- See A dialog variable name enclosed in parentheses (varname) … If the dialog variable name is on the left, its content is totally replaced.. So variable f gets the value of the line number of the current line
- “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.
- See A dialog variable name enclosed in parentheses (varname) … If the dialog variable name is on the right, the entire contents of the variable are considered part of the data, including any quotes, apostrophes, blanks, commas, or other special characters.
- 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.