RPGLE %SCAN and %SCANRPL Reference

This page covers two RPGLE built-in functions: %scan, which finds where one string occurs inside another, and %scanrpl, which replaces every occurrence. It gives the arguments, what each function returns, and the mistakes that cause wrong positions or run-time errors.

It is a focused page. For the other string functions, use the RPGLE String BIFs Reference, and for worked parsing patterns read ILE RPG String Handling.

All examples are free-form RPGLE. Positions in a string start at 1.

%SCAN

%scan returns the first position of the search argument in the source string, or 0 if it is not found.

%scan(search : source {: start {: length}})
ArgumentMeaning
searchThe text to look for. It comes first
sourceThe string to search
startThe position to start searching from. Defaults to 1
lengthHow many characters of source to search, counted from start. Defaults to the rest of the string

The result is always a position in the whole source string, even when you give a start position. The first two arguments must be character, graphic or UCS-2 data, and start and length must be numeric with no decimal places. The comparison is exact, so 'D' does not match 'd'.

dcl-s source varchar(30) inz('Dr. Doolittle');
dcl-s pos int(10);

pos = %scan('oo' : source);              // 6
pos = %scan('D' : source : 2);           // 5, the first D from position 2
pos = %scan('D' : source : 2 : 3);       // 0, no D in positions 2 to 4
pos = %scan('D' : source : 2 : 4);       // 5, the D is now inside the range
pos = %scan('abc' : source);             // 0, not found

When any argument is varying length, the other arguments are checked against its current length, not its maximum.

Not found, and what to do with the result

A result of 0 means no match. Test it before you use it as a position.

dcl-s city varchar(30) inz('Toronto, Ontario');
dcl-s province varchar(30);
dcl-s comma int(10);

comma = %scan(',' : city);
if comma > 0;
  province = %trim(%subst(city : comma + 1));   // 'Ontario'
endif;

%subst(city : 0) is invalid, because the start position must be greater than zero. The guard is what keeps a missing comma from becoming a run-time error. The RPGLE %SUBST Reference covers the bounds rules.

Trailing blanks in a fixed-length search field

A fixed-length field is padded with blanks, and %scan searches for those blanks too. IBM’s own example makes the point.

dcl-s source varchar(30) inz('Dr. Doolittle');
dcl-s srchFld char(10) inz('Dr.');
dcl-s srchVar varchar(10) inz('Dr.');
dcl-s pos int(10);
dcl-s posTrim int(10);
dcl-s posVar int(10);

pos = %scan(srchFld : source);                 // 0, searches for 'Dr.' plus seven blanks
posTrim = %scan(%trimr(srchFld) : source);     // 1
posVar = %scan(srchVar : source);              // 1

Use %trimr on a fixed-length search field, or hold the search text in a varying-length field.

Find every match

%scan returns one position. To visit every match, search again from one position after the last one found. Stop when the result is 0, or when the match ended at the last character.

dcl-s text varchar(50) inz('a,b,,c');
dcl-s pos int(10);
dcl-s matches int(10) inz(0);

pos = %scan(',' : text);
dow pos > 0;
  matches += 1;                            // 3 by the end
  if pos = %len(text);
    leave;                                 // the match was the last character
  endif;
  pos = %scan(',' : text : pos + 1);
enddo;

The guard keeps the start position from running past the end of the string. For a search text longer than one character, start from pos + %len(search) to avoid overlapping matches.

Case-insensitive search

%scan has no ignore-case option in its syntax. IBM states that the scan opcode is case-sensitive and does not say the same about %scan on its function page, so treat %scan as an exact-case search. Convert both strings first.

dcl-s source varchar(30) inz('See NAME run');
dcl-s pos int(10);

pos = %scan(%upper('name') : %upper(source));    // 5

For single-byte data the conversion keeps the length, so the position is valid for the original string. %upper is documented in the IBM i 7.5 reference. IBM delivered %upper and %lower for 7.3 and 7.4 as PTFs (a compiler PTF and a runtime PTF), so on a system without them use %xlate.

%SCANRPL

%scanrpl returns the source string with every occurrence of the scan string replaced.

%scanrpl(scan : replacement : source {: scanstart {: scanlength}})
ArgumentMeaning
scanThe text to replace
replacementThe text to put in its place. It can be empty
sourceThe string to change
scanstartWhere scanning begins. Defaults to 1
scanlengthHow many characters to scan. Defaults to the rest of the string

The scan argument comes first, then the replacement, then the source. This differs from %scan, where the source is second. The function returns a new value and does not change the source. Text outside the scanned range is kept in the result.

dcl-s string1 varchar(50) inz('See NAME. See NAME run. Run NAME run.');
dcl-s string2 varchar(60);

string2 = %scanrpl('NAME' : 'Tom' : string1);
   // 'See Tom. See Tom run. Run Tom run.'
string2 = %scanrpl('NAME' : 'Tom' : string1 : 6);
   // 'See NAME. See Tom run. Run Tom run.'
string2 = %scanrpl('NAME' : 'Tom' : string1 : 1 : 31);
   // 'See Tom. See Tom run. Run NAME run.'
string2 = %scanrpl('NAME' : 'Tom' : string1 : 10 : 10);
   // 'See NAME. See Tom run. Run NAME run.'

The result can be longer, equal or shorter than the source, depending on the two lengths and the number of matches. Each position is scanned once, so with the scan string 'aa' and the source 'baaaaac', the matches are positions 2 and 3, then 4 and 5. With the replacement 'xy' the result is 'bxyxyac'.

An empty replacement removes the scan string.

dcl-s string3 varchar(30) inz('*Hello**There**Everyone*');
dcl-s cleaned varchar(30);

cleaned = %scanrpl('**' : '' : string3);         // '*HelloThereEveryone*'

The return value is varying length when the source and replacement have different lengths, or when any argument is varying length. Assign it to a field large enough for the longest possible result.

Compared with %SUBST, %REPLACE and %CHECK

NeedUse
Where a string occurs%scan
Replace every occurrence of a value%scanrpl
Replace the characters at a known position, or insert there%replace
Cut out part of a string at a known position%subst
Find the first character that is not on an allowed list%check

A common pair is %scan to find a delimiter and %subst to take the text on either side of it. For whole-field splitting, the %split built-in function is another option.

Character counting

By default, positions and lengths count bytes for alphanumeric data and double bytes for graphic and UCS-2 data. *NATURAL as the last argument counts characters instead, and *STDCHARSIZE forces the default. The same setting is available program-wide with the CHARCOUNT control keyword. It matters only for data where one character takes more than one byte, such as UTF-8.

Common mistakes

  • Putting the source first. The search text is the first argument.
  • Using the result without testing for 0.
  • Searching with a fixed-length field that carries trailing blanks.
  • Testing %found after %scan.
  • Expecting %scan to ignore case.
  • Assigning a %scanrpl result to a field too short for it.

FAQ

What does %SCAN return if the text is not found?

It returns 0.

Does %SCAN find all occurrences?

No. It returns the first position from the start position. Loop with a new start position to find the rest, as shown above, or use %scanrpl when you want to replace them all.

Can %SCANRPL replace only part of a string?

Yes. Give a scan start and a scan length. Matches outside that range are left alone.

Summary

  • %scan(search : source {: start {: length}}) returns a position, or 0.
  • %scanrpl(scan : replacement : source {: start {: length}}) replaces every occurrence and returns the new string.
  • Test for 0, trim fixed-length search fields, and convert case yourself when you need an ignore-case search.
  • The family overview is the RPGLE String BIFs Reference, and the companion page for extracting text is the RPGLE %SUBST Reference.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top