VOICE Home Page: http://www.os2voice.org |
April 2004
Newsletter Index
|
By Thomas Klein © April 2004 |
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.
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 Ronny 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
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 | Current time as HH:MM (pm|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.
Example:
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! ;)
filespec
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
.
directory
If called without any parameter, it will tell (return) you the current directory:
say directory()
or
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 "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...
value
...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 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
xyz
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
myvar
Why? Because you gave it a literal string (by using the quotes) instead of a symbol. Got it?
random
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:
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)
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:
RxFuncAdd
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).
RxFuncDrop
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
.
RxFuncQuery
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) |
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 /* --- 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
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 /* --- 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" 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.
rexxutil: SysRmDir
Removes a directory and behaves almost equal to SysMkDir
:
rc = SysRmDir( <directory> )
Example:
/* 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.
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" else 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 /* -- 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 behavior. 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 /* -- 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
os2sys.ini
")os2.ini
")regedit2.exe
does by default)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 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.
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.
Feature Index
editor@os2voice.org
< Previous Page | Newsletter Index | Next Page >
VOICE Home Page: http://www.os2voice.org