DrDialog, or: How I learned to stop worrying and love REXX - Part 12

From EDM2
Jump to: navigation, search

By Thomas Klein

This month's article is the last one that will deal with REXX itself. By now, you should know a little more about how REXX works and what can be achieved with it. Today, we'll take a look at some of the most important and commonly used functions. The next parts of the series will get us back into dealing with DrDialog. Some parts that deal with REXX will be discussed within the DrDialog subject like basic File-I/O, or the basic use of the parse statement. If time allows and people are interested, we'll have 'specials' on PARSE, stream handling and all that stuff we didn't discuss so far. But my intention is to get you back to a sample application in DrDialog, this is why we'll stop talking 'REXX only' by concluding with this issue.

Commonly used REXX functions

Well, of course there's a lot more to REXX functions than what will be contained in here, but... the following functions are those that you'll either use mostly in your programs or those that'll help you solve common programming issues.


Although these ones are mostly used to simply retrieve the current date and time strings from the machine's internal clock, the TIME function can be used to act as a stop watch... but let's take a look at the DATE syntax diagram first:

result = DATE( [ option ] )

The short story: If you run DATE without options, it'll return the current date using the form DD MMM YYYY (with MMM being the first three letters of the months name). If run on the 4th of July 2004, it would give you 04 Jul 2004. (Note the spaces between the date parts). While this might look 'good' to humans, you won't be able to perform anything like sort actions with that format nor could you use it as part of file or directory names for example and other countries have different date formats they might prefer. This is where the option comes in:

Option Meaning / Result Example for February 27, 2004
<none> Current date as DD monthabbreviation YYYY 27 Feb 2004
normal Current date as DD monthabbreviation YYYY 27 Feb 2004
basedate Number of days elapsed since January 1st, 0001 731637
ordered Current date as YY/MM/DD 04/02/27
sorted Current date as YYYYMMDD 20040227
USA Current date as MM/DD/YY 02/27/04
European Current date as DD/MM/YY 27/02/04
language Current date according to language setting; defaults to DD monthname YYYY 27 February 2004
days Number of days elapsed since start (Jan. 1st) of the current year 58
month Complete english name (even on localized systems) of the current month February
weekday Complete english name (even on localized systems) of the current weekday Friday

Note that you only need to specify the first character of the option and that it's not case sensitive (thus, you can use 'Sorted', 'S', 's', 'sORted', etc.). In addition, you don't need to enclose the option string in quotes.

That's a lot of options for such a simple function. Unfortunately, you can't supply it a date string that it should work upon - it'll always use the current system date. Thus, if you want to perform date calculations or formatting with a user-specified date string, I'll recommend the 'datergf' package by Rony G. Flatscher which is publicly available (on Hobbes for example). This is a REXX script (not a DLL) that you can use to perform almost each and every date algorithm you might imagine like

  • 'real' week number calculation (according to German DIN standard)
  • retrieve date of week/quarter/semester begin for a given date
  • date difference calculation
  • time functions and time difference calculation

and much more besides what the date function does too, all by considering leap years and so on... absolutely a must-have for doing date stuff in REXX.

Time for TIME

The time function works in the same way like what we said about DATE above: It uses a single (optional) option string. But besides returning different formats for the current system time (as date does for the current system date), it also offers an easy to use stop-watch feature! Here we go with the syntax diagram and options explanation table:

result = TIME( [ option ] )

Again, here's for the short story: If you run TIME without options, it'll return the current system time using the form HH:MM:SS using a 24 hour-scheme. If run at 4:17 pm (and 32 seconds) it would thus give you 16:17:32. And again options are used to return a different format that might be preferred in other countries or required to solve specific programming issues:

Option Meaning / Result Example for 4:17pm, 32 seconds
<none> Current time as HH:MM:SS 16:17:32
normal Current time as HH:MM:SS 16:17:32
civil am) 4:17pm
long Current time as HH:MM:SS.hh0000 (hh being the hundredths of seconds) 16:17:32.450000 (32 sec, 45/100 sec)
hours Current amount of hours elapsed since midnight 16
minutes Current amount of minutes elapsed since midnight 977
seconds Current amount of seconds elapsed since midnight 58652

Remember what we said about option notation for the date function? The same applies here: You only need to specify the first character of the option and the case doesn't matter. But stop - there are two additional options for controlling the built-in stop-watch feature of time:

