Fri, Jul
4 New Articles

The CL Corner: A CL Command to Scan for Characters

  • Smaller Small Medium Big Bigger
  • Default Helvetica Segoe Georgia Times

Find characters within a variable.

Last month, in "A CL-Based Implementation of RPG Built-in %Check," we built the command Check Characters (CHKCHR). The CHKCHR command returns the position of the first character within a CL variable that is not found within a list of valid values. Based on reader requests, this month we'll look at how to implement a Scan Characters (SCNCHR) command. The SCNCHR command will return the position of the first character within a CL variable that is found in a list of valid values.


Note that SCNCHR is not the same as the RPG built-in %Scan. If you were to specify %scan('abc' :'a cabbage') in an RPG program, the %Scan built-in would return a value of 0 as the string 'abc' is not found within the string 'a cabbage'. The SCNCHR command on the other hand, which is patterned after last month's CHKCHR command, would return a value of 1. This is because SCNCHR will be looking for the first occurrence of an 'a', a 'b', or a 'c' within the string 'a cabbage'. In a future article, we will look at how to build a CL command (perhaps named Scan for String (SCNSTR)) that will more closely mimic the behavior of RPG's %Scan built-in.

The SCNCHR Command

As we did last month, we'll start by providing a command definition for the SCNCHR command:

Cmd       Prompt('Scan for Characters')                

Parm       Kwd(Chars)     Type(*Char) Len(192) Min(1) +

             Prompt('Characters to search for')          

Parm       Kwd(Base)       Type(*Char) Len(1024) Min(1) +

             Prompt('String to be scanned')              

Parm       Kwd(Pos)       Type(*UInt4)         Min(1) +

             RtnVal(*Yes) +                              

             Prompt('Position of first match')        

Parm       Kwd(StrPos)     Type(*UInt4)         Dft(1) +

             Rel(*Gt 0) +

             Prompt('Starting position within Base')        

The first three parameters are defined in a manner very similar to what was used for last month's CHKCHR command and will not be further discussed. The fourth parameter however, Starting position (or StrPos), is new. This parameter allows you to specify the starting position, within the Base variable, for where the scan should start. The default value for the StrPos keyword is 1 (start at the initial character of the string identified by the Base keyword), and the value specified must be greater than 0. You will soon see that another check, to verify that the starting position is not greater than the length of the Base string, is done within the command processing program (CPP) of the SCNCHR command.

Assuming that the previous command source is stored in member SCNCHR of source file QCMDSRC, you can create SCNCHR with the following command:

CrtCmd Cmd(ScnChr) Pgm(ScnChrCPP) Allow(*BPgm *IPgm)

This command indicates that the CPP of SCNCHR is program SCNCHRCPP and that the command can be run from within a program. As with CHKCHR, you will not be able to run the SCNCHR command interactively from a command line due to the Pos parameter being a return value.

Prior to looking at the source of our CPP, the ScnChrCPP program is going to be using a new command—Retrieve Variable Length (RtvVarLen)—which will return the blank trimmed length of the Base string passed to the ScnChr command. Following is the command and CL source for the RTVVARLEN command.


Cmd       Prompt('Retrieve Variable Length')        

Parm       Kwd(Var)   Type(*Char) Len(5000) Min(1) +

             Vary(*Yes *Int4) Prompt('Variable name')

Parm       Kwd(Length) Type(*UInt4) RtnVal(*Yes) +  

             Min(1) Prompt('Variable length')        

Assuming the command source is stored in member RTVVARLEN of source file QCMDSRC, you can create the command using this command:

CrtCmd Cmd(RtvVarLen) Pgm(RtvVarLen) Allow(*BPgm *IPgm)

A RTVVARLEN Command Processing Program

Pgm       Parm(&Var &Length)                          

Dcl       Var(&Var) Type(*Char) Len(5000)            

Dcl       Var(&VarLength) Type(*UInt) Stg(*Defined) +

             Len(4) DefVar(&Var)                      

Dcl       Var(&Length) Type(*UInt)                    


