Showing posts with label ISPF. Show all posts
Showing posts with label ISPF. Show all posts

Thursday, September 18, 2025

Why SRCHFOR didn’t find my string (and how I fixed it)

 Not long ago, I was trying to search for a string inside a PDS using the SRCHFOR command. To my surprise, the command returned the message:String(s) not found

Invoking the SRCHFOR command with the search string

String(s) not found

But I knew for sure that the member contained the exact string!

Here is the member in PDS with the exact string that we are searching for.

This reminded me of a post I had read about two years ago where someone faced the same issue. At that time, I noted it as an interesting quirk of ISPF. Now that I’ve stumbled upon it again, I thought I’d document the problem, the solutions I explored, and how I eventually rediscovered that original tip.

The problem: SRCHFOR and case sensitivity

The root cause is simple: SRCHFOR automatically converts the search string to uppercase after you press Enter.

So if your dataset member has mixed-case text (e.g., HelloWorld) and you try:

SRCHFOR 'HelloWorld'

it silently changes it to:

SRCHFOR 'HELLOWORLD'

…and of course, the match fails.

Step 1: Checking the documentation

To be sure I wasn’t missing something, I pulled out the z/OS ISPF User Guide. I even uploaded the manual to NotebookLM and asked how to prevent SRCHFOR from changing the case of my search string.

NotebookLM suggested two options:

    1. Use ISPF 3.15 (Extended Search-For)

  • This option provides an ASIS field.
  • When you specify ASIS=YES, the search is performed without converting the string to uppercase.
  • This worked perfectly.

Type the search string in the Asis field. As you see, multiple search strings can be searched in one go. 


String(s) found. Yeahh!

    2. Use the SFE Command

  • SFE is a panel-driven version of SRCHFOR.
  • You can invoke it directly against a PDS and provide the search key.
  • This also respected the case and gave correct results.
Type 'SFE' in the line command area against the PDS.

Type the search string. Put a '/' on the Any case process option at the bottom left to select it. 

String(s) found.

Both solutions worked, but I still felt there must be a simpler way.

Step 2: Rediscovering the Reddit Tip

While searching online, I found the exact Reddit post I had read two years ago. The OP there had shared a neat trick:

  • Invoke SRCHFOR without parameters:

    SRCHFOR
  • This opens up the Search-For Options panel.

  • From there, select the “Any case” process option.

Once you do this, SRCHFOR will no longer force your search string to uppercase. Problem solved!

(Credit to the Redditor who originally shared this tip)

Invoke SRCHFOR from the command line without passing a search string.

In the SRCHFOR options displayed, put a '/' on the Any case process option at the bottom left.

Try searching for the string (with lowercase) again

String(s) found.

Three Ways to Solve the Problem

Here’s a quick summary of the methods I tested:

  1. ISPF 3.15 (Extended Search-For) with ASIS

    • Best for detailed searches with lots of parameters.

  2. SFE Command

    • Quick, panel-based, and respects the case.

  3. SRCHFOR (no parameters) → Select “Any case”

    • Simple, and once set, it keeps SRCHFOR from changing your string.

Bonus: SRCHFOR vs. SRCHFORE

While digging around, I also refreshed my memory on the difference between the two commands:

  • SRCHFOR → Searches within a single dataset (PDS or sequential).

  • SRCHFORE → The extended version, which lets you search across multiple datasets or a dataset list.

So if you often search across a group of libraries, SRCHFORE is your friend.

Wrap-Up

This little exercise was a reminder that even tools we’ve used for years can have hidden quirks. SRCHFOR changing case silently tripped me up, but now I know at least three ways around it.

If you ever find SRCHFOR telling you “No strings found” when you’re sure otherwise, try one of these fixes. And a big thanks to that Redditor from two years ago—your tip still saves the day!



Wednesday, May 5, 2021

Using process statements in SuperCE utility

One of the item that I always strike off ✅ from my checklist whenever I'm assigned with a task of modifying an existing code is Source Code Comparison. It allows me to highlight the difference between different versions of the code. It also acts as a proof for the reviewer that only the intended parts of the code were modified. 

Although, CA Endevor lets us use the Changes (C) option to look at the actual lines we've changed, I rely upon SuperCE utility (option 3.13) to compare the modified code and the existing version of the code in Production environment. 

Welcome to my blog! 😀 In this blog post, we will look at the SuperCE (option 3.13) ISPF option - which is used to compare the content of two datasets - and the usage of Process statements which is similar to the usage of control statements in IBM's DFSORT utility. 

This one is for the Thumbnail 😁

Your time is precious. So, please use the following links to navigate to different sections of this post. 


Intro

SuperC (I guess the suffix 'C' after Super stands for Compare) is the standard option to compare two datasets of unlimited size and record length. SuperCE is the extended version of the standard SuperC Utility and it offers more flexibility like,

  • Comparing the datasets in line, word or byte level, 
  • Supplying process statements for specific compare requirements 
  • Various listing types and so on. 

How to access SuperCE Utility and use it?

To access the SuperCE Utility from ISPF Primary Option Menu, type 3 (Utilities) and press Enter.

Click on the image for a larger version.

ISPF Primary Option Menu.


From the Utility Selection Panel, type 13 (SuperCE)  and press Enter

Selecting SuperCE from Utility Selection Panel.


Voila! 👏
SuperCE Utility Panel.

Alternatively, you can type =3.13 from ISPF Primary Option Menu (or command line for that matter) and hit Enter to directly get into SuperCE Utility panel.
 

How to use SuperCE Utility?

Now that we're inside the SuperCE Utility panel, let's use it. 
The true method of knowledge is experiment. 
 - William Blake
To use SuperCE utility, we should have two datasets. It can be a sequential dataset, PDS or a member inside a PDS. ❗ SuperC and SuperCE doesn't support tape datasets. 

I've got 2 PDS members with a simple COBOL program in each of them. For better understanding, I've named these members as NEW and OLD because the contents in the NEW member is an updated version of contents in OLD member.

The NEW member. This COBOL program accepts a name from the user and displays the name with a greet. 


The OLD member. As you probably know, this COBOL program simply displays a very famous message to the user. 


The next step is to input these datasets in the SuperCE Utility panel and do the comparison. The New DS Name field should be provided with the updated version of the dataset that you want to compare and the Old DS Name field should be provided with the previous version of the dataset. 

Using the SuperCE Utility panel.

Whenever you access the SuperCE Utility panel, it provides default setting for the Compare Type, Listing Type, Listing DSN, and Browse option. 

SuperCE Utility works the best for you with the following settings,
  • Compare Type - Line (Compares the dataset for line differences)
  • Listing Type - Delta (SuperCE provides a listing after the comparison. This listing shows some awesome stats. Delta option lists the differences between the source data sets, followed by the general summary)
  • Listing DSN - This is where the listing output will be stored. SuperCE allocates a default DSN in case if you leave this field blank. If you want to store the results of comparison (I do, as I used to pass on this dataset to my code reviewer), you may provide your own DSN.  
  • Display Output - Yes (This option tells ISPF that you want the output listing to be displayed. If you choose the option No, SuperCE will not show the listing but it shows the result of the comparison (Differences found or No differences found) at the top right corner of the panel). 
  • Output Mode - View or Browse 
  • Execution Mode - Foreground is the default. 
For more details about the SuperCE Panel Fields, click 👉 here.  

Let's hit Enter to allow SuperCE perform the comparison. The listing output after the comparison is shown below.
 
Listing output for Line Compare. 

In the Listing Output Section (Line #4 thru 21), the source lines are shown. 

Left side of each line is either marked with I (Insert) or D (Delete). 

The first source line at line #9, 000200 PROGRAM-ID. NEW.  , is marked with I (Insert) i.e., the listing tells that this line was inserted in the New DSN and wasn't found in the Old DSN. 

The next source line at line #10 is marked with D (Delete) i.e., the listing tells that this line is present in Old DSN but not in the New DSN. So, it must have been deleted in the updated version of the code. 

The Line Compare Summary and Statistics section at the bottom shows the overall summary of the comparison. 

How to use process statements to perform diverse data comparisons?

As you would've noticed in the listing output, the first 6 bytes (Column Numbers) of the COBOL code was also included for the comparison by SuperCE. 

Suppose you want to compare data residing in the columns 7 thru 72 in both the datasets, you should supply process statements for this requirement. 

The process statements panel can be accessed by typing E in the command line of SuperCE Utility panel, or by using Options action bar choice and choosing Option 1 - Edit Statements.

Accessing Process Statements panel.


In the following picture, some examples of the statements that can be used are shown in the bottom half of the screen. The actual statements required for your comparison should be typed in the EDIT window shown in the first half of the screen. 

Process Statements panel.


CMPCOLM process statement should be used to compare using a column range.

Inputting Process Statements. 

We can exit the screen now by pressing F3. A message, 'Statements DS saved' is displayed at the top right corner of the SuperCE Utility panel. 

Statements DS Saved.


The compare statements will be stored in the dataset provided in Statements DSN field in SuperCE Utility panel. This field can also be left blank allowing the system to create one dataset for you to store the process statements. 

On hitting Enter, the compare request will be invoked with the process options.
 
Listing Output

The Line Compare Summary shows that there are 4 line matches and 6 differences. At the bottom of the screen, the criteria used for this compare task is specified. 

There are many flavours of process statements that can be invoked depending on what you need to compare. Some of them are listed below. 

Example 1:



You can notice that the end of the process statement, CMPCOLM, contains a suffix of N and O, indicating that it is referencing the New DSN and Old DSN respectively. What follows the statement is the column range within the referenced dataset. 

With these statements, we tell SuperCE that we want to compare the data residing in columns 5 to 30 in the New DSN with data in columns 1 to 25 in the Old DSN. 

Example 2:
Suppose you want to ignore the comment lines in your COBOL code from being compared. 



DPLINE (Do not process lines) process statement do not process the lines that can be recognized by a unique character string, for comparison. 

DPLINE '*',7 scans for an asterisk ('*') in column 7 and ignores it from being compared.


Example 3:
Suppose if you want to compare only specific rows in each datasets.


The NFOCUS and OFOCUS process statements can be used to specify the rows to be used for the comparison. In this case, rows 1 thru 10 will be used from the New DSN while rows 11 thru 21 will be used from the Old DSN. 

More about Process Statements can be found 👉 here

Running SuperCE in batch mode

Sit back and relax. You can create a JCL from SuperCE Utility panel (with fewer hits on that Enter button) to run the comparison in batch mode. ISRSUPC is the program which is used for comparison.

After providing the datasets in the New and Old DSN, select the execution mode as Batch and press Enter. In the Submit Batch jobs panel, Job statement info is provided at the bottom of the screen. I've chose to Edit JCL before submit. 

SuperC Utility - Submit Batch jobs panel.


Upon hitting Enter, the JCL is shown to user. 



If you are adding Process Statements, a SYSIN DD statement will be added to the JCL. 



Conclusion

Hope you witnessed the uses of SuperCE utility. If SuperCE stands for Super Compare Extended, then adjective Super is well suited and appropriate. Should you have any questions/suggestions please leave it in the comments section below. Thx 👍


References: 
  • z/OS ISPF User's Guide Vol II
  • TSO/ISPF Curriculum z/OS v2.3 - Interskill Learning


Sunday, December 20, 2020

Writing a REXX program to copy Mainframe tape dataset to DASD

Hiya! 👋 Welcome to my blog. In this post, let's look at a REXX (not T-REX🦖) code which will take a Mainframe tape dataset name as input and let you copy it to a DASD dataset.  

Mainframe tape datasets  can't be browsed like a PS dataset. To view the contents of the tape dataset, we need to copy the tape dataset to a DASD first. 

I hope this post will be of help to those who don't have prior experience in writing REXX programs. If you read till the bottom of this post, you'll get a fair idea on the following stuff:

  • Usage of REXX in IBM z/OS. 
  • Defining File-tailoring skeletons. 
  • Addressing environments in REXX. 
  • Built-in functions.
  • IF/THEN/ELSE instructions in REXX. 
  • Invoking REXX Exec.

A short intro about REXX:

  • REXX is a programming language that was developed by IBM. 
  • REXX is easy to learn and use. To prove this, let's write the traditional Hello World🌍 program in REXX.

    • Create a new member in your own PDS

      The first line of the REXX program SHOULD be a comment (delimited by /* and */) and it must contain characters  'REXX' in it. say in the second line is a REXX function that is used to print messages to the console.

      Save the member and press F3 to go back to PDS members list view. In the line command area, type ex to execute the member.

      No caption needed😎

  • REXX is very readable, REXX instructions are based on English. 
  • REXX has powerful set of built-in functions. 
  • REXX is an interpreted language that does not require compilation. Each line of code is checked and interpreted into "machine understandable code" before being executed. 
  • REXX runs in all MVS address spaces and on many platforms (there's one for Android too 👀). 
REXX is a program langauge swiss army knife for z/OS System Programmers and System Administrators.  

I guess it's enough talking about REXX. Let's begin writing ✍ the program itself. The entire program is shown below and we will go through the program section by section. 


Lines 1 thru 16: 

In REXX, comment is a sequence of characters delimited by /* and */. The first 16 lines are comments and they tell the modifications that have been made to this code right from the creation of this REXX Exec. Wait, What is a REXX Exec? (you may be asking yourself 🤔). 

Well, a REXX Exec contain REXX language instructions plus commands that are executed by the host environment. 

💡IBM recommends that all REXX execs start with a comment that includes the characters 'REXX' within the first line (line 1) of the exec. Failure to do so can lead to unexpected or unintended results in your REXX exec. 


Lines 17 thru 19: 

One of the strengths of REXX is that you can use it to invoke the functions of other products. ADDRESS command temporarily or permanently changes the destination of the commands that are followed next. Commands are strings sent to an external environment. 

💡ADDRESS statement is more similar to sudo (Switch User and DO this) command in Linux, which will make you the root user briefly to perform root user actions like installing a package. 

To use DB2 commands in REXX, you must first address that environment. Likewise, in Line #17, address ispexec is coded to execute the command in line #18 in ISPF environment, the full panel application that we all are addicted to😀. 

LIBDEF command is used to define the application-level libraries that will be in effect when the application is running. In Line #18, we use the LIBDEF command to define the Skeleton Library 💀. Gotcha! A Skeleton library is used to store skeleton files. A Skeleton file can be a JCL like the below: 


This is pretty much a simple job that has a SORT step in it to copy an input dataset, provided in SORTIN DD statement, to the output dataset, defined in SORTOUT DD statement. But, something here is so weird, isn't it?🤔 You see a lot of ampersands, don't you? Let me list out all the variable names preceded by an ampersand. 

  • &JNUM
  • &DSNME
  • &USRID
  • &MON
  • &DY
  • &HH
  • &MM
  • &SS 

There isn't an input dataset name defined in SORTIN. Instead, you've got &DSNME. You'll be issued with JCL errors when you manually submit this JCL. So, please don't do that ⚠. 

This JCL is meant for REXX. When our REXX exec is running, this skeleton file will be scanned record-by-record by the File-tailoring services in REXX (more about File Tailoring services will be covered later in this post). Each record will be scanned to find any dialog variable names, which are names preceded by an ampersand. When a variable name is found, it's current value is substituted from the REXX exec. 

💡A skeleton file can be assumed as a template that is non-functional on its own. File-tailoring services read skeleton files and write tailored output that can be used to drive other functions. 

Do you get the whole picture now? We'll use File-Tailoring services in Rexx to substitute values in all those variable names, preceded by an ampersand, in the skeleton file. We'll then have a functional JCL which upon submission will copy the input dataset to the output dataset. Simple! 😎  


Line #19 uses the ARG instruction. ARG retrieves the argument strings provided to a program or internal routine and assigns them to variables. For example, if you pass the string "IBM z/OS" to the statement, arg company product, then

company contains 'IBM'

product contains 'z/OS'

Line #19 is used to pull the input dataset name and assign it to the variable, dsn.


Lines 20 thru 26 in REXX exec: 

We need an input tape dataset name for the REXX exec to copy it to an output dataset. When the REXX exec is invoked without an argment (i.e., an input dataset name), we should tell the user to invoke the REXX exec with an argument and exit the exec. Line 20 thru 26 does that with the help of say instruction in REXX. 

IF/THEN/ELSE instruction in REXX is used to vaildate the dsn variable. When you have more than one statement under an IF condition, enclose them between a DO and END. 


Lines 27 thru 41 in REXX exec: 

The else part starting from line #27 is executed when the user has invoked the REXX exec with an argument. 

STRIP() is a built-in function in REXX and it is being used in line #29 to remove the leading spaces and trailing spaces as well as single quotes (if any) that surrounds the input argument. The stripped value is assigned to variable, a.

RANDOM() is a built-in function in REXX and it is being used in line #30 to generate a random non-negative whole number between the min and max range that are provided as arguments i.e., 001 and 999 respectively. The random number is assigned to a variable JNUM and this variable name, preceded by an ampersand, is present in the first line of the skeleton file. The usage of RANDOM()function in this exec ensures that the job name is unique every time when the output JCL is created by the file-tailoring services. 

USERID() is a built-in function in REXX and it is being used in line #31. This function returns the TSO User ID. Note that the return value of the function is being assigned to variable that is used in the Skeleton file. 

In line #32, the stripped input dataset name available in the variable a is being assigned to the Skeleton file's variable, DSNME

Pause! Let's do a status check to see where we are right now ✅. 

Till now, we only have a valid job name and an input dataset name to be assigned to the JCL in the skeleton file. We need an output dataset name. The output dataset name should be unique every time when the REXX exec is invoked. To do that, we will be adding the Date and Time values at the last 2 qualifiers of the dataset name. At the time of writing this line, I invoked the REXX exec against a dataset and got the output dataset name as 'Z01071.TAPE.COPY.Z01071.DDEC20.T053624'. The last but 1 qualifier has got the date as 20th December and  the last qualifier contains the time in Thhmmss format. Let's continue. 

In line #33, a variable upper is assigned with sequence of characters from a to z in upper case and in line #34, a variable lower is assigned with sequence of characters from a to z in lower case. We'll be using these variables as argumens to the TRANSLATE() built-in function in REXX.  

In line #35, two built-in functions are used. SUBSTR() and DATE(),

DATE() function returns the local date in dd mon yyyy format by default. When 'U' is passed as an argument to the DATE() function, the local date will be returned in MM/DD/YY format.   The return value from the DATE() function is being used as the string for SUBSTR() function. SUBSTR() function is used to extract a portion of the string. Therefore, line #35 is used to extract the MM from the date string (in MM/DD/YY format) and value is assigned to a variable named m

The CALL instruction in line #36 calls an internal routine, find_month


This routine uses the SELECT instruction in REXX to choose one of the 12 months based on the value in variable, m. return statement at the end of the routine, returns back the control to the line next to CALL instruction. 

Line #37, translates the month value from upper case to lower case using the TRANSLATE()built-in function.  

I hope you will be able to decode the lines 38 thru 41. They use the DATE(), TIME() and SUBSTR() functions to assign values to variables that are used in the Skeleton file.

At the end of line #41, we would have assigned values to all the dialog variable names, preceded by an ampersand, in the skeleton file. It's now time to use the File-tailoring services of REXX to write a tailored JCL that can be edited and submitted by the user. 

Lines 42 thru 47:

File-tailoring services:

To use the file-tailoring services, we must first address the ISPEXEC environment. After addressing the environment, the host environment's commands are passed as strings from the lines 43 thru 47. 

  • FTOPEN TEMPPrepares the file-tailoring process and specifies whether the temporary file is to be used for output. 
  • FTINCL TCSKEL - Specifies the skeleton file named TCSKEL from the skeleton library to be used and starts the file tailoring process. The skeleton file is read record-by-record and the dialog variable names in the skeleton file are assigned with values from the variable pool created by the REXX exec.
  • FTCLOSE -  Ends the file-tailoring process. 
  • VGET ZTEMPF - The file tailoring output is directed to a temporary sequential file. The file name of the temporary file is available in the system variable ZTEMPF. Before editing or submitting the job from the temp. dataset, the VGET service should be invoked to initialize the ZTEMPF.  

In line #48, we have issued EDIT dataset command so that the temporary file will be opened in edit mode for the user. The user after checking the JCL can submit the job by issuing SUB command. 

Invoking the REXX exec:

There are several ways of invoking a REXX exec from foreground or background. 

  1. Easiest of the lot is, after saving a REXX exec, you can issue an ex  command on the member to execute the exec in foreground mode. 
  2. When you want to use the member name of the REXX exec as a line command, you must first allocate your private PDS that contain the REXX Exec(s) to the system libraries SYSEXEC/SYSPROC. Refer the first step in this link for more details on how to do that. You might have to copy paste the REXX exec to your PDS and provide you REXX PDS in the DATASET parameter of the ALLOC command. After the allocation, when you type the member name of a REXX exec, you're private PDS will be scanned by the system and the REXX exec will be executed if it's found in the PDS. 
Only option 2 works with the REXX exec that we've prepared. Invoking the exec will be lot easier when you allocate your private PDS to the system libraries. Let's see how our REXX exec fares out.

First, let's assign the private PDS to system library. 

Note that I've provided my PDS in the DATASET parameter of ALLOC command. 

Type ex on the member to execute the REXX exec. 

Allocation successful! Now, we can use the member name TC as a line command.


Type tc on a tape dataset or a DASD dataset that you want to make a copy. 


Tada! Here is the file tailored output. You may type SUB on the command line to submit this job. You may also type some SORT statements before submitting the job.  


Thx for reading. Please share your thoughts in the comments section. I would be glad to answer to your questions if any. Happy Holidays!