Showing posts with label Mainframe. Show all posts
Showing posts with label Mainframe. 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!



Tuesday, May 27, 2025

Modern Mainframe tools I use daily. Plus, a blog milestone!

A few days ago, I stumbled upon the Code4z Extension Pack for Visual Studio Code at my workplace. This bundle brings several modern tools to Mainframe developers right within VS Code. Here's what it includes:

✅ COBOL Control Flow
✅ COBOL Language Support
✅ Explorer for Endevor
✅ Zowe Explorer
⬜ Abend Analyzer for Mainframe
⬜ Data Editor for Mainframe
⬜ Debugger for Mainframe
⬜ HLASM Language Support

(I’ve highlighted the ones I’ve personally tried so far.)


Disclaimer:
The tools and setup showcased in this blog post were explored using my personal computer and a Mainframe ID obtained through the IBM Z Xplore learning platform. The content reflects my personal experimentation and learning and does not involve or represent any proprietary systems, data, or configurations from my workplace.

πŸ› ️ Getting Started with COBOL in VS Code

COBOL Control Flow and the COBOL Language Support extensions were immediately put into use. I had already created a custom REXX tool that extracts the COBOL source code from Endevor Element Listing output (one advantage of this source code is that all the INCLUDEs on the COBOL program are expanded) and sends it via email as a .txt file. I'd then open this .txt file in VS Code and manually set the language mode of the file to COBOL.

You can easily find out that the file is recognized as COBOL source code with the help of syntax coloring.

πŸ“· Click on the image to view it in full size.

A file with COBOL source code opened on VS Code. The language mode highlighted at the bottom right corner indicates COBOL. 


Right-clicking anywhere on the file presents you with a lot of useful options, my favorites being
  • Go to Definition
  • Find All References
  • Generate COBOL Control Flow
  • and, lately, Copilot.

COBOL Language Support features are shown upon right-clicking on the file. 

πŸ’‘ Real Productivity Boosters

When analyzing a COBOL program using CA Endevor’s listing output mode, I often find myself jumping up and down the listing—switching between paragraphs to trace the program's flow. In the process, it’s easy to lose track of the original paragraph I started from, especially when the logic branches out deeply. I’ve often resorted to using a notebook just to jot down the key paragraph names to avoid getting lost.

This is where modern tools shine. With features like Peek Definition and Peek References, you can stay on the current paragraph and quickly explore where a variable, copybook, or another paragraph is defined or referenced—without losing context.

Now, I use Visual Studio Code instead of 3270 Terminal for any kind of COBOL program analysis, whether I’m debugging abends, understanding functionality, or implementing enhancements.

πŸ”„ Visualizing Control Flow

Another useful feature is Generate COBOL Control Flow, which allows you to graphically visualize the COBOL program. 

πŸŽ₯ Watch: COBOL Control Flow in Action

This πŸ‘‡ video shows how the VS Code extension visualizes the control flow of a COBOL program, highlighting the entry point and relationships between paragraphs.

For me, the COBOL control flow helps answer questions such as

  • Where does the program begin execution? The flowchart clearly shows the entry point and how the control moves across different paragraphs or sections.
  • Are there any unreachable or unused paragraphs? If a paragraph exists but has no inbound flow, it may indicate dead or obsolete code.
  • How complex is the program?
  • Which paragraph calls which?  Instead of manually tracking PERFORM statements, you can visually trace the relationships between paragraphs.

Zowe Explorer & Endevor Integration

The next set of extensions that I configured were the Zowe Explorer and Explorer for Endevor. Though I've been using Zowe since 2020, this was the first time I tried my hand at it in a work setting. 

With the Explorer for Endevor extension, I was able to set up an Endevor synchronized workspace and retrieve elements on VS Code. This proved that the REXX tool to extract COBOL source code from listing output is futile. After all, why reinvent the wheel?

πŸ€– Using GitHub Copilot on the Mainframe

Thanks to GitHub Copilot access at work, my productivity has further improved. I use it along with Zowe Explorer for:

  • Writing/Debugging DFSORT

  • Resolving SQLCODE errors

  • Enhancing JCL scripts

  • and much more…

The use cases are endless. Here are some.

🧠 The Problem:

You wanted to create a DFSORT step that performs a transformation—like converting lowercase to uppercase—but weren’t sure about the syntax.

πŸ› ️ The Workflow:

  1. You started writing your JCL in VS Code.
  2. Invoked GitHub Copilot's In-line Chat with a natural-language prompt:
    • “Finish the OUTREC to convert lowercase letters to uppercase.”
  3. Copilot suggested:
    • OUTREC FIELDS=(1,80,TRAN=LTOU)
  4. You tested the job and confirmed the expected result. This step is essential as the code generated by Copilot may not always be correct.

πŸ“½️ Demo:

πŸ‘‡ Here's a short demo of me using GitHub Copilot to complete a DFSORT JCL step:

🧩 Final Thoughts

Modern Mainframe tooling, especially with extensions like Code4z, Zowe Explorer, and Explorer for Endevor, has revolutionized how I approach development and debugging. VS Code has now become my go-to tool nowadays. If you're still sticking to just the green screen, I highly recommend giving these tools a try. You’ll be surprised how much more productive and enjoyable Mainframe development can become!

I'm planning a follow-up blog post to dive deeper into how GitHub Copilot can be integrated with Zowe - bringing native-like AI assistance to mainframe development tasks. Stay tuned!

Over to you! Have you explored any modern tools for mainframe development? I'd love to hear about your experiences. Drop a comment below, and let's keep up the conversation.

πŸ… Featured Among the Top 30 Mainframe Blogs!

Before I wrap up this post, I’m thrilled to share a bit of good news. My blog has been ranked among the Top 30 Mainframe Blogs by Feedspot! πŸŽ‰

This recognition motivates me to continue sharing hands-on experiences from the Mainframe world. A big thank you to everyone who reads, shares, and engages with my content.



Saturday, July 31, 2021

How to concatenate all generations of a dataset?

 Hello πŸ‘‹

In this post, let's see πŸ‘€ how we can concatenate all the existing generations of a dataset. In z/OS, a dataset which have generations (each generation is a successive update) is called as Generation Data Group (abbreviated to GDG).

This picture is for the thumbnail of this blog post. 

When you have a job whose task is to reference all existing generations of a data set, you would normally need to manually check the generation numbers and insert them into the JCL. 

One way around this is to just code the GDG base entry name and the system will automatically pick up all cataloged generations. You don't have to manually check for the generation numbers πŸ’ƒ.

 

JCL to concatenate the GDG generations.

Upon submitting the JCL,

JESYSMSG Listing after the completion of the job.

It is evident in the output produced from this job that the latest generation of this data set is accessed first (LIFO) and so on. 

How the order of concatenations can be modified?

LIFO order can be reversed using the GDGORDER parameter. 

Usage of GDGORDER Parameter in the JCL.


Upon submitting the JCL,

You can now see that the order of concatenation has changed.

It's worth noting that by default, the generations are concatenated in Last In First Out order. GDGRODER comes handy when you want to override the default. 

Hope this helps!


Monday, January 11, 2021

How to schedule jobs using JOBGROUP?

I was on Production support for that week and (Nope! I wasn't haunted by a ghost πŸ‘») I was given a task of running a specific SQL SELECT query against a DB2 table in Production environment, 2 times a day, throughout that week. If the query returned rows, I had to prepare a one time job to be run in Prod. Else, do nothing. 

The project had only scheduled releases due to Agile way of working and the one time job that we ran on Prod environment was a temporary fix that we had to do until the next release, which was to occur in 2 months down the line.  

Therefore, it was imperative to run that SQL SELECT query 2 times a day by the person who is on support for that week and as we all know, Human error is inevitable - there were some misses in running the query. I already knew about JOBGROUP by then. So, I was thinking πŸ€” about applying it in this scenario to prevent any misses. 

Thumbnail πŸ˜‰


The main reason that JOBGROUP struck my mind was due to the fact that we had to run the SQL query at specified times. 

What's JOBGROUP πŸ€”?

The JOBGROUP statement in JCL allows you to group several jobs together thereby allowing you to schedule them and trigger them based on various events. JOBGROUP gives you just some basic job scheduling capabilities and it is not there to replace high-end batch scheduling softwares like Ctrl-M or CA-7. 

JOBGROUP is available from z/OS Version 2.2

JOBGROUP definition starts with a JOBGROUP statement and ends with an ENDGROUP statement. Within this boundary, we have to define the job names of all the jobs that are being grouped together. We should also specify when each job should run. 

Note: Click on the picture to get an enlarged view. 

Note that the JOBGROUP is given a name (Z01071GP) and an ENDGROUP statement is used to define the end of definition. 

Like the JOB statement, positional parameters like accounting information, programmer's name can also be specified in the JOBGROUP statement. Additionally, we can provide 2 optional parameters in the JOBGROUP statement. Those are as follows:

ERROR=(condition)  - This parameter will define the error condition that the JOBGROUP needs to evaluate after the completion of each job in the group. 

The syntax of condition remains the same as used for the IF statement in JCL. In the picture shown above, ERROR=(ABEND) is being used and ABEND indicates that an ABEND condition occurred. 

ONERROR=STOP|SUSPEND|FLUSH - This parameter will define  the action that needs to be taken when an error occurs. 

When things are put together, ERROR=(ABEND),ONERROR=STOP instructs the JOBGROUP to evaluate each job's output after its completion; if there's an ABEND, STOP submitting new jobs from that JOBGROUP. Though, jobs that are actively running are allowed to complete.

An example showing the usage of JOBGROUP: 

Let's see how we can define the job names inside a JOBGROUP

Defining job names in a JOBGROUP

Each job name within a JOBGROUP is identified by GJOB statement. Job names should be unique. AFTER is positioned after a GJOB statement and it defines the dependency to another job. WHEN defines a condition associated with the dependency. 

In the picture shown above, the jobs are defined in such a way that Z01071A job runs first; Z01071B runs after Z01071A and only WHEN the return code of Z01071A is 0. Likewise, Z01071C job should run after Z01071B and only when the RC of Z01071B is 0. 

Like AFTER statement, BEFORE and CONCURRENT can be specified to define the order in which the jobs can run.  

The next step is to add the jobs to the JCL. Yup! You need to have the JOBGROUP definition and the jobs that are part of the JOBGROUP, in the same JCL. 

The complete JCL with JOBGROUP definition and its jobs.

As you can see from the picture above, all the jobs that are defined within a JOBGROUP is added after the ENDGROUP statement. Do you notice a SCHEDULE statement included below each JOB statement? This is used to assign the scheduling attributes and in this case, it is assigning the job to your group, Z01071GP. Also note that I've used IDCAMS to set the MAXCC to 16 in the second job. This is done deliberately and the reason for doing that, you'll witness shortly. It's time ⏰ to watch things in action. Let's see the outcome of this JCL after submission.

The JOBGROUP and its jobs are submitted.

Tada! 🧞 Just ignore the order the messages are being displayed. Z01071A and Z01071B jobs ran (one after another) and completed with MAXCC 0 πŸ‘. But, what happened to the third job? Z01071C job was flushed as the return code from Z01071B was not zero πŸ˜‘. As per the definition in the JOBGROUP, Z01071C should run only when Z01071B job issues a return code of 0

I hope you are now better off with JOBGROUP. Hang on and read a little bit as I'm about to unearth another useful feature of JOBGROUP i.e., the HOLDUNTL parameter.

How I used JOBGROUP at my site to prevent misses in running the SQL query? 

The requirement was to run the SQL SELECT query 2 times in a day through out that week. I defined a JOBGROUP with 10 jobs in it. Each job had the following steps:
Step 1: a DB2 UNLOAD step with the SQL SELECT query which would write the rows to an output dataset. 
Step 2: IDCAMS step to check for emptiness of the output file. RC=0 if the file is not empty. Else, RC=4. 
Step 3/4: Email step runs based on the return code from Step 2. If RC=0, an email is sent from Mainframe to folks who are on support for that week, notifying that there are rows to update in prod.   

All the 10 jobs were scheduled to run at a pre-defined time. Let me show you how we can do that. 
The SCHEDULE statement of the first job now have the HOLDUNTL parameter with which we can set a job to specify the date and time until which the job should be held. 

After submitting the JOBGROUP, the status of the first job, Z01071A shows HOLD, in spool.

If you traverse across columns from left to right, you'll find a column 'HoldUntil' which holds the Date and time we passed in the JCL

The job are exactly fired at the specified date and time. 


More about HOLDUNTL parameter and its syntax can be found here.

In my scenario, I added HOLDUNTL parameter in all the 10 jobs, with the date and time spanning across that whole week. 

Now, folks who were on support only had to monitor their inbox and when they received an email notification, saying there are rows on Prod table that needs a fix, they had to take an action to run the one-time job on Prod.  JCL's JOBGROUP feature was counted on running the SQL query without a miss πŸ˜ƒ.

All of these might sound like automating stuff . True, but only up to certain extent. We still needed manual intervention to run the one-time prod job with help from another team. 

Something is better than nothing!

Thanks for reading! Should you have any questions or suggestions, please drop them in the Comments section below.


References: IBM Knowledge Center
Screenshot courtesy: Screenshots were taken from MTM2020 system. 


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!