ChgVar     Var(&Length) Value(&VarLength)              


Assuming the preceding CL source is stored in member RTVVARLEN of source file QCLSRC, you can create the RTVVARLEN CPP using this command:

CrtBndCL Pgm(RtvVarLen)

The ability to build a command such as RTVVARLEN was alluded to in the article "Just How Big Is That Variable?" back in October of 2008. For a description of how to access both character variable size and length information, refer to that earlier article. Note that we could have simply defined the Base parameter of the SCNCHR command as Vary(*Yes *Int4) and directly accessed the blank trimmed length of &Base, but I thought providing a general-purpose command such as RTVVARLEN might be more useful to some readers.

With the RTVVARLEN command behind us, below is the source for our CPP SCNCHRCPP.

The SCNCHRCPP Command Processing Program

Pgm       Parm(&Chars &Base &Pos &StrPos)                        

Dcl       Var(&Chars)     Type(*Char) Len(192)                  

Dcl       Var(&Base)       Type(*Char) Len(1024)                

Dcl       Var(&Pos)       Type(*UInt)                          

Dcl       Var(&StrPos)     Type(*UInt)                          


Dcl       Var(&Null_Chars) Type(*Char) Len(193)                  

Dcl       Var(&Null_Base) Type(*Char) Len(1025)                

Dcl       Var(&Null_Char) Type(*Char) Len(1)   Value(x'00')    

Dcl       Var(&DataToMove) Type(*UInt)                          


Dcl       Var(&MsgDta)     Type(*Char) Len(8)                    

Dcl       Var(&MsgStrPos) Type(*UInt) +                        

             Stg(*Defined) DefVar(&MsgDta 1)                    

Dcl       Var(&Len)       Type(*UInt) +                        

             Stg(*Defined) DefVar(&MsgDta 5)                    


RtvVarLen Var(&Base) Length(&Len)                                  

If         Cond(&StrPos > &Len) Then(Do)                            

           ChgVar Var(&MsgStrPos) Value(&StrPos)                    

           SndPgmMsg MsgID(ESC0003) MsgF(OurMsgs) +                

             MsgDta(&MsgDta) MsgType(*Escape)                        



ChgVar     Var(&Null_Chars) Value(&Chars *TCat &Null_Char)          


ChgVar     Var(&DataToMove) Value(&Len - &StrPos + 1)              

ChgVar     Var(&Null_Base) +                                      

             Value(%sst(&Base &StrPos &DataToMove) *Cat &Null_Char)

CallPrc   Prc('strcspn') Parm((&Null_Base) (&Null_Chars)) +        



If         Cond(&Pos = &DataToMove) Then( +                          

             ChgVar Var(&Pos) Value(0))                

Else       Cmd(ChgVar Var(&Pos) Value(&Pos + &StrPos))



Assuming that the preceding source is stored in member SCNCHRCPP of source file QCLSRC, you can, if your system is V6R1 or higher, use the following command to create the SCNCHRCPP program:

CrtBndCL Pgm(ScnChrCPP)

If your system is V5R4, you will need to use the two following commands to create the SCNCHRCPP program:

CrtCLMod Module(ScnChrCPP)

CrtPgm Pgm(ScnChrCPP) BndDir(QC2LE)

If your system is earlier than V5R4, then the preceding source will not compile successfully due to the use of *Defined storage. You will need to change the source shown to remove the use of defined storage, which can be done, but I'm not going to show the necessary changes in this article as it's time for you to move to V5R4 (or later).

While the general flow of SCNCHRCPP is similar to the source of last month's CHKCHRCPP program, there are some significant differences. With two exceptions, the changes are related to our support of the STRPOS keyword. The two exceptions are 1) some rather trivial changes to variable names (for instance, &Comparator to &Chars) and 2) SCNCHRCPP is calling the Find Offset of First Character Match (strcspn) API rather than the strspn API used last month. Documentation for the strcspn API can be found here.

In order to support the STRPOS function of the SCNCHR command, the following changes to last month's CHKCHRCPP program have been made:

