REXX Tips & Tricks:General hints for REXX
This section contains general hints for REXX under OS/2.
Another good source for REXX related information is the Web pages of Quercus System--in particular, the pages How do I ... and Common REXX Pitfalls (see Internet - Web Pages)
You should also take a look at the REXX FAQ maintained by Dave Martin (see Internet - Web Pages).
REXX and Y2K
The latest FixPaks for OS/2 Warp 3 (FP35 and above) and Warp 4 (FP6 and above) contain extensions for REXX to make it "Y2K Tolerant".
From a recent FixPak's Readme:
4.1 QUERYING FILE DATES FOR FILES AFTER DEC 31, 1999 IN REXX Existing REXX functions return file dates with a two digit year only. While these functions are Year 2000 tolerant (i.e. the results will be correct for files dated after Dec 31, 1999) they require some additional logic in existing programs to handle the returned date correctly when they are compared with other file dates. Since the output format of the exisiting functions could not be changed for compatibility reasons, new options have been added to the REXX interpreter to return file dates with the year formatted with 4 digits. Two functions have been extended to support the new format. The syntax to retrieve the file date in 4 digit format is as follows: /********************************************/ /* Use STREAM QUERY TIMESTAMP to query file */ /* date in 4 digit format */ /********************************************/ Say Stream("C:\CONFIG.SYS", "C", "QUERY TIMESTAMP") /***********************************************/ /* Use option "L" with SysFileTree to return a */ /* list of files with long date format */ /***********************************************/ Call RxFuncAdd "SysLoadFuncs", "RexxUtil", "SysLoadFuncs" Call SysLoadFuncs Call SysFileTree "C:\*.*", "Files", "L" Do i = 1 To Files.0 Say Files.i End /* do */
Reserved Words
"REXX has no reserved words. Words are only reserved in context. Hence:
if=1 if if then then=2; else else=7 do forever=3 to 7 until end; end=1; end
parse var x with var with left right value
are all valid instructions. A function is recognised solely by the presence of a '(' appearing immediately after it, not by the spelling of its name.
This is where Rexx has a big advantage over things like Visual BASIC. (I was typing a BASIC program the other day, and the first three variable names I tried were all keywords...)"
Source: Ian Collier
Using comments
Although it's possible to use multi-line comments in REXX programs, it's not advisable. Multi-line comments can make it very difficult to search an error in REXX programs.
Note: Because nested comments are allowed in REXX, you should use the following technique to use the strings to mark the beginning and the end of a comment in a string:
"/" || "*"
and
"*" || "/"
Writing OS independent programs
To write OS independent REXX programs, you can use the PARSE SOURCE statement to distinguish between the different platforms (see example below and the chapter about PARSE SOURCE. The chapter titled Force a REXX program to run in a special way also discusses the use of PARSE SOURCE to identify the environment in which a REXX program is running and then process conditional commands.
When writing programs for use on other platforms in addition to OS/2, remember that some of the features and functions in OS/2 REXX are not implemented in REXX on other platforms (or may be implemented in a different manner)!
(see REXXTRY.CMD for another example for OS independent REXX programs)
/* ------------------------------------------------------------------ */ /* */ /* APPLICATION - Cross Platform - CMS, OS/2 2.0 and TSO */ /* FUNCTION - Merge 4 comma-delimited input files into an */ /* outputfile, tagging each record with the name of */ /* it's corresponding input file */ /* USE: OS/2 - MERGE f1.ext,f2.ext,f3.ext,f4.ext,outfile.ext */ /* TSO - MERGE f1.qlf,f2.qlf,f3.qlf,f4.qlf,outfile.qlf */ /* CMS - MERGE fn1 ft1 fm1,fn2 ft2 fm2,...fm4,ofn oft ofm */ /* AUTHOR - Michel Plungjan April '93 */ /* (see EMail Addresses) */ /* */ /* History: */ /* 12.12.1995 /bs */ /* - reformatted and added some comments */ /* 26.02.1996 /bs */ /* - corrected a bug in the TSO read secition according to */ /* information from Michel Plungjan */ /* */ /* ------------------------------------------------------------------ */ arg InFile.1 "," InFile.2 "," InFile.3 "," InFile.4 "," Merged if InFile.1 = "/?" then signal HELP; call INIT j = 0; do i = 1 to 4 FileName = Infile.i call READFILE; if InRec.0 > 0 then do k = 1 to InRec.0 j = j + 1 OutRec.j = strip(InRec.k,"T") substr(FileName,1,4) end; /* do k = 1 to InRec.0 */ end; /* do i = 1 to 4 */ if j > 0 then do OutRec.0 = j; call WRITEFILE; end /* if j > 0 then */ else do XReason = "Input files empty..."; XRc = 8; SIGNAL ABNORMAL_END end; /* else */ SIGNAL NORMAL_END; /* ------------------------------------------------------------------ */ /* */ READFILE: select when sys = 'CMS' then do /* --------------------- code for CMS ------------------------------- */ 'EXECIO * DISKR' FileName '(FINIS STEM INREC.' hrc = rc if hrc <> 0 then do XRc = hrc XReason = 'Error when reading' FileName 'dataset' SIGNAL ABNORMAL_END end /* if hrc <> 0 then */ end /* CMS */ when sys = 'TSO' then do /* --------------------- code for TSO ------------------------------- */ 'ALLOC DA('FileName') FI(INDD) SHR' hrc = rc if hrc <> 0 then do XRc = hrc XReason = 'Allocation error of' FileName 'dataset' SIGNAL ABNORMAL_END end 'EXECIO * DISKR INDD (FINIS STEM INREC.' /* v2.10 */ hrc = rc if hrc <> 0 then do XRc = hrc XReason = 'Error when reading' FileName 'dataset' SIGNAL ABNORMAL_END end /* if hrc <> 0 then */ 'FREE FI(INDD)' hrc = rc if hrc <> 0 then do XRc = hrc XReason = 'Error when freeing' FileName 'dataset, DDName INDD' SIGNAL ABNORMAL_END end /* if hrc <> 0 then */ end /* TSO */ when sys = 'OS/2' then do /* --------------------- code for OS/2 ------------------------------ */ do ii = 1 while lines(filename) > 0 InRec.ii = linein(FileName) end; /* do ii = 1 while lines( fileName) > 0 */ InRec.0 = ii - 1 if (stream(FileName,'S') <> 'READY') then do XRc = 1 XReason = 'Error when reading' InFile , 'file, Error indicator is 'stream(FileName,'D') SIGNAL ABNORMAL_END end /* I/O Error */ end /* OS/2 */ otherwise do /* --------------------- unknown OS --------------------------------- */ XReason = 'This program does not know how the environment' sys, 'uses I/O, please contact author' XRc = 8 SIGNAL ABNORMAL_END end /* otherwise */ end /* Select */ return /* ------------------------------------------------------------------ */ /* */ WRITEFILE: select when sys = 'CMS' then do /* --------------------- code for CMS ------------------------------- */ 'EXECIO 'OutRec.0 'DISKW 'Merged '0 F 80 (FINIS STEM OUTREC.' hrc = rc if hrc <> 0 then do XRc = hrc XReason = 'Error when writing' Merged 'dataset' SIGNAL ABNORMAL_END end /* if hrc <> 0 then */ end /* CMS */ when sys = 'TSO' then do /* --------------------- code for TSO ------------------------------- */ 'ALLOC DA('Merged') FI(OUTDD) SHR' /* File must already exist */ hrc = rc if hrc <> 0 then do XRc = hrc XReason = 'Allocation error of' Merged 'dataset' SIGNAL ABNORMAL_END end /* if hrc <> 0 then */ 'EXECIO' OutRec.0 'DISKW OUTDD (FINIS STEM OUTREC.' hrc = rc if hrc <> 0 then do XRc = hrc XReason = 'Error when writing' Merged 'dataset' SIGNAL ABNORMAL_END end /* if hrc <> 0 then */ 'FREE FI(OUTDD)' hrc = rc if hrc <> 0 then do XRc = hrc XReason = 'Error when freeing' Merged 'dataset, DDName OUTDD' SIGNAL ABNORMAL_END end /* if hrc <> 0 then */ end /* TSO */ when sys = 'OS/2' then do /* --------------------- code for OS/2 ------------------------------ */ do i = 1 to OutRec.0 Dummy = lineout(Merged,OutRec.i); end; /* do i = 1 to OutRec.0 */ rc = stream(Merged,'c','close') /* please put your own OS/2 error checking here */ end /* OS/2 */ otherwise do /* --------------------- unknown OS --------------------------------- */ XReason = 'This program does not know how the environment' sys, 'uses I/O, please contact author' XRc = 8 SIGNAL ABNORMAL_END end /* otherwise */ end /* Select */ return; /* ------------------------------------------------------------------ */ /* init global variables and get the current OS (in the var SYS) */ /* */ INIT: true = 1; false = 0; XReason = 'Files merged, you may now sort the file 'Merged; XRc = 0 parse source sys . return /* ------------------------------------------------------------------ */ /* show the usage help */ /* */ HELP: do i = 1 until pos('* ---',sourceline(i)) > 0 say strip(sourceline(i)) end /* do i = 1 ... */ exit; /* ------------------------------------------------------------------ */ /* */ ABNORMAL_END: say 'Program stopped due to' /* ------------------------------------------------------------------ */ /* */ NORMAL_END: say XReason 'return code:' Xrc exit
Writing filter programs in REXX
Filter programs are programs that read from standard input, convert the input in some way, and write the converted lines to standard output. Filter programs are heavily used in UNIX systems, but you can use them on any operating system supporting redirection of standard input and standard output with pipes, for example, OS/2.
In OS/2 you can write filter programs very simply in REXX (see the example below). But you should take care to be aware of the following points:
Write all messages (error messages, logos, etc.) to STDERR instead of STDOUT, e.g. use
call LineOut "STDERR", "This is an error message"
(see also Using the handles 3 to 9 in REXX programs)
Always use
call trace "OFF"
as first statement in a filter program (after the required REXX comment delimiters on line 1, of course). This statement makes sure that your program ignores the environment variable RXTRACE.
Also be aware that the function LINES() does not work as expected in Object-Oriented REXX (see The function LINES() in Object REXX). Therefore, you must distinguish between the different REXX versions in your filter program.
see General input line enhancer for a special filter program
/* ------------------------------------------------------------------ */ /* Simple filter program in REXX */ /* */ /* A filter program reads lines from STDIN, does something with them, */ /* and writes the changed lines to STDOUT */ /* */ /* In this example we simply convert the lines to uppercase. */ /* This program works for Classic REXX and for Object REXX. */ /* */ call trace "OFF" signal on notready name ProgramEnd /* check the REXX interpreter version */ parse version rexxVersion . if rexxVersion = "OBJREXX" then do; /* current REXX version is Object REXX */ /* main loop for Object REXX */ /* (The loop is aborted by a NOTREADY condition) */ do forever .output~lineout( convert( .input~linein ) ) end /* do forever */ end /* if rexxVersion = "OBJREXX" then */ else do /* current REXX version is Classic REXX */ /* main loop for Classic REXX */ do while lines( "STDIN" ) <> 0 call lineOut "STDOUT", convert( lineIn( "STDIN" ) ) end /* do while lines() <> 0 */ end /* else */ programEnd: exit 0 /* ------------------------------------------------------------------ */ /* this function returns the parameter in uppercase */ /* */ Convert: PROCEDURE parse arg inputLine return translate( inputLine )
Using the handles 3 to 9 in REXX programs
OS/2 defines 10 general file handles for batch files (and therefor also for REXX programs):
ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³Handle ³Default assignment ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³0 ³Standard Input (STDIN) ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³1 ³Standard Output (STDOUT) ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³2 ³Standard Error (STDERR) ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³3 ³no default assignment ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³4 ³no default assignment ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³5 ³no default assignment ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³6 ³no default assignment ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³7 ³no default assignment ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³8 ³no default assignment ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³9 ³no default assignment ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
To use the handles 0 to 2 in REXX programs, use the filenames STDIN, STDOUT, or STDERR. Note that these are the default values and therefor you do not have to explicitly code them. The handles 0 to 2 always exist and point either to the keyboard (handle 0), the screen (handles 1 and 2), or to a file if redirected via <, >, or |.
It's also possible to use the handles 3 to 9 in REXX programs. To do that, you must call the program with a redirection for the handle and inside the REXX program you can access them only via OS/2 commands, like for example the �ECHO command.
Examples:
Example for using ony the handle 3:
/* testHandle3.cmd */ /* */ /* REXX sample on using handle 3 in a REXX program */ /* call this cmd with */ /* */ /* testHandle3.cmd 3>handle3.out */ /* */ testString = "This text goes to the file referenced by handle 3" "echo. " testString ">>&3" exit 0
Example for using the handles 3 to 9:
/* testHandles3to9.cmd */ /* */ /* REXX sample on using the handles 3 to 9 in a REXX program */ /* call this cmd with */ /* */ /* testHandles3to9.cmd 3>ha3 4>ha4 5>ha5 6>ha6 7>ha7 8>ha8 9>ha9 */ /* */ testString = "This text goes to the file referenced by handle " do i=3 to 9 "echo. " testString || i ">>&" || i end /* for */ exit 0
You can also redirect more then one file handle into a file. Example:
testhandles3to9.cmd 3>test3x.out 4>>&3 5>>&3 6>>&3 7>>&3 8>>&3 9>>&3
In this example every echo command writes to the file test3x.out.
testhandles3to9.cmd 3>test3x.out 4>>&3 5>>&3 6>>&3 7>>&3 8>>&3 9>>&3 3>test3y.out
In this example every echo command except the echo command for handle 3 writes to the file test3x.out. The echo command for handle 3 writes to test3y.out
Please be aware that the output goes to STDOUT or you will get an error message if you try to use an unassigned file handle - for example if you call the first example testHandle3.cmd above without the redirection of the handle 3:
testHandle3.cmd
Also be aware that files you open inside your REXX program will get the next free file handle. Therefor the first file you open in a REXX program normally gets the handle 3. But if you redirect the handle 3 into a file via redirection on the command line the first file you open inside your REXX program will get the handle 4. Try the example below to see the difference.
/* testHandle3a.cmd */ /* */ /* REXX sample */ /* */ call stream 'TESTSTREAM', 'c', 'OPEN WRITE' testString = "This text goes to the file referenced by handle 3" "echo. " testString ">>&3" call stream 'TESTSTREAM', 'c', 'CLOSE' exit 0
If you call this program with
testHandle3a.cmd 3>handle3.out
the output goes to the file HANDLE3.OUT. if you call this example with
testHandle3a.cmd
the output goes to the file TESTSTREAM.
To check if one or more of the handles 3 to 9 are redirected to a file, you can check which handle you'll get if you create a new file:
/* This code works in Object-Oriented REXX only! */ DATAFILE = 'NUL' if stream( DATAFILE, 'c', 'OPEN READ' ) = "READY:" then do DATAFILE_handle = stream( DATAFILE, 'c', 'QUERY HANDLE' ) say "DATAFILE handle is" DATAFILE_handle call stream DATAFILE, 'c', 'CLOSE' end /* if */ else say "Error opening " || DATAFILE || " for reading!"
Normally, if the handles between 3 and 9 are not redirected, the handle for the new file is 3. If one or more of the handles 3 to 9 is redirected to a file the handle you get is 5 (if only handle 3 is redirected), 6 (if handle 3 and 4 are redirected), and so on.
BUT
The handle is also 5 or above if STDIN, STDOUT, or STDERR is redirected to another device, file or pipe. So, to be sure, check this also (How to check if STDIN, STDOUT or STDERR are redirected to a file) a
see Using redirection for a simple process controller, General input line enhancer for other samples. see Output & Input control, Reserved names for files in REXX for additional information.
Calling REXX programs in the CONFIG.SYS
You can call REXX programs in the CONFIG.SYS file by using the CALL statement. To do this, load the REXX support with (for example) BOS2REXX.EXE prior to the line that calls your REXX program in CONFIG.SYS.
Example:
REM *** load the REXX support RUN=E:\OS_2\tools\bootos2\bos2rexx.exe REM *** call the REXX program CALL=C:\OS2\CMD.EXE /C myprog.cmd
Note that you can only use the non-WPS related functions from REXXUTIL.DLL in programs called in the CONFIG.SYS. Note further, that you must use �RxFuncAdd to load each necessary REXXUTIL function by hand -- you cannot use �SysLoadFuncs! See the next chapter for an example for using this technique.
(see also the important note at the end of the section Using REXX if booted from diskette and RxFuncAdd)
Example for using REXX programs in the CONFIG.SYS
A REXX program called from within the CONFIG.SYS is always called after loading all drivers. So, if you want to use a REXX program to change one or more DEVICE or BASEDEV statements (or a file used by a device driver) in the CONFIG.SYS while booting the workstation, you must use a 2-step-method.
Here's an example: Some time ago a CompuServe forum member said he wanted to change the file PROTOCOL.INI in respect to the input of an user while OS/2 was booting. (PROTOCOL.INI is an ini file used by a device driver necessary for the Network support).
I suggested to call the REXX program below in the CONFIG.SYS for this purpose (see Calling REXX programs in the CONFIG.SYS on how to call a REXX program in the CONFIG.SYS). This program uses a status file and an additional boot process to change the configuration on the fly.
The advantage of this method in contrast with using OS/2's ALT-F1 feature to perform maintenance at bootup:
It runs not only on WARP 3, but also under OS/2 versions prior to WARP; there's no overhead and you only have to maintain one configuration.
(see RxFuncAdd if you want to use other DLLs in a REXX program called in the CONFIG.SYS)
/* REXX program which can be called in the CONFIG.SYS to get some */ /* input from the user, check the current configuration against the */ /* user input, and change the configuration and reboot the */ /* workstation with the changed configuration if necessary. */ /* */ /* Note that you can use this method also to change some lines in */ /* your CONFIG.SYS. */ /* Note further, that you can also replace the code to get the user */ /* input with some code to automatically get the needed configuration.*/ /* */ /* You can also use the routine Getkey in programs called in the */ /* CONFIG.SYS. */ /* */ /* (c) 1996 Bernd Schemmer, Germany, EMail: Bernd.Schemmer@gmx.de */ /* */ /* install an error handler for user breaks */ SIGNAL ON HALT Name UserBreak /* name of the status file for the current boot */ /* process. If this file exists, this is the */ /* second boot, if not it's the first. */ statusFile = "C:\BOOTSEM" if stream( statusFile, "c", "QUERY EXISTS" ) = "" then do /* first boot */ /* load the necessary REXXUTIL function(s). Note */ /* that you CANNOT use SysLoadFuncs!!! */ call rxFuncAdd "SysGetKey", "REXXUTIL", "SysGetKey" /* get the user input */ call CharOut, "Which PROTOCOL.INI do you want? Press A or B ->> " do forever userInput = translate( SysGetKey( "NOECHO" ) ) if UserInput = "A" | UserInput = "B" then leave else /* invalid user response - ring the bell */ call CharOut , "07"x end /* do forever */ call LineOut , UserInput /* check the configuration, in this example: */ /* check if the existing PROTOCOL.INI is correct */ call LineOut , "Checking the configuration. Please wait ..." /* ... insert the code to check the configuration ... */ /* set ConfigurationOK to 1 if PROTOCOL.INI is okay */ if ConfigurationOK = 1 then do /* the current configuration is okay */ /* continue with the boot process */ call LineOut, "The current configuration is okay." , "Boot process continues ..." end /* if ConfigurationOK = 1 then */ else do /* the current configuration is NOT okay */ call LineOut, "The current configuration is NOT okay." , "Now changing the configuration ..." /* correct the configuration, in this example: */ /* replace the PROTOCOL.INI */ /* ... insert the code to change the configuration ...*/ /* now create the status file */ call LineOut statusFile, "We need a second boot ..." /* close the status file */ call stream statusFile, "c", "CLOSE" call LineOut, "Rebooting your workstation please wait ..." /* and reboot the workstation using SETBOOT */ "SETBOOT /IBD:C" end /* else */ end /* if stream( statusFile, "c", "QUERY EXISTS" ) = "" then */ else do /* second boot */ /* normally nothing to do because the */ /* configuration should now be okay */ end /* else */ /* delete the status file also on user breaks */ UserBreak: /* delete the status file if it exists */ if stream( statusFile, "c", "QUERY EXISTS" ) <> "" then "@del " statusFile "2>NUL 1>NUL"
Using REXX if booted from diskette
When OS/2 is booted from diskette, you must use a special program to load the REXX support (normally, OS/2 boots from the hard disk and the WPS loads REXX support automatically).
The best program available to load REXX from diskette is BOS2REXX.EXE.
To create a boot diskette with REXX support do the following:
- Create a boot diskette with, for example BOOTOS2.
- Copy BOS2REXX.EXE to the boot diskette
- Add the statement RUN=A:\BOS2REXX.EXE to the file A:\CONFIG.SYS
- Add the directory with the files REXX*.DLL (\OS2\DLL) to the �LIBPATH in the file A:\CONFIG.SYS
- Add the directory with the file REX.MSG (\OS2\SYSTEM) to the �DPATH in the file A:\CONFIG.SYS
Note: You may omit the third step. In this case you must init the REXX support manually with the command
�detach A:\BOS2REXX.EXE
This method allows you to load REXX support only if necessary.
- see also Using REXXUTIL; use LX lite - an compressor for OS/2 executables to create one (!) boot diskette with REXX support and other useful tools!)
Important: Do NOT kill the process with BOS2REXX! Your REXX programs might not act as expected if you do so! The process which initially starts the REXX support is required throughout the session because it handles the global data, semaphores and so on. This is true even if you restart BOS2REXX after killing it!
Source: APAR PJ15496
- see also Adding Object REXX Support to a maintenance partition and RxFuncAdd)
Force a REXX program to run in a special way
Sometimes it is important for a REXX program to run within a specific environment (e.g. PMREXX) or in a special way (e.g. minimized or in the background).
To ensure that your REXX program uses the environment you intend it to use, you can check the current environment in your REXX program and, if it's not the envionment you need, restart the REXX program in the needed environment:
In the first case mentioned above, you can use the return code of the function �ADDRESS to check the current environment (see Run a REXX program under a specific environment and the warning regarding WARP 4 in the section PMREXX!); in the second case (i.e., running your program in a special way, such as minimized), you cannot detect the current environment and thus you've to restart your program using an additional parameter to distinguish between the two passes. (see Force a REXX program to run minimized).
Run a REXX program under a specific environment
Use the return code of the function �ADDRESS to force a REXX program to run under a specific environment:
/* ------------------------------------------------------------------ */ /* sample prolog to force a REXX program to run under a specific */ /* environment. */ /* */ /* In this example we force the program to run under PMREXX */ /* */ /* (see also Force a REXX program to run minimized and the warning */ /* in the section PMREXX)! */ /* */ /* name of the needed environment (return */ /* code of the function �ADDRESS) */ neededEnvironment = "PMREXX" /* program to call to setup the correct */ /* environment */ executor = "PMREXX.EXE" /* check the address environment */ if address() <> neededEnvironment then do /* THIS part is only executed if NOT running */ /* under the needed environment */ /* get the name of this program */ parse source . . thisProg /* now call the program again using the */ /* defined program to setup the environment */ "start " executor thisProg arg(1) exit rc end /* if adress() <> neededEnvironment then */ /* THIS part is only executed if we are */ /* running under the needed envrionment! */ call RxMessageBox "This program was forced to run under PMREXX",, "This is a test message" exit 0
Force a REXX program to run minimized
Use the following method to force a REXX program to run in a special way (e.g. minimized, in the background, in a full screen session, ...)
/* sample prolog for a REXX program to run minimized */ /* (see also Run a REXX program under a specific environment) */ /* turn CTRL-BREAK OFF */ signal off halt /* get the name of this file */ parse source . . thisFile /* get the parameter */ parse arg thisParameter /* check the pass of this file */ pass2Parameter = "$$PASS2$$" if word( thisParameter,1 ) <> pass2Parameter then do /* this is the first pass: restart the */ /* CMD minimized */ address "CMD" "start /C /MIN /BG cmd /c " , thisFile pass2Parameter thisParameter /* and end the program */ exit end /* if */ /* this is the second pass: cleanup the parameter */ /* and continue */ parse var thisParameter (pass2Parameter) thisParameter /* !!! insert your code here !!! */ exit
Start a REXX program in PM mode
To start a REXX program in a PM environment there are at least three methods available (note that a REXX program must run in a PM environment to use the function RxMessageBox()):
1. Method Start the program from within a PM program (like EPM) or use the program PMREXX to start the REXX program. (see also Run a REXX program under a specific environment)
You can also use one of the visual REXX development tools for the REXX program (like VX-REXX, VisPro/REXX or GpfREXX; see also the REXX development tools & extensions listed in the section PM Tools). These environments normally provide better PM controls than RxMessageBox().
2. Method Start the REXX program with the following command:
start /pm cmd /c {programPath}programName
Note that this is not possible in 4OS2 sessions!
3. Method Create an object with PROGTYPE set to PM for the REXX program and use the function SysOpenObject or SysSetObjectData to start the program:
/* REXX code to create a WPS object to start the REXX program */ /* C:\TESTPM.CMD */ /* in a PM environment (assuming OS/2 is installed on drive C:) */ /* load the necessary REXXUTIL functions */ call rxFuncAdd "SysLoadFuncs", "REXXUTIL", "SysLoadFuncs" call SysLoadFuncs /* create the object */ if SysCreateObject( , "WPProgram" ,, /* object class */ "REXX program in a PM environment" ,, /* object title */ "<WP_DESKTOP>" ,, /* object location */ "EXENAME=C:\OS2\CMD.EXE;" || , /* object setup */ "PROGTYPE=PM;" || , /* string */ "PARAMETERS=/C C:\TESTPM.CMD;" || , "OBJECTID=<MyObject>;" ,, /* replace flag */ "U" ) = 1 then do /* execute the program (all OS/2 version, you can */ /* also use SysOpenObject in OS/2 WARP) */ call SysSetObjectData "<MyObject>", "OPEN=DEFAULT"; end /* if SysCreateObject( ... */
Starting other programs in REXX programs
This sections contains some hints about starting other programs from within a REXX program.
Start an OS/2 program synchronously
Some PM programs (for example VIEW.EXE and NOTES.EXE) start asynchronously if called from a REXX program. Use the following code to start one of these programs and wait until it ends (You can also use this technique for a simple Process controller in REXX; see also Start a Windows program synchronously and StartDOS):
/* sample code to start an OS/2 program synchronously */ /* load the necessary REXXUTIL function(s) */ call rxFuncAdd "SysSleep", "REXXUTIL", "SysSleep" /* name of the "semaphore" file */ /* Note: If you want to run this program in */ /* separate sessions at the same time, use */ /* a routine that lets you get a unique */ /* name for a temporary file, to get a */ /* unique name for your semaphore files. */ /* (for example */ /* Get a name for a temporary file) */ SemFile = "TEST.SEM" /* start the program and redirect STDOUT into the */ /* "semaphore" file */ /* Note that this technique does not work 3.20 */ /* if there is already an instance of the 3.20 */ /* view program running!!! 3.20 */ "view cmdref.inf >" || semFile /* wait until the program ends */ do until stream( semFile, "c", "OPEN READ" ) = "READY:" call SysSleep 1 end /* do until ... */ /* close and delete the "semaphore" file */ call stream semFile, "c", "CLOSE" "del " semFile "2>NUL 1>NUL" exit 0
Note that this technique does not work if there is an instance of the program to start already running and if the call of the program only passes the parameter to the running instance. This is true for view.exe, for example.
Start a Windows program synchronously
To start a Windows program synchronously, you must use a DOS batch file. The batch file might look like (see also Start an OS/2 program synchronously and StartDOS):
REM *** DOS Batch REM Usage: DOSSTART.BAT semFile windowsProgram {progParameters} REM SET semFile=%1 SET programName=%2 SET parameter=%3 :pLoop SET parameter=%parameter% %3 SHIFT IF NOT "%3" == "" GOTO pLoop %programName% %parameter% >%semFile% exit The calling REXX program might look like /* sample code to start a Windows program synchronously */ /* load the necessary REXXUTIL function(s) */ call rxFuncAdd "SysSleep", "REXXUTIL", "SysSleep" /* name of the "semaphore" file */ /* Note: Use a routine like for example */ /* Get a name for a temporary file */ /* to get a unique name for the semaphore */ /* file if you want to run this program */ /* in separate sessions at the same time. */ semFile = 'd:\sem' winProg = 'd:\win\apps\excel4\excel' 'DOSSTART.BAT' semFile winProg do forever say 'Waiting for the Windows Session ... ' if stream( semFile, 'c', 'OPEN READ' ) = 'READY:' then leave call SysSleep 1 end /* close and delete the "semaphore" file */ call stream semFile, 'c', 'CLOSE' 'del ' semFile '2>NUL 1>NUL'
Detach a program & capture the PID
Use the following routine to �DETACH a program and capture the PID of the detached program.
/* */ /* sample routine to detach a program and return the process */ /* identification number (PID) */ /* */ /* get the parameter */ parse arg progName progParameter /* detach the program */ thisRC = DetachProgram( progName, progParameter ) /* and process the return code */ if word( thisRC,1 ) = "ERROR:" then say "DetachProgram returned the error message: " thisRC else say "The process identification number of the", "detached process is <" || thisRC || ">." exit /* ------------------------------------------------------------------ */ /* function: detach a program and return the process identification */ /* number */ /* */ /* call: DetachProgram programName {, programParameters} */ /* */ /* where: programName - name of the program */ /* programParameters - parameter for the program */ /* */ /* returns: ERROR n: error n occured */ /* where n is */ /* -255 : unknown error occured */ /* -1 : parameter missing */ /* -2 : program to detach not found */ /* else the process identification number */ /* */ /* Note: This routine uses static names for the temporary files */ /* and thus cannot run in separate programs at the same */ /* time (see below for a solution of this limitation)! */ /* */ /* This routine assumes that the PID is always the last */ /* word in the output of the detach command. */ /* */ /* */ DetachProgram: PROCEDURE parse arg ProgramName , ProgramParameter programName = strip( programName ) /* set the necessary ADDRESS environment */ /* Note that the previous ADDRESS environment */ /* is automatically restored after this */ /* routine ends. */ ADDRESS "CMD" /* init the return code */ thisRC = "ERROR: -255" if programName <> "" then do if stream( programName, "c", "QUERY EXISTS" ) <> "" then do /* get the path for the temporary files */ tempPath = value( "TEMP",, "OS2ENVIRONMENT" ) if tempPath = "" then tempPath = value( "TEMP",, "OS2ENVIRONMENT" ) if tempPath = "" then tempPath = directory() if right( tempPath, 1 ) <> "\" then tempPath = tempPath || "\" /* Note: */ /* Change the algorithm to get the names for the */ /* logfile and the temporary copy of the logfile */ /* if you want to run this routine in more than */ /* one program at the same time!!! */ /* (e.g. use the routine GetTempFile from RXT&T) */ /* create the name for the STDERR logfile */ /* of the detached program */ STDErrLogFile = tempPath || "$$out$$.ERR" /* create the name for the temporary copy of the */ /* logfile */ TempLogFile = tempPath || "$$out$$.TMP" /* detach the program redirecting STDOUT to NUL */ /* and STDERR into the logfile */ /* */ /* The use of another copy of the CMD.EXE is */ /* necessary to handle batch files and REXX */ /* programs correctly */ /* */ "@detach cmd /c " ProgramName , ProgramParameter , "2>" || STDErrLogFile , "1>" || "NUL" /* because we can't read the logfile in REXX at */ /* this time (it is still opened), we use the */ /* TYPE command to copy the current contents */ /* of the logfile into another file */ /* */ /* Note that you cannot redirect the output of */ /* the TYPE command into RXQUEUE. */ /* You cannot use the COPY command to copy the */ /* file because it is still in use by the */ /* detached program. */ /* */ "@type " STDErrLogFile ">" || TempLogFile /* and now we read the first line of the */ /* temporary copy of logfile to get the PID */ call stream tempLogfile, "c", "OPEN READ" if lines( tempLogFile ) >= 1 then do curLine = lineIn( tempLogFile ) /* we assume the last word of the output message */ /* from DETACH is the PID */ thisRC = Word( curLine, words( curLine ) ) if right( thisRC,1 ) = "." then thisRC = dbrright( thisRC, 1 ) end /* if */ /* last thing to do: close temporary copy of */ /* the logfile and delete it */ call stream tempLogfile, "c", "CLOSE" "@del " tempLogFile "1>NUL 2>NUL" end /* if stream( programName, "c", "QUERY EXISTS" ) <> "" then */ else thisRC = "ERROR: -2" end /* if programName <> "" then */ else thisRC = "ERROR: -1" RETURN thisRC
Check if a REXX program is already running
Use the REXXUTIL function SysQuerySwitchList (introduced in the new REXXUTIL.DLL from Object-Oriented REXX) to check if a REXX program is already running.
Note: This function is only available in the REXXUTIL DLL from Object REXX for OS/2 Version OBJREXX 6.00 11 Nov 1997 or newer.
Parameters ...
Distinguish between empty and omitted parameter
You can check if a parameter has been ommitted using the function �ARG. This is useful if you work with default values for parameter and if an empty string is a valid value for a parameter. (Note that this is only possible in internal or external REXX functions or procedures! see also Starting new VIO/Fullscreen sessions)
Example:
/* sample code to show how to distinguish between omitted and */ /* empty parameter */ /* call the subroutine with omitted and empty */ /* parameter */ call TestRoutine '', 'v2', , 'v4' exit /* sample sub routine */ TestRoutine: parse arg v1, v2, v3, v4, v5 do i = 1 to 5 if arg( i, 'E' ) = 1 then do say 'Parameter ' || i || ' was entered. ' say ' The value is "' || arg( i ) || '"' end else say 'Parameter ' || i || ' was omitted. ' end /* do i = 1 to 4 */ return
Using %0 ... %9 with OS/2 commands
Be aware that the strings %0 ... %9, when used with OS/2 commands in a REXX program, are replaced by the CMD.EXE with the name of the program (%0) and the parameters (%1 ... %9). Example:
/* */ /* The lineout statement prints */ /* %0 %1 %2 %3 */ /* to the screen */ call LineOut , "%0 %1 %2 %3" /* The ECHO command prints */ /* - the name of the CMD (%0) */ /* - and the parameter 1 to 3 (%1 ... %3) */ /* to the screen */ '@ECHO %0 %1 %2 %3'
The same is true for the names of environment variables enclosed in % used with OS/2 commands.
(see the sections Get the invocation syntax, Get the name of the MAIN program called, and Get the parameters as seen by CMD.EXE or Get the parameters as seen by CMD.EXE - 2 - for usage of this feature)
Parameters for a REXX program
There's a difference in the way REXX programs handle parameters when they're called from the command line (e.g. you enter erexxtry in an OS/2 window) and when they're called from another REXX program (e.g. you write myRC = rexxtry() in your REXX program):
In the first case (when called from the command line), the REXX program always retrieves the parameters given on the command line as single string. Thus, the programmer must include code in the REXX program to parse the string into individual parameters. In the second case (when called from within a REXX program), the REXX interpreter itself splits the parameter string into its parts, much as if it has been called by a REXX routine (see also How to use parse arg).
To get around the differences in these behaviors, you can check the "call type", returned by PARSE SOURCE (see also Simple parameter parsing routine). Then, having identified the type of call that was used, your program can branch to code based on the identified call type:
/* simple example code to handle parameters to a REXX program */ /* in a manner equivalent to the handling of parameters for a */ /* REXX routine */ /* */ /* get the call type of this routine */ /* DO NOT turn NOVALUE on at this time! */ parse source . callType . if callType <> "COMMAND" then signal Main /* called as function or procedure */ else do /* called from the command line */ args = "" if pos( ",", arg(1)) = 0 then do /* no comma found in the parameters -- use blanks */ /* as argument delimiter */ /* split argument in multiple arguments using */ /* blanks as argument separator */ do i = 1 to words( arg(1) ) args = args "'" || word( arg(1),i ) || "'" args = args "," end /* do i = 1 to words( arg(1) ) */ end /* if pos( ... */ else do /* at least one comma found in the parameters -- */ /* assume commas are used to separate the */ /* parameters */ /* split argument in multiple arguments using */ /* commas as argument separator */ argT = strip( arg(1) ) do while argT <> "" parse var argT argC "," argT argC = strip( argC ) args = args || "'" || argC || "'" args = args "," end /* while argT <> "" */ end /* else */ drop argT argC interpret "call Main " || args if symbol( "RESULT" ) = "VAR" then do /* return the return code to the caller */ return result end /* if symbol( "RESULT" ) = "VAR" then */ else return end /* else */ /* main function entry point */ /* Note: Do not use PROCEDURE for this routine */ Main: say "Main called with " || arg() || " arguments:" do i = 1 to arg() say " Argument " || i || " is <" || arg(i) || ">" end /* do i = 1 to arg() */ return arg(1)
Parameters eaten by the CMD.EXE
The following parameter, when fed to a REXX program (or a batch program) is interpreted and "eaten" by the command interpreter CMD.EXE (see also Parameters eaten by the REXX interpreter):
- q
Suppress all output of OS/2 commands while running the program (Use ECHO ON in your REXX program to turn this feature off)
Important: The parameter /Q is always interpreted by the CMD.EXE. It is never seen by the REXX program. This is also true for any parameter beginning with /Q (or /q). Therefore, if you call a REXX program with the parameter '/QUIET' it will only see 'UIET' as parameter!
To avoid this limitation for parameters beginning with /Q you can call the program with the parameter in double quotes--e.g., myProgram "/QUIET". But be aware that in this case the CMD will also pass the double quotes to your program!
Another workaround: Simply use the parameter /Q twice. Example:
D:\Test> test2.cmd test /q 3 *-* parse arg thisargs >>> "test " D:\Test> test2.cmd test /q /q 3 *-* parse arg thisargs >>> "test /q" D:\Test> test2.cmd test /q/quiet 3 *-* parse arg thisargs >>> "test /quiet"
Source: Barry Lemon
Please note that other command line interpreter, such as 4OS2, for instance, may or may not have the limitation mentioned above.
Parameters eaten by the REXX Interpreter
The following parameters, when passed to a REXX program, are interpreted and eaten by the REXX Interpreter himself (see also Parameters eaten by the CMD.EXE):
This parameter delimiter is reserved for the REXX interpreter. You can't use it for program-specific parameters. Note that this limitation is removed in Object-Oriented REXX
- t
Create the tokenized version of the program without executing it
To use the parameter //t in Object-Oriented REXX you can add other parameter(s). Example:
D:\Test> test2.cmd //t dfad 3 *-* parse arg thisargs >>> "//t dfad" D:\Test> test2.cmd //t //t 3 *-* parse arg thisargs >>> "//t //t" D:\Test> test2.cmd //t
(see also Workaround for the // limitation for a method to workaround for these limitations).
Workaround for the // limitation
You may use the following workaround to pass parameters containing // to a REXX program. (see Parameters eaten by the REXX interpreter). I found the base idea of this workaround (using an OS/2 batch to call the REXX program) in a public message in one of the CompuServe forums. In contrast to this version, the version published in the CompuServe message used REXX queues to pass the parameter. But I think that's a lot of overhead for this purpose.
Note: You can also use the method described in the section Get the parameters as seen by CMD.EXE or Get the parameters as seen by CMD.EXE - 2 - to get around this limitation.
First, create an OS/2 batch file to launch your REXX program:
@ECHO OFF REM *** OS/2 Batch program to launch a REXX program with REM normally inpossible parameters for REXX programs REM REM *** init the environment variable for the parameters SET REXXParms= REM *** copy the parameters to the environment variable REM (use a loop to handle more than 9 parameters) REM :PLOOP IF '%1' == '' GOTO CALLREXX SET REXXParms=%REXXPARMS% %1 SHIFT GOTO PLOOP :CALLREXX REM *** now call the REXX program REM *** This line was buggy in RXT&T versions prior to 3.20! REM The parameter for the REXX program is the name of the REM environment variable containing the parameters! myRexxProg REXXParms
Second, use the following prolog to get the parameters in your REXX program:
/* sample REXX program prolog to get the parameters from an */ /* environment variable */ /* get the name of the environment variable with */ /* the parameters */ parse arg parmVar . /* get the parameters from the environment */ /* variable */ thisParms = value( parmVar , , "OS2ENVIRONMENT" ) /* if necessary, delete surrounding " */ thisParms = strip( strip( thisParms ), 'B', '"' ) say "The parameters for this program are:" thisParms
And third: Use only the OS/2 batch program to launch your REXX program.
Passing parameters to Rexx Dialog programs
Use the following technique to pass parameters to Rexx Dialog programs:
/* ------------------------------------------------------------------ */ /* sample prolog for Rexx Dialog programs to access parameters from */ /* the command line. */ /* */ /* Note that you must call this CMD without the prefix RX to make */ /* this work! */ /* */ /* Use PMREXX to create a WPS object for this program that does not */ /* open an OS/2 window when running this program. */ /* */ /* Note further that you cannot use the default REXX queue to pass */ /* the parameters to the Rexx Dialog program. */ /* */ /* name of the environment variable to pass */ /* the parameters */ envVarName = "ThisParms" /* check the address environment */ if address() <> "RXDLG" then do /* THIS part is only executed if NOT running */ /* under RXDLG! */ /* get the name of this program */ parse source . . thisProg /* check for parameters and save them in an */ /* envionment variable if any exist */ parse arg thisParms call value envVarName, thisParms, "OS2ENVIRONMENT" /* now call the program again using RX.EXE */ "@RX " thisProg exit rc end /* if adress() <> "RXDLG" then */ /* THIS part is only executed if we are */ /* running under RXDLG! */ /* get the parameters */ thisParms = value( envVarName, , "OS2ENVIRONMENT" ) rxSay "Parameters for this program are: " thisParms exit 0
Special environment variables for REXX programs
The following environment variables have a special meaning for REXX programs:
RXTRACE
If the environment variable RXTRACE is set to ON before executing a REXX program, the REXX program is executed in single-step mode. This is equivalent to including the REXX �TRACE command with the parameter ?R. Any other value for the variable is ignored by the REXX interpreter.
Note: The REXX Report 1994 contains a detailed description about the debug facilities of REXX.
Hint: To prevent the execution of a REXX program in single-step mode, even if the environment variable RXTRACE is set to ON, use the statement:
dummy = trace( 'OFF' )
as the first statement of your program. This is very useful to prevent tracing programs like PMREXX for a REXX program "compiled" with programs like REXXCC.
see also Catching debug output)
RXQUEUE
This environment variable contains the name of the queue that the program RXQUEUE.EXE should use if the queue name is NOT specified when RXQUEUE is launched. The default queue used by RXQUEUE is the default REXX queue SESSION. (see also The RXQUEUE filter)
Special filenames
INIT.CMD
"There is a hint in the OS/2 developer documentation that there was at one time a planned use for the file INIT.CMD. I would recommend that you use a different file name than INIT.CMD regardless."
Source: Richard K. Goran
OS2INIT.CMD
"In earlier (1.x) versions of OS/2, the OS2INIT.CMD file was used as a session-by-session AUTOEXEC.BAT, by setting the shell in the config.sys to be CMD.EXE /K OS2INIT.CMD instead of just CMD.EXE. (It's still possible to do that now)."
"Most recommendations I have read say you should not name any file OS2INIT.CMD because it is still considered to be 'reserved' (like STARTUP.CMD)."
(see also Simulating an AUTOEXEC.BAT for OS/2 sessions)
String constants
The maximum length for a string constant in OS/2 REXX is 250. To use strings longer than 250 chars, split them into pieces less than 250 chars and concatenate them in a variable. (There's no limitation for the length of the contents of a variable in OS/2 REXX.) Example:
myConstant =, "This is a really long string constant. " || , "Well, because it is to long for a constant in OS/2 REXX " || , "We split it into smaller pieces and concatenate them " || , "with the operator || to a big string and save the " || , "result in a variable. This is possible, because there " || , "is no limit for the length of a variable in REXX. "
Source: IBM: OS/2 2.0 Procedures Language/2 Reference Manual
Variables
Special variables
A special variable is one that may be set automatically during processing of a REXX program.
There are 3 (5 in Object-Oriented REXX) special variables used by the REXX interpreter:
RC
This variable contains the return code of the last external non-REXX command called (e.g. OS/2 commands like DIR or EAUTIL) In case of a SYNTAX error this variable is set to the syntax error number (1-99). After an ERROR or FAILURE condition RC contains the command return code. Commands executed manually while tracing interactively do not change the value of RC. (see also Pipes & Errorlevel)
RESULT
This variable contains the return code of an internal or external REXX function if called with the CALL statement. The variable RESULT is dropped if the REXX function doesn't return a return code. It's also dropped if the function is called in an implicit or explicit assignment (e.g. a = myFunc() or if myFunc() = 4 then)
SELF (Object-Oriented REXX only)
"This variable is set when a method is activated. Its value is the object that forms the execution context for the method (that is, the receiver object of the activating message)."
SIGL
This variable is set to the line number of the last instruction that caused a transfer of control to a label (that is, any �SIGNAL, �CALL, internal function call, or trapped condition).
SUPER (Object-Oriented REXX only)
"This variable is set when a method is activated. Its value is the class object that is the usual starting point for a superclass method lookup for the SELF object."
Note: You can change these variables just like any other REXX variable. You should save the values of the special variables in a normal variable if you want to use them in other statements!
Read-only and Write-only environment variables
You can use REXX to create environment variables that the CMD.EXE either can not read or not write.
To create read-only environment variables use names with an imbedded equal sign, for example =MYREXXVAR.
The CMD.EXE can only read (but not change) environment variables that contain equal signs in the name. It is not possible to modify or delete those variables from the command line or from within a plain batch program.
To create write-only environment variables use the names 0, 1, to 9.
The CMD.EXE can only write (or delete) these environment variables but not read them. If you code something like
ECHO %1 SET myvar=%2
%1 or %2 always reference the parameter of the batch program. And if you use them on the command line they are either empty or contain their name as value.
Compound variables
To avoid mysterious errors you should always use some special chars as first chars of a tail (for example two underscores __) and never use these chars for normal variables (or vice versa).
Another nice method to avoid errors: Always use a number (0..9) for the first character of the tails. This method works because variable names can't begin with a number.
Source: I found this hint in the documentation for the DLL RXU
Defining compound variables
A useful technique to define compound variables is
/* stem with the WPS objects to create */ j = 0 objects.0 = j j = j + 1 /* use the index "CodeServerStemEntry" v1.80 */ /* for reference to this entry v1.80 */ CodeServerStemEntry = j /* v1.80 */ objects.j.__class = "WPFolder" objects.j.__title = ServerName || "^CodeServer" j = j + 1 objects.j.__title = "Start the ^CodeServer" || ServerName objects.j.__class = "WPProgram" j = j + 1 objects.j.__title = "Stop the ^CodeServer" || ServerName objects.j.__class = "WPProgram" j = j + 1 objects.j.__title = "Status the ^CodeServer" || ServerName objects.j.__class = "WPProgram" objects.0 = j /* access the entry for the folder v1.80 */ FolderTitle = objects.CodeServerStemEntry.__title /* v1.80 */
This technique makes it possible to insert and delete further elements without changing the index for the other elements. (see also Using "open" compound variables)
Using "open" compound variables
If you're using compound variables completely defined in your source code, you may use a technique I call "open compound variables" shown in the example below.
The advantage of this method: You can add new variables to the end of the compound variable without maintaining a counter (stem.0 is not used). I often forget to correct the counter after adding new entries to the compound variable. Note that using this method in Classic REXX is only useful if you define the compound variable completely in the source code (no changes at runtime)!
In Object-Oriented REXX you can use it anyway because there you can get all stem entries very simply with the DO ... OVER construct (see the code at the end of the example).
/* sample for using "open" compound variables */ /* drop the stem (to be sure) */ drop nameStem. /* define the compound variable */ nameStem.1 = "Albert" nameStem.2 = "Robert" nameStem.3 = "William" /* use the compound variable */ /* use the function symbol to find the last entry */ /* of the compound variable */ do i = 1 while symbol( "nameStem." || i ) = "VAR" say "Entry no. " || i || " is " nameStem.i end /* do i = 1 while symbol( "nameStem." || i ) = "VAR" */ /* Note: i-1 is the number of stem entries. You */ /* can save this value in the variable */ /* nameStem.0 and use the normal methods */ /* in the rest of your program. */ say "The compound variable contains " || i-1 || " entries." /* another useful method for a loop over all entries v2.50 */ /* Note that this method is only possible in Object REXX!!! v2.50 */ parse version thisVersion . if thisVersion = "OBJREXX" then do say "In Object REXX you can use also the DO OVER construct:" j = 0; do i over nameStem. j = j + 1; say "Entry no. " || i || " is " namestem.i end /* do i over namestem. */ say "The compound variable contains " || j || " entries." end /* if thisVersion = "OBJREXX" then */
(see also Defining compound variables)