Option Meaning / Result Example for 4:17pm, 32 seconds
elapsed number of seconds and hundredths of seconds elapsed since the timer was started or reset
Format of returned value is sssssssss.hh0000 (s being the seconds, hh being the 1/100 )
first call since boot: 0
else: seconds since start/reset
reset number of seconds and hundredths of seconds elapsed since the timer was started or reset
Format of returned value is sssssssss.hh0000 (s being the seconds, hh being the 1/100 )
In addition, the timer is reset to 0.
first call since boot: 0
else: seconds since start/reset

Now this might require some additional information...

When the program is started, the internal timer is off an set to 0. The first "stop-watch" call to the time function (either "elapsed" or "reset") will simply start the timer and return 0. Any subsequent call will now return the time elapsed since the timer was started. While "elapsed" only returns the elapsed time, "reset" in addition will rest the timer to 0.


time event result
09:02:58 computer was started
09:17:15 .00 time("e") is called returned value of time() function: 0
timer started
09:17:25 .75 time("e") is called returned value of time() function: 10.750000
09:17:35 .75 time("r") is called returned value of time() function: 20.750000
timer reset to 0
09:17:45 .75 time("e") is called returned value of time() function: 10.000000

Note that there is no call to "stop" the timer. The timer simply runs on and on and on... either, until it is reset, the program is shut down or the seconds will increase to more than 999999999.990000 - which is approximately about 31.7 years. I won't mind that if we were Windows users, because there simply is no windows machine able to run 31.7 years without the need for a reboot... you already have to reboot it every two years in order to install the new version... but hey: This is OS/2. So be careful when you trigger the timer and leave your program running! ;)


This functions is quite useful when dealing with drive letters and file and path names. Given a specific filename, it'll return you the drive letter, the path or the file name including the extension (if any) depending on the option specified. Here's the syntax diagram:

result = FILESPEC( option , <filename> )

Option is either "drive" , "path" or "name" (including the quotes). Again, this can be abbreviated to the first letter only like in

SAY FILESPEC( "p", myfilename )

Compared to most of the functions that are coded manually and are used to split up a file name, FILESPEC perfectly handles situations with "unusual" file names. UNC-style file names for example (having a leading double backslash) are recognized, as well as file and path names that contain blanks. Also, root-based files like "c:\myfile" are returned a path of "\" instead of something like <nothing> or "\myfile". ;) If you ever tried to code such a function by your own, you might know the possible pitfalls it can contain... and you'll like the benefits of filespec.


If called without any parameter, it will tell (return) you the current directory:

say directory()


currdir = directory()

Optionally, you can use it to change the current directory by specifying the directory you want to change to. directory() will then first change the directory and then return the current directory's name if the previous change was successful (i.e. the directory exists):

newdir = directory("\mmos2")
say newdir

If the directory exists, you'll get the name of it in return.
Note that in contrast to the simple "CD" command, directory() supports drive letters - thus, you don't need to change the drive first.
Note too, that directory() doesn't simply return the name of the directory changed to "like it is", but in the same way that you specified it in the call:

say directory("c:\MPtn\dLl")

will display c:\MPtn\dLl (if the directory exists), instead of its possible "real" name C:\mptn\DLL. Thus, directory() can easily be used to check if a directory exists by simple comparison of the return value without any modifications (like uppercase conversion, etc.). Of course there's other ways of performing such a check, but this is just a code example (if you want to retype this, omit the leading line numbers - they're just for referencing later):

1    currdir = directory()
2    newdir = "S:\OS2Image\SomeDir"
3    if directory(newdir) = newdir then
4       say "directory exists"
5    else
6       say "directory does not exist"
7    call directory(currdir)

Line 1 is used to retrieve the current directory in order to re-change there after the check is done. Line 2 stores the directory name that we want to check in a variable called 'newdir'. Lines 3 to 6 represent the actual check with line 3 calling the directory() function and comparing its return value with the directory name we wanted to check. The last line (7) finally changes back to the directory that was the current one before the whole thing started. In addition it shows again, how a function can simply be "called" without having to deal with any return value.
Actually we only need to do the final re-change in case that the check was successful, because otherwise (if the newdir doesn't exist), we would still be in the current directory. But hey - code tuning is for advanced REXX programmers and we're doing a beginners lesson here. ;)

One final note: Make sure you don't use a trailing backslash in the name of the directory you want to change to (or check), because directory() doesn't like it. Again, directory() doesn't behave like the "CD" command, which won't complain about the trailing backslash...

...is used to set and/or retrieve values for a variable. This applies to variables of the REXX environment (your program for example) as well as the OS/2 environment. In latter case, value() deals with so-called environment variables of OS/2. An environment variable basically is defined by a SET <variable> = <value> command in config.sys. But let's first take a look at the command syntax:

result = VALUE( <symbol> , [newvalue], [<environment>] )

Okay - the newvalue parameter is optional. This means that if you don't want to alter a symbols (variables) value, you simply omit that parameter. The <environment> parameter is used to tell the value function, which environment to "search" for the specified variable. If this parameter is omitted, value() will check the REXX environment. If "os2environment" is specified, value() will deal with OS/2's environment variables. Take a look at the following code to get a clue:

/* REXX code sample */
path = "C:\Apps\MyApps\RexxCode\Sample1"
say value("path")
say value("path",,"os2environment")

Your program might contain a variable called "path" to store a specific directory name for whatever reason. As you might know, OS/2 itself uses - amongst others - a "path" variable which is used to scan for executables if they are not found in the current directory. The first say command above will retrieve the program internal path variable and thus display C:\Apps\MyApps\RexxCode\Sample1. The second say command simply uses the OS/2 environment to "resolve" the variable name and thus will display the current path environment variable setting... something like C:\MPTN\BIN;C:\IBMCOM;C:\OS2\emx\bin;C:\IBMLAN\NETPROG;C:\MUGLIB;... and so on, depending on your systems path.

As you might imagine, value() thus could be used to change environment variable settings of OS/2 by using the "os2environment" parameter and additionally specifying a new value. Yes, this can be done, but if you're interested in this kind of stuff, take an additional look at the SETLOCAL and ENDLOCAL functions. The use of this functions also is demonstrated in the VALUE() function help text of rexx.inf.
You might wonder about the necessity to use value() to retrieve a variables value in your REXX program. value() does not actually retrieve a variables name, but rather evaluates an expression which then is used. Take a look at the following code:

/* another REXX code sample */
xyz = "This is the content of variable 'xyz'..."
myvar = "xyz"

Now, if you would code

say myvar

it'll display


That's nothing special. But...: If you would code

say value(myvar)

it'll display

This is the content of variable 'xyz'...

because value() will first evaluate the contents of myvar1 (which is xyz) and then retrieve the contents of the variable named xyz. But watch your step: If you code the following instead (note the quotes!):

say value("myvar")

it will simply display


Why? Because you gave it a literal string (by using the quotes) instead of a symbol. Got it?

This is the function that you might rely upon when programming some kind of game in REXX. Simply drop it a minimum and maximum value and it'll return you a random number from that range. To simulate rolling a dice, all to have to do is:

diceval = random(1,6)

Great huh? But there's something even more interesting about that function. A base number used for generating the random numbers. In order to understand what this is used for, I suggest you fire up Mahjongg Solitaire. You know that the tile positions are randomly generated - right?. So far, so good. But try this: Start a new game and select a game number. Look at the tile positions, then start over a different game, then restart a new game and select the same number from the first game again: The tile positions are again the same. How do they do it? Do they use a predefined list of games with tile positions? Naaahh... they use the base number "trick":
If you don't specify a base number, the random number each time is generated randomly indeed. But if you give it a base number, the randomized numbers are rather determined mathematically for all subsequent calls, which leads to a predictable (or say "reproducible") sequence of numbers for that base number. For instance:

/* random with base number */
sequence = random(1,6,basenum)
do 69
   sequence = sequence || random(1,6)
say sequence

Paste the above code in an empty editor window and save it as randtest.cmd (and use plain-text format for instance if requested). Then run it. Run it again. And again. It will show you a random sequence of digits ranging from 1 to 6. But the sequences will always match. This is what the base number does. If you want to check what will be happening, you can then remove the basenumber parameter from the first random function call to read it

sequence = random(1,6)

like the subsequent call and re-run the script a few times. Now it'll always show different sequences. Once a base number was specified in you program, all subsequent calls to the random function will work based on that number, as long as you don't specify a different base number (which again could be a random number).
What the above code does: It starts with an empty string ("sequence") which will be added a new number by the first call to random. The following do will generate 60 other random numbers (without a base number) which are appended to the string one after another. Finally, the string is displayed. And here's the syntax diagram of random:

syntax 1:

result = RANDOM( <min>, <max>, [<basenumber>] )

syntax 2:

result = RANDOM( <max>, [<basenumber>] )

In the second syntax scheme, it'll use zero as the min parameter. Thus the following two lines of code have the same meaning:

result = RANDOM(100)
result = RANDOM(0,100)

Common REXXutil functions

There are many reasons why REXX is an incredibly versatile programming language. One of the most important ones is, that REXX's capabilities can easily be extended by using external function libraries. There are lots of function libraries available for REXX in OS/2, for almost each and every purpose - and most of them are available for free. And there's one library that already comes with OS/2 (or eComStation) right from the start: rexxutil.dll. Rexxutil provides various functions, but we'll focus on a subset here: Those that you might require most.

In order to use functions contained in external libraries from within REXX, you need to "load" them first (make them available to REXX). Before going on, here's a short introduction about "libraries": Libraries contain one or more functions and maybe other stuff like text strings, bitmaps and so. Programs actually don't use libraries - they rather use functions or resources which are contained in that library. This results in a request to load the library into memory. Such requests thus are originated by a program, but are processed and handled by the operating system. This enables the operating system to load a library only once, even it was requested multiple times from different programs: If the library is already loaded by one program, it won't be loaded again if another program requests a load. Instead, the operating system will create another "instance" of the library. Such an instance basically is an application-specific memory region to store the data shared between one program and the library. From the operating system viewpoint, a library consists of the library "stem" which contains the data and... ehh... well, let's call it a "data instance" that is established for each requester (program). The idea behind this is that it saves memory and thus improves performance, because the library "stem" is loaded only once. The actual parts used by one specific requester program are handled by the instance. If five programs are running, all using a specific library (by using any one function or resource of it), the library "stem" is loaded only once and five "data" instances are created instead of loading the entire stiff five times (which would equal the same space as five "stems" plus five "data instances").

Okay, let's get back to where we were: Loading functions from libraries. REXX provides a set of built-in functions to deal with external functions:

This loads an external function from a library.

result = RXFUNCADD(<my-funcname>,<dll-name>,<dll-funcname>)

The result is simply a return code that'll tell you if the function was loaded successfully or not. A result of 0 (zero) will mean that loading was successful, anything else means that there was a problem. While <dll-name> is the name of the library (without the ".dll" - in our case it would be 'rexxutil') the <dll-funcname> specifies the function to be loaded from the library. This must be the name that the library uses to identify its contained functions. The first parameter <my-funcname> tells REXX the name that you want to use for the function in your program (in your CALL to the function that's to say). In general, you should use the same name as the <dll-funcname> in order to keep things clear. But at least note that you're free to use a different name for it. The drawback with it is, that other programs don't know how YOU called that function and thus can't check if it was already loaded. Thus you should always use the <dll-funcname> in your <my-funcname> as well. This is what most REXX programmers do and thus provides a convenient way of checking (see also RxFuncQuery).
Note that if you don't want to, you are not forced to use the result (return code). This is the same as for all other functions in REXX - in this case, use the following notation:

call RXFUNCADD <my-funcname>,<dll-name>,<dll-funcname>

Note that the function parameters are not enclosed in brackets when using this type of call (as always).

This unloads a previously loaded function from memory. Actually it unloads the instance only and the operating system will unload the library, if no one uses it any more...

result = RXFUNCDROP(<my-funcname>)

The same we said for the result of RxFuncAdd above applies to RxFuncDrop as well. Zero means okay, anything else means trouble. Note that you have to use the same name here that you used for <my-funcname> in the RxLoadFunc.

Now there's a lot of people telling you that in order to optimize performance and stability, you should check if the function was already loaded prior to do the load request and - in return - not to request to unload the library if a load wasn't necessary. This is what RxFuncQuery is used for: It checks if an external function was already loaded "somewhere else". We'll talk about that, yes, but let me just tell you that as far as I'm concerned, this is "advanced" optional stuff that you might neglect for now and get back to once you know how to deal with REXX and your programming issues are solved. ;) Nevertheless, here's its syntax:

result = RXFUNCQUERY(<name>)

When a function was registered (loaded) by one program, the associated <my-funcname> is kind of posted to a REXX-internal list of functions which is shared among the REXX programs. Thus the programs can check whether "someone else" already loaded a function of that name and save the work of loading it themselves. The problem with function names again is that if you load a function giving it some arbitrary name (different from its dll-internal name), this name might be used by another program which might get the picture of its library already being loaded. But as the function loaded is completely different from what the second program expects... oops. This is why you should try to load functions with their dll-internal name.

One last comment about the function stuff: As a beginner in REXX, you should know how to load libraries/functions with RxFuncAdd and simply forget the rest for know. If your program loads libraries or functions without first checking if they were loaded and terminates without unloading them, your machine won't go up in flames, neither will other programs crash. It will simply still work without problems. Okay? Now that we know how to use them, let's go for the RexxUtil functions. Note that I'll prefix the function names in the paragraph headers by "rexxutil:" to emphasize that they are no REXX built-in functions.

rexxutil: SysLoadFuncs and SysDropFuncs
They are used to load or unload all functions of RexxUtil at once. SysLoadFuncs is used to load them all into memory, SysDropFuncs is used to unload them.We might need an example here: Imagine that you might want to load the functions SysFileTree, SysFileSearch, SysFileDelete and SysSleep from rexxutil for use in your program. You could now either code:

rc = RxFuncAdd('SysFileTree', 'rexxutil', 'SysFileTree')
rc = RxFuncAdd('SysFileSearch', 'rexxutil', 'SysFileSearch')
rc = RxFuncAdd('SysFileDelete', 'rexxutil', 'SysFileDelete')
rc = RxFuncAdd('SysSleep', 'rexxutil', 'SysSleep')

to load them (with the need of unloading them again one by one later)... or simply use:

rc = RxFuncAdd('SysLoadFuncs', 'rexxutil', 'SysLoadFuncs')
call SysLoadFuncs

And that's it. All functions from rexxutil.dll are now loaded and available for being called.
For unloading it's just

call SysDropFuncs

and you're done.
Note that SysLoadFuncs loads all functions from rexxutil, but still it has to be loaded itself first!

rexxutil: SysFileTree
Don't panic - there's no directory tree drawing here. This rather is a dir-like function used to retrieve file names (and file information) and is able to use wildcards and recurse into subdirectories for its search. Of course, this can lead to a whole bunch of matching files. In order to be able to store the possible large amount of data, SysFileTree uses a user-specified stem variable.

result = SysFileTree(<pattern>, <stemvar>, [options], [searchattrib], [newattrib] )
result is the return code indicating whether the function was executed successfully. It does not tell anything about the amount of files found - you rather need to check the stem variable contents once the result was 0 (zero) and thus successful.
pattern is the file mask (or wildcard) that you want to search for. It may include a path that the function then will limit its search to.
stemvar is the name of the variable (enclosed in quotes) that you want the results to be filled in
options is used to specify how the function will search for <pattern> This is one or a combination of the following characters:
F searches for files only that match <pattern>
D searches for directories only that match <pattern>
B searches both files and directories that match <pattern>
S search is extended into subdirectories
T the date and time data for each file are returned in timestamp format (YY/MM/DD/hh/mm)
O will only return the file/directory names instead of date/time/size/attributes/filename
searchattrib specifies a character string which consists of a mask of toggled switches for each possible file attribute. If specified, the function will only return files whose attributes match with what was specified here. (see the note)
newattrib This is a combination of attributes that will be applied to all files matching <pattern>. (see the note)
In addition, SysFileTree can also be used to not only retrieve files matching certain attributes (like read-only, system, etc.) but also to change the attributes that files. This is some advanced "feature" we won't be discussing here. We just use SysFileTree to search for files - if you're interested in the other features, I recommend you to read the rexx.inf section on SysFileTree...
So far so good. But how to specify the pattern and options and what is actually returned in the stem variable? Here's how it works: For instance, if you want to search for all .DLL files on your entire hard disk C:, the <pattern> would be "C:\*.DLL" and the <options> would be "FS" (for files, including subdirectories). If you want to search C:\OS2\DLL only (and no subdirectories of it) for the .DLL files, you would select "C:\OS2\DLL\*.DLL" for the <pattern> and simply "F" for the <options>. The stem variable is up to you to specify - something like "files" for example is a quite good idea:
returncode = SysFileTree("C:\*.DLL", "files", "FS")

is the function call for the above first example. Upon completion (this might take a while and you'll notice your hard disk working) the returncode variable might contain 0 (zero) if the call was successful or something else to indicate an error code. The stem variable will contain all files found (and the additional file data) by providing one entry for each file. In addition, entry #0 of the stem variable ("files.0") tells you the number of files contained (=found). If the function did not succeed, this will be zero. To search and display all the .DLL files on your hard disk C:, the code would go like this:

/* display all .DLL files of C: */
/* this line loads the function from the rexxutil library */
call RxFuncAdd 'SysFileTree', 'rexxutil', 'SysFileTree'

/* this line now calls the function  */
returncode = SysFileTree("C:\*.DLL", "files", "FS")

/* loop through all returned entries of the stem variable */
do i = 1 to files.0
   say files.i

/* --- end of code sample --- */

The entries themselves would look like this (excerpt of output):

 8/27/02   4:19p       96359  A----  C:\TCPIP\dll\TCPUNX.DLL
 8/27/02   4:19p       46080  A----  C:\TCPIP\dll\tnls16.dll
 9/18/01   3:52p      103540  A----  C:\TCPIP\dll\unzip.dll
 8/27/02   4:19p       70255  A----  C:\TCPIP\dll\VT100.DLL
 9/18/01   5:05p       64236  A----  C:\TCPIP\dll\wptelnet.dll
 9/04/01   2:02p       49664  A----  C:\TCPIP\dos\bin\wftpapi.dll
 8/21/00  10:44a       83817  A----  C:\TCPIP\dos\bin\winsock.dll

This looks quite "messed up" at first sight. if you prefer to have "sortable" time/date information, use the option character "T" in addition, to read the call:

returncode = SysFileTree("C:\*.DLL", "files", "FST")

and the output will become:

02/08/27/16/19       41497  A----  C:\TCPIP\dll\TCPOOCSJ.DLL
01/09/18/15/13      503845  A----  C:\TCPIP\dll\TCPOOCSX.DLL
02/08/27/16/19       96359  A----  C:\TCPIP\dll\TCPUNX.DLL
02/08/27/16/19       46080  A----  C:\TCPIP\dll\tnls16.dll
01/09/18/15/52      103540  A----  C:\TCPIP\dll\unzip.dll
02/08/27/16/19       70255  A----  C:\TCPIP\dll\VT100.DLL
01/09/18/17/05       64236  A----  C:\TCPIP\dll\wptelnet.dll
01/09/04/14/02       49664  A----  C:\TCPIP\dos\bin\wftpapi.dll
00/08/21/10/44       83817  A----  C:\TCPIP\dos\bin\winsock.dll

And if you don't care about file date/time or file size and attributes, use the "O" option string to read the call
returncode = SysFileTree("C:\*.DLL", "files", "FSO")

This will give you entries of the following format:


You might get a picture of what the function behaves. If you don't want to scan within subdirectories of what you possibly specified in <pattern>, just omit the "S". Ever wanted a directory list of your hard drive? Here's how it works:

/* display directories on C: */
call RxFuncAdd 'SysFileTree', 'rexxutil', 'SysFileTree'
returncode = SysFileTree("C:\*", "files", "DSO")
do i = 1 to files.0
   say files.i

rexxutil: SysFileSearch
Doesn't search for files but rather searches within files for a specific string. It'll return all the lines of a specified file that contain the specified text to search for.

result = SysFileSearch( <searchtext>, <filename>, <stemvar> [, options ] )
result is the return code indicating whether the function was executed successfully. It does not tell anything about the amount of lines found - you rather need to check the stem variable contents once the result was 0 (zero) and thus successful.
searchtext is the text string to search for
filename is the name of the file you want to search within (note: wildcards - i.e. multiple files - are NOT supported!)
stemvar is the name of the stem variable you want the lines found to be contained in
options is used to specify how the function will search. This is one or a combination of the following characters:
C searches for exact matches of upper/lower case letters. By default, case is ignored
N tells the function to include the line number (within the file) for each line found. By default, line numbers are not returned.

The result (return code) and the stem variable entry .0 behave in the same way like what we said about the SysFileSearch function. Thus, let's move on to a quick sample code:

/* display all lines of config.sys that contain a "basedev" command */
/* including the line number within config.sys */

/* load the function */
call RxFuncAdd 'SysFileSearch', 'rexxutil', 'SysFileSearch'

/* call the function */
returncode = SysFileSearch("basedev", "c:\config.sys", "lines", "N")

/* loop through all entries of the stem variable, displaying the contents */
do i = 1 to lines.0
   say lines.i

/* --- end of code sample --- */

rexxutil: SysMkDir
Creates a directory. This is a quite simple yet fast function that enables you to check for various error codes if the creation did not succeed. The syntax is:

rc = SysMkDir( <directory> )

Note that SysMkDir cannot create nested new directories, that's to say, it can only create one new level of directories at once. If you want to create a new directory named "test1" which should contain a new directory named "test1sub1", you cannot create "\test1\test1sub1" on the fly. Instead, you should call it two times... like this:

/* sysmkdir sample */
call rxfuncadd "sysmkdir", "rexxutil", "sysmkdir"
call sysmkdir "c:\test1"
call sysmkdir "c:\test1\test1sub1"

Okay, this sample lacks some error handling (e.g. the directory c:\test1 already exists) so here's one more:

/* sysmkdir sample */
call rxfuncadd "sysmkdir", "rexxutil", "sysmkdir"
if sysmkdir("c:\test1") = 0 then
   if sysmkdir("c:\test1\test1sub1") = 0 then
      say "creation successful"
      say "cannot create test1sub1"
   say "cannot create test1"

For possible error codes of the SysMkDir function, take a look at the rexx.inf help file section.

rexxutil: SysRmDir
Removes a directory and behaves almost equal to

rc = SysRmDir( <directory> )


/* sysrmdir sample */
call rxfuncadd "sysrmdir", "rexxutil", "sysrmdir"
if sysrmdir("c:\test1") = 0 then
   say "removal successful"
   say "cannot delete test1"

Note that it won't delete directories unless they are empty and are not the current directory. Again, use the appropriate section of rexx.inf to see possible error codes and reasons.

rexxutil: SysFileDelete
Removes a file (and does not support wildcards!). It uses one single parameter - the file name:

result = SysFileDelete( <filename> )

/* delete single file sample */
call rxfuncadd "sysfiledelete", "rexxutil", "sysfiledelete"
if sysfiledelete("c:\temp.tst") = 0 then
   say "deletion successful"
   say "cannot delete test1"
/* -- end of sample code -- */

The following sample demonstrates a combination of SysFileSearch and SysFileDelete by making use of SysLoadFuncs to load the required functions:

/* find and delete temporary files */
/* Caution: No delete confirmation requests! Use carefully! */
call rxfuncadd "sysloadfuncs", "rexxutil", "sysloadfuncs"
call sysloadfuncs
call SysFileTree "c:\var\temp\*.tmp", "files", "FO"
do i = 1 to files.0
   call SysFileDelete files.i
/* -- end of code sample -- */

rexxutil: SysSleep
Suspends program execution for a given interval of seconds. Note that you can also use it with fractions of a second (which is not stated in the rexx.inf help by the way). Simply use 0.1 to make it wait 1/10 of a second (100 milliseconds). It recognizes values down to 0.0000001 seconds but I I didn't test it for correct behaviour. Also, in addition, be careful with its precision, because this largely depends on whether the system load is high or not...

Syntax: call SysSleep <seconds>

Note that there is no return code from SysSleep.

/* SysSleep sample */
call rxfuncadd "syssleep", "rexxutil", "syssleep"
do 5
   call SysSleep 1
   say "tick"
/* -- end of code sample -- */

The above sample displays 5 times a string of "tick" after having waited 1 second each time.

rexxutil: SysIni
This is used to process INI files. But this is not the Windows-style plain text INI files you might know but rather the OS/2 style which means binary files that are not human-readable. There are several tasks involved in dealing with INI files. Let's have a brief discussion about how an INI file is organized first.
INI files contain applications which are like "root entries" in the INI file. Each application can have one or more key(s) (like sub-entries) that are associated (or not) a single value each. Take a look at the following screenshot of OS/2's own "registry editor" (REGEDIT2.EXE) that can be used to manually work with INI files:


The above screenshot was taken while regedit2 had loaded "C:\IBMLVL.INI". The upper part of the screen shows the applications contained in the file. "IBM_LS" was selected and the lower part displays all keys contained for the application selected. One key was highlighted to show it's associated value. To conclude this topic, here's a scheme to show how INI files are organized:

1 Ini-File can contain N applications
1 application can contain N keys
1 key can contain 1 value

Most of the time, you'll be dealing with the basic tasks like reading and/or writing a key and value to an application entry. But due to the organization of INI files, the SysIni function supports a whole lot of tasks like getting a list of all keys for an application entry and so on. In addition, SysIni can handle OS/2's own INI files (os2sys.ini and os2.ini) as well as a user-specified INI file. To be able to specify which INI file to work with, the <inifile> parameter of SysIni can contain

  • "SYSTEM" to refer to the OS/2 system-related INI file ("os2sys.ini")
  • "USER" to refer to the OS/2 user-related INI file ("os2.ini")
  • "BOTH" in order to use both system INI files at once (like a single, large INI file just as regedit2.exe does by default)
  • A full-qualified file name (including drive and path) to refer to a user-specified INI file (like "C:\MyApp\MyApp.INI" for example)

Here's an overview of how the function has to be called in order to achieve specific tasks:

task syntax comments
write a single key/value to an application
result = SysIni(<inifile>, <application>, <key>, <value> )
- creates both application and/or key if they didn't exist in the INI file
- creates the INI file as well if it doesn't exist
- the return value is a string which is either empty if the call was successful or contains "error:" if there was an error (like the INI file didn't exist)
read a value from an application/key
result = SysIni(<inifile>, <application>, <key> )
either returns the value associated with the application/key or "error:" if there was an error
delete a key (along with the associated value if any) from an application
result = SysIni(<inifile>, <application>, <key>, "DELETE:")
the return value is a string which is either empty if the call was successful or contains "error:" if there was an error
delete an application (along with all its keys and values)
result = SysIni(<inifile>, <application>, "DELETE:" )
the return value is a string which is either empty if the call was successful or contains "error:" if there was an error
retrieve all keys of an application
result = SysIni(<inifile>, <application>, 'ALL:', <stemvar>)
will list all key names into a stem variable. Entry.0 of the stem variable (as always) holds the total number of keys, entries 1 through the last hold one key name each.
retrieve all applications of an INI file
result = SysIni(<inifile>, 'ALL:', <stemvar>)
will list all application names into a stem variable. Entry.0 of the stem variable (as always) holds the total number of applications, entries 1 through the last hold one application name each.

In order to have a little example that you can use to extend and check what it's about, I've prepared a small application. You can paste the lines into an empty editor window (for example e.exe), save and run the file:

/* SysIni sample application */
call rxfuncadd "sysini", "rexxutil", "sysini"

myini = "C:\test.ini"

if SysIni(myini, "app1", "key1") = "ERROR:" then call CreateIni

say "Displaying all applications of the INI file:"
call SysIni myini, 'ALL:', 'apps'
do i = 1 to apps.0
   say apps.i
say "press enter to continue"
pull line

say "Displaying all keys of application 'app1' of the INI file:"
call SysIni myini, 'app1', 'ALL:', 'apps'
do i = 1 to apps.0
   say apps.i
say "press enter to continue"
pull line

say "Displaying value of 'key2':"
inival = SysIni(myini, 'app1', 'key2')
say "value is:" inival
say "Now altering it... press enter."
pull line
call SysIni myini, 'app1', 'key2', 'newvalue'
inival = SysIni(myini, 'app1', 'key2')
say "new value is:" inival
say "press enter to continue"
pull line

say "Deleting key 'key2' of application 'app1':"
call SysIni myini, 'app1', 'key2', 'DELETE:'
say "done - press enter to continue"
pull line

say "Displaying all keys of application 'app1' again:"
call SysIni myini, 'app1', 'ALL:', 'apps'
do i = 1 to apps.0
   say apps.i
say "press enter to continue - program will quit."
pull line

call SysIni myini, "app1", "key1", "value1-1"
call SysIni myini, "app1", "key2", "value1-2"
call SysIni myini, "app1", "key3", "value1-3"
call SysIni myini, "app2", "key1", "value2-1"
call SysIni myini, "app2", "key2", "value2-2"
call SysIni myini, "app3", "key1", "value3-1"
/* -- end of sample code -- */

The program checks to see if the ini file exists by retrieving the value of app1/key1. If this fails, the INI file (probably) doesn't exist and the program will call the CreateIni subroutine. It will create some INI entries and then return. All applications are displayed, then all keys of app1. Next, the program will alter the value for key "key2" of "app1" and display the (new) value again. Then it will delete the key and re-display all (remaining) keys of app1. Have fun playing around with it.

What's coming up?

The next part will take us back to DrDialog! We'll do a short tour of how to use and control more than just one dialog window. We'll examine the pitfalls, benefits and drawbacks. This should leave you with an understanding of what to consider when dealing with multiple dialogs. Next we'll take a look at Chris Wohlgemuth's set of enhancement functions to DrDialog: A progress bar control, fly-over-help control, a picture control capable of dealing with all formats supported by OS/2 (through MMPM/2) and additional features to control master/slave behavior of multiple dialogs. Greetings. Stay tuned.