DrDialog, or: How I learned to stop worrying and love REXX - Part 12
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.
DATE and TIME
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|
|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.
|09:02:58||computer was started|
|09:17:15 .00||time("e") is called||returned value of time() function: 0|
|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:
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 directorys 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 "
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:
will display c:\MPtn\dLl (if the directory exists), instead of its possible "real" name
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/2s 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
ENDLOCAL functions. The use of this functions also is demonstrated in the
VALUE() function help text of
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
That's nothing special. But...: If you would code
This is the content of variable 'xyz'...
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!):
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 minimun 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 "reproduceable") sequence of numbers for that base number. For instance:
/* random with base number */ basenum=45678 sequence="" sequence = random(1,6,basenum) do 69 sequence = sequence || random(1,6) end 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:
result = RANDOM( <min>, <max>, [<basenumber>] )
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 programing-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 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
<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
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
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
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
and you're done.
SysLoadFuncs loads all functions from rexxutil, but still it has to be loaded itself first!
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)|
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
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 /* --- 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:
C:\TCPIP\dll\TCPUNX.DLL C:\TCPIP\dll\tnls16.dll C:\TCPIP\dll\unzip.dll C:\TCPIP\dll\VT100.DLL C:\TCPIP\dll\wptelnet.dll C:\TCPIP\dos\bin\wftpapi.dll C:\TCPIP\dos\bin\winsock.dll
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 end
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.
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 /* --- end of code sample --- */
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> )
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" else say "cannot create test1sub1" else say "cannot create test1"
For possible error codes of the
SysMkDir function, take a look at the
rexx.inf help file section.
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" else 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.
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" else say "cannot delete test1" /* -- end of sample code -- */
The following sample demonstrates a combination of
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 /* -- end of code sample -- */
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 behavior. Also, in addition, be careful with its precision, because this largely depends on whether the system load is high or not...
call SysSleep <seconds>
Note that there is no return code from
/* SysSleep sample */ call rxfuncadd "syssleep", "rexxutil", "syssleep" do 5 call SysSleep 1 say "tick" end /* -- end of code sample -- */
The above sample displays 5 times a string of "tick" after having waited 1 second each time.
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 (
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.exedoes 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:
|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 end 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 end 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 end say "press enter to continue - program will quit." pull line exit CreateIni: 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" return /* -- 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.