A fourth parameter, &STRPOS, has been added to the PGM command. &StrPos is defined as an unsigned integer.

The structure &MsgDta has been added. This structure is used to provide replacement data within an error message when the user-specified starting position (&StrPos) exceeds the length of the Base string being passed to SCNCHRCPP. The two variables included in the message are the user-specified starting position (&MsgStrPos) and the actual length of the Base string (&Len). The message that will be sent is ESC0003. To create this message into message file OURMSGS (a message file we've used in past articles), you can use the following command.

AddMsgD MsgID(ESC0003) MsgF(OURMSGS) +

Msg('STRPOS value of &1 is greater than the length of BASE value (&2)') +

Fmt((*UBin 4) (*UBin 4))

In terms of processing, SCNCHRCPP first uses the RTVVARLEN command to determine the blank-trimmed length of the parameter &Base. If the &StrPos parameter value is greater than this length, then the ESC0003 escape message we created earlier is sent and the program ends.

The next change is related to determining the characters of &Base that are to be scanned. In order to substring out the relevant characters of &Base, SCNCHRCPP first calculates the number of characters to scan. This is done by subtracting the STRPOS value from the blank-trimmed length of variable &Base (&Len), adding 1, and then setting this value to the variable &DataToMove. The program then sets the variable &Null_Base to the substring of &Base, starting at &StrPos and for a length of &DataToMove, concatentated with a null byte (&Null_Char). Last month's program, CHKCHRCPP, used the *TCat operation when concatenating &Null_Char, and while we could have used *TCat in SCNCHRCPP, it would also be a bit wasteful. As we already know the blank-trimmed length (&Len) and that's all we're substringing out of &Base, there is no need for *TCat to recalculate the blank-trimmed length. So we simply use *Cat.

Having set &Null_Base to the null-terminated data that is to be scanned, CHKCHRCPP calls the strcspn API. The API returns the relative location of the first match within &Null_Base to any character that is found in the variable &Null_Chars. If the returned value &Pos is equal to the length of the scanned string (&DataToMove), then no matching characters were found and the SCNCHR command returns a value of 0 for the variable associated with the SCNCHR POS keyword. Otherwise, SCNCHR returns the location within &Base where the first match was found. Note that this returned location is relative to the original value of &Base, not to the user-specified &StrPos starting position. Returning the original relative position is in keeping with the RPG built-ins %Scan and %Check and is accomplished by simply adding &StrPos to the strcspn returned &Pos value.

Testing the SCNCHR Command

Similar to last month's USECHKCHR test program, here is a test program for the SCNCHR command.

Pgm       Parm(&Chars_In &Base_In &StrPos_In)              

Dcl       Var(&Chars_In) Type(*Char) Len(32)              

Dcl      Var(&Base_In)   Type(*Char) Len(32)              

Dcl       Var(&StrPos_In) Type(*Dec)                        


Dcl       Var(&Pos)       Type(*UInt)                      

Dcl       Var(&Pos_Char) Type(*Char) Len(5)                


ScnChr     Chars(&Chars_In) Base(&Base_In) Pos(&Pos) +      


           MonMsg MsgID(ESC0003) Exec(Do)                    

             SndPgmMsg Msg('Invalid starting position') +    




If         Cond(&Pos *NE 0) Then(Do)                        

             ChgVar     Var(&Pos_Char) Value(&Pos)        

             SndPgmMsg Msg('Character ' *Cat +          

                           %sst(&Base_In &Pos 1) *Cat +  

                           ' found at ' *Cat +          

                           &Pos_Char) +                  



Else       Cmd(SndPgmMsg Msg('No characters found') +    



The program is based on USECHKCHR but does have the addition of a MONMSG for message ESC0003.

Assuming that the preceding source is stored in member USESCNCHR of source file QCLSRC, then you can use the following command to create the USESCNCHR program.

CrtBndCL Pgm(UseScnChr)

From the command line, we can now test a few scenarios.

Entering the command Call UseScnChr ('abc' 'cabbage' 1) will result in the message 'Character c found at 00001' as the first character of 'cabbage' (the 'c') is in the list of scanned for values ('abc').

Entering the command Call UseScnChr ('abc' 'cabbage' 6) will result in the message 'No characters found' as the sixth and seventh characters of 'cabbage' ('ge') are not within the list of scanned for characters ('abc').

Entering the command Call UseScnChr ('abc' 'cabbage' 100) will result in the message 'Invalid starting position' as the string 'cabbage' is only seven characters in length and we asked for a starting position of 100 (which doesn't exist).

Entering the command Call UseScnChr ('abc' 'a cabbage' 6) will result in the message 'Character b found at 00006'. While there is also a 'b' in the fifth position of 'a cabbage' that 'b' is bypassed due to the starting position being six.

To provide support for a starting position within last month's CHKCHR command, you will need to retrofit the changes found in the SCNCHR command and SCNCHRCPP program to the CHKCHR command and CHKCHRCPP program.

More CL Questions?

Wondering how to accomplish a function in CL? Send your CL-related questions to me at This email address is being protected from spambots. You need JavaScript enabled to view it..

Bruce Vining

Bruce Vining is president and co-founder of Bruce Vining Services, LLC, a firm providing contract programming and consulting services to the System i community. He began his career in 1979 as an IBM Systems Engineer in St. Louis, Missouri, and then transferred to Rochester, Minnesota, in 1985, where he continues to reside. From 1992 until leaving IBM in 2007, Bruce was a member of the System Design Control Group responsible for OS/400 and i5/OS areas such as System APIs, Globalization, and Software Serviceability. He is also the designer of Control Language for Files (CLF).A frequent speaker and writer, Bruce can be reached at This email address is being protected from spambots. You need JavaScript enabled to view it.. 

MC Press books written by Bruce Vining available now on the MC Press Bookstore.

IBM System i APIs at Work IBM System i APIs at Work
Leverage the power of APIs with this definitive resource.
List Price $89.95

Now On Sale



Support MC Press Online

$0.00 Raised:

Book Reviews

Resource Center

  • SB Profound WC 5536 Have you been wondering about Node.js? Our free Node.js Webinar Series takes you from total beginner to creating a fully-functional IBM i Node.js business application. You can find Part 1 here. In Part 2 of our free Node.js Webinar Series, Brian May teaches you the different tooling options available for writing code, debugging, and using Git for version control. Brian will briefly discuss the different tools available, and demonstrate his preferred setup for Node development on IBM i or any platform. Attend this webinar to learn:

  • SB Profound WP 5539More than ever, there is a demand for IT to deliver innovation. Your IBM i has been an essential part of your business operations for years. However, your organization may struggle to maintain the current system and implement new projects. The thousands of customers we've worked with and surveyed state that expectations regarding the digital footprint and vision of the company are not aligned with the current IT environment.

  • SB HelpSystems ROBOT Generic IBM announced the E1080 servers using the latest Power10 processor in September 2021. The most powerful processor from IBM to date, Power10 is designed to handle the demands of doing business in today’s high-tech atmosphere, including running cloud applications, supporting big data, and managing AI workloads. But what does Power10 mean for your data center? In this recorded webinar, IBMers Dan Sundt and Dylan Boday join IBM Power Champion Tom Huntington for a discussion on why Power10 technology is the right strategic investment if you run IBM i, AIX, or Linux. In this action-packed hour, Tom will share trends from the IBM i and AIX user communities while Dan and Dylan dive into the tech specs for key hardware, including:

  • Magic MarkTRY the one package that solves all your document design and printing challenges on all your platforms. Produce bar code labels, electronic forms, ad hoc reports, and RFID tags – without programming! MarkMagic is the only document design and print solution that combines report writing, WYSIWYG label and forms design, and conditional printing in one integrated product. Make sure your data survives when catastrophe hits. Request your trial now!  Request Now.

  • SB HelpSystems ROBOT GenericForms of ransomware has been around for over 30 years, and with more and more organizations suffering attacks each year, it continues to endure. What has made ransomware such a durable threat and what is the best way to combat it? In order to prevent ransomware, organizations must first understand how it works.

  • SB HelpSystems ROBOT GenericIT security is a top priority for businesses around the world, but most IBM i pros don’t know where to begin—and most cybersecurity experts don’t know IBM i. In this session, Robin Tatam explores the business impact of lax IBM i security, the top vulnerabilities putting IBM i at risk, and the steps you can take to protect your organization. If you’re looking to avoid unexpected downtime or corrupted data, you don’t want to miss this session.

  • SB HelpSystems ROBOT GenericCan you trust all of your users all of the time? A typical end user receives 16 malicious emails each month, but only 17 percent of these phishing campaigns are reported to IT. Once an attack is underway, most organizations won’t discover the breach until six months later. A staggering amount of damage can occur in that time. Despite these risks, 93 percent of organizations are leaving their IBM i systems vulnerable to cybercrime. In this on-demand webinar, IBM i security experts Robin Tatam and Sandi Moore will reveal:

  • FORTRA Disaster protection is vital to every business. Yet, it often consists of patched together procedures that are prone to error. From automatic backups to data encryption to media management, Robot automates the routine (yet often complex) tasks of iSeries backup and recovery, saving you time and money and making the process safer and more reliable. Automate your backups with the Robot Backup and Recovery Solution. Key features include:

  • FORTRAManaging messages on your IBM i can be more than a full-time job if you have to do it manually. Messages need a response and resources must be monitored—often over multiple systems and across platforms. How can you be sure you won’t miss important system events? Automate your message center with the Robot Message Management Solution. Key features include:

  • FORTRAThe thought of printing, distributing, and storing iSeries reports manually may reduce you to tears. Paper and labor costs associated with report generation can spiral out of control. Mountains of paper threaten to swamp your files. Robot automates report bursting, distribution, bundling, and archiving, and offers secure, selective online report viewing. Manage your reports with the Robot Report Management Solution. Key features include:

  • FORTRAFor over 30 years, Robot has been a leader in systems management for IBM i. With batch job creation and scheduling at its core, the Robot Job Scheduling Solution reduces the opportunity for human error and helps you maintain service levels, automating even the biggest, most complex runbooks. Manage your job schedule with the Robot Job Scheduling Solution. Key features include:

  • LANSA Business users want new applications now. Market and regulatory pressures require faster application updates and delivery into production. Your IBM i developers may be approaching retirement, and you see no sure way to fill their positions with experienced developers. In addition, you may be caught between maintaining your existing applications and the uncertainty of moving to something new.

  • LANSAWhen it comes to creating your business applications, there are hundreds of coding platforms and programming languages to choose from. These options range from very complex traditional programming languages to Low-Code platforms where sometimes no traditional coding experience is needed. Download our whitepaper, The Power of Writing Code in a Low-Code Solution, and:

  • LANSASupply Chain is becoming increasingly complex and unpredictable. From raw materials for manufacturing to food supply chains, the journey from source to production to delivery to consumers is marred with inefficiencies, manual processes, shortages, recalls, counterfeits, and scandals. In this webinar, we discuss how:

  • The MC Resource Centers bring you the widest selection of white papers, trial software, and on-demand webcasts for you to choose from. >> Review the list of White Papers, Trial Software or On-Demand Webcast at the MC Press Resource Center. >> Add the items to yru Cart and complet he checkout process and submit

  • Profound Logic Have you been wondering about Node.js? Our free Node.js Webinar Series takes you from total beginner to creating a fully-functional IBM i Node.js business application.

  • SB Profound WC 5536Join us for this hour-long webcast that will explore:

  • Fortra IT managers hoping to find new IBM i talent are discovering that the pool of experienced RPG programmers and operators or administrators with intimate knowledge of the operating system and the applications that run on it is small. This begs the question: How will you manage the platform that supports such a big part of your business? This guide offers strategies and software suggestions to help you plan IT staffing and resources and smooth the transition after your AS/400 talent retires. Read on to learn: