Characters versus Lines
The In's and Out's of REXX
Input / Output Instructions and Functions
I/O in REXX is really a very simple matter, yet it seems confusing to both programming rookies and programming veterans alike. Newcomers aren't sure how to get started and programmers familiar with other languages are confused by the relative simplicity of I/O in REXX. Table 1 contains a list of the I/O related instructions and functions in OS/2 SAA REXX (functions are indicated by the paired parenthesis following the name). There are character (CHAR) related functions, line (LINE) related functions, screen related functions (Sys...), REXX external data queue functions (QUEUED) and one that is unique to the OS/2 Presentation Manager environment (RxMessageBox). The SAY instruction writes output to the screen while the PULL, PUSH, and QUEUE instructions deal with REXX external data queues. The character, line, and queue functions are similar in purpose. CHARS(), LINES() and QUEUED() Each is used to determine if data exists in the respective file or queue. Though the SysFileDelete() function is not exactly an I/O function, I have included it in the table since, if a file exists, REXX output will be append to the file. The SysFileDelete() function should be called first to erase any existing file when a new file is to be created.
The PC file system, independent of FAT or HPFS, is a character based file system rather than a record based file system as is found in the IBM mainframe environment. This factor is important to remember when using CHARS() vs. LINES(). On a character based file system, the CHARS() function will return the number of characters remaining on the input source whereas the LINES() function will only return a Boolean value of 0 or 1 (false or true) indicating the absence or presence of remaining data. On the other hand, the use of these functions is reversed on a record based file system. CHARS() will only return a Boolean value of 0 or 1 indicating the absence or presence of data whereas LINES() will return the actual number of records remaining on the input source. REXX data queues are always line oriented with the QUEUED() function returning the number of lines remaining in the REXX external data queue.
Characters versus Lines
The only difference between the character and line input and output functions is the absence / presence of an end-of-line designator. With both DOS and OS/2, the end-of-line designator defaults to a carriage return / line feed pair ('0D0A'x). Other platforms differ and in some cases use simply a line feed ('0A'x) to indicate the end of a line.
The LINEIN() function in OS/2 REXX will accept either the cr/lf pair or either character independently to indicate the end of a line and return the data from the current position in the input stream (file) up to, but not including, the end-of-line designator (which is always transparent to the function). CHARIN(), on the other hand, returns the number of characters specified or to the end of the input stream, whichever is less. There is no restriction placed on the contents of the data retrieved with the CHARIN() function. LINEIN() simply parses the data and returns it, a line at a time, with the end-of-line designator removed whereas CHARIN() returns a specified number of characters.
Similarly, the CHAROUT() function writes a specified number of characters to the output stream and the LINEOUT() function writes a specified number of characters to the output stream and appends the carriage return / line feed pair following the specified data. For example:
call LINEOUT file_name, '1'
will result in three characters being written to the file - the character 1 followed by a carriage return / line feed pair. If the CHAROUT() function is used in place of the LINEOUT() function, only the character 1 is written to the file.
In some instances, it is more efficient to read an entire file into storage and parse it for a particular value than to read the file line-by-line looking for a certain line or lines. Listing 1 contains just such a program. The program 9510ls01.cmd reads the config.sys file, parse it for the LIBPATH= line, and writes the tokens of the LIBPATH= line to the screen. The programs appearing here are available for download from ftp://ftp.cfsrexx.com/pub/. I have appended sequence numbers to the source programs simply for easy reference in this column.
Line 04 is the search argument used to find the LIBPATH= statement in the file area. It is preceded by a carriage return / line feed pair to assure that LIBPATH= begins in the first position of the line. Otherwise, if LIBPATH= occurred in a comment, it would satisfy the search criteria. Line 10 contains a function within a function, a technique I usually frown upon. However, here is an instance where its use is warranted. The combined functions assign the drive letter and colon of the boot drive to the beginning of the string config_sys_file. Lines 23 - 27 read the entire config.sys file, close the file, and copy the contents of the file to a hold area while, at the same time, translating the data to uppercase. This provides a simple method of case-insensitively searching the data. The actual string will be extracted from the untranslated area so that case is preserved for displaying the tokens within the LIBPATH= line.
Typically, screen output is produced with the Say keyword instruction. However, the CHAROUT() and LINEOUT() functions can also be used to write data to the screen. Omitting the first parameter (file name) from a call or function invocation to either function causes the STDOUT file handle to be selected. STDOUT is a name reserved by the operating system as the standard output device. Also, a file name of 'CON:' can be specified as the file name parameter. Use of CON: is subtly different than allowing the data to go to STDOUT since STDOUT can be redirected and CON: is the just like using a user-selected file name. Listing 2 contains a program which writes a bright white on cyan string on the screen and then creates the illusion of a spinning cursor at the end of the colored line for ten seconds. It is important to note that using a file name of CON: uses a file handle like an other file and therefore, the file must be closed to release the resource.
Opening and Closing files
Probably the least understood part of REXX I/O is the opening and closing of files. It is not necessary to explicitly open a file in REXX so long as an implied open in both read and write mode is acceptable. That's exactly what happens when a file is referenced with CHARIN() or LINEIN(). CHAROUT() and LINEOUT() obviously have an implied write mode open.
Files can be closed with a call to either CHAROUT() or LINEOUT() with just a single parameter of the file name. However, it goes against my grain to call LINEOUT() for an input file. Therefore, I strongly recommend that the STREAM() function be used to close a file. Closing a file, either input or output, releases an operating system resource known as a file handle. Since there are just a finite number of file handles available (15 useable at the present time), you should always remember to close a file after referencing it. If you have a requirement for more that 15 files being open simultaneously, REXXLIB, from Quercus Systems provides the DOSFILEHANDLES() function to increase the number available to a given session. Also, the new REXXUTIL module anticipated from IBM which I detailed in last month's column will provide the ability to increase or to explicitly set the number of file handles per session.
The STREAM() function can also be used to position the read / write pointer to a file as well as permitting information about a specific file to be retrieved. Information available via the STEAM() function includes the files presence ('QUERY EXISTS'), size ('QUERY SIZE'), and date and time stamp ('QUERY DATETIME').
In my January column (pages 55-58) I covered REXX external data queues. In this column, I am only discussing data queues and there relationship to I/O. Lines can be placed in a REXX data queue with the keyword instruction PUSH and QUEUE. The former causes the data to be inserted for first-in, first-out (FIFO) retrieval and the latter results in the data being inserted for last-in, first-out (LIFO) retrieval. Also, data can be redirected or piped to a queue with the inappropriately named RxQueue subcommand (not to be confused with the RXQUEUE() function. For example,
dir | RxQueue
will result in the output of the DIR command being stored in the REXX data queue. The data could be extracted from the queue with the following sequence:
do while QUEUED() > 0> input_line = LINEIN( 'QUEUE:' )> say input_line> end>
Note that the special file name of QUEUE: is used in this instance. If both the STDOUT and STDERR output is to be piped, the following notation would be used:
dir | 2>&1 | RxQueue
STDERR would receive the error message produced when there are no files to be displayed (SYS0002: The system cannot find the file specified.).
RxMessageBox() is a unique built-in function found only in OS/2 REXX. It provides a graphical user interface (GUI) method of simulating a command line interface on the OS/2 Desktop. It can only be used in REXX programs that are run in Presentation Manger (PM) mode. To satisfy this requirement, IBM supplied the PMREXX program. Details of the RxMessageBox() function can be found in the rexx.inf file. One of the major drawbacks to using PMREXX for regular programming is that it always pops up a dialog box with "The REXX procedure has ended." when the user program completes. This foolish message is hard-coded into PMREXX and there is no getting around it.
REXXUTIL I/O functions
REXXUTIL contains the I/O functions in Table 1 that begin with "Sys". They are all screen related, are reasonably self-explanatory, and are included in the rexx.inf documentation. The program in [#Listing 2 Listing 2] uses the SysCurState() function to turn the cursor off and back on.
/* 9510LS01.CMD - Parse Libpath from CONFIG.SYS */ /* 01 */ /* 02 */ crlf = '0D0A'x /* carriage return / line feed *//* 03 */ libpath_search_arg = crlf || 'LIBPATH=' /* 04 */ /* 05 */ /*--------------------------------------*\ /* 06 */ | Determine CONFIG.SYS location & size | /* 07 */ \*--------------------------------------*/ /* 08 */ config_sys_file =, /* 09 */ LEFT( VALUE( 'RUNWORKPLACE',, 'OS2ENVIRONMENT' ), 2 ) ||, /* 10 */ '\CONFIG.SYS' /* 11 */ config_sys_size =, /* 12 */ STREAM( config_sys_file, 'C', 'QUERY SIZE' ) /* 13 */ if config_sys_size = then /* 14 */ do /* 15 */ say 'Unable to locate' config_sys_file /* 16 */ exit /* 17 */ end /* 18 */ /* 19 */ /*-----------------------------*\ /* 20 */ | Read entire file & close it | /* 21 */ \*-----------------------------*/ /* 22 */ config_sys_area =, /* 23 */ CHARIN( config_sys_file, 1, config_sys_size ) /* 24 */ call STREAM config_sys_file, 'C', 'CLOSE' /* 25 */ uppercase_config_sys_area =, /* 26 */ TRANSLATE( config_sys_area ) /* uppercase for searching */ /* 27 */ /* 28 */ /*------------------------------------*\ /* 29 */ | Find LIBPATH= at beginning of line | /* 30 */ \*------------------------------------*/ /* 31 */ libpath_beg_pos =, /* 32 */ POS( libpath_search_arg, uppercase_config_sys_area ) + 2 /* 33 */ if libpath_beg_pos = 2 then /* 34 */ do /* 35 */ say 'Unable to locate LIBPATH=' /* 36 */ exit /* 37 */ end /* 38 */ libpath_end_pos =, /* 39 */ POS( crlf, uppercase_config_sys_area, libpath_beg_pos ) /* 40 */ libpath_string =, /* 41 */ SUBSTR( config_sys_area,, /* 42 */ libpath_beg_pos,, /* 43 */ libpath_end_pos - libpath_beg_pos ) /* 44 */ /* 45 */ /*------------------------*\ /* 46 */ | Write tokens to screen | /* 47 */ \*------------------------*/ /* 48 */ token_number = 0 /* 49 */ do while libpath_string <> /* 50 */ parse value libpath_string with, /* 51 */ token, /* 52 */ ';', /* 53 */ libpath_string /* 54 */ token_number = token_number + 1 /* 55 */ if token_number = 1 then /* 56 */ do /* 57 */ say token || ';' /* 58 */ end /* 59 */ else /* 60 */ do /* 61 */ say COPIES( ' ', 8 ) || token || ';' /* 62 */ end /* 63 */ end /* 64 */ exit /* 65 */
/* 9510LS02.CMD - Color & spinning character */ /* 01 */ /* 02 */ /*-------------------*\ /* 03 */ | Register REXXUTIL | /* 04 */ \*-------------------*/ /* 05 */ call RxFuncAdd 'SysLoadFuncs', 'REXXUTIL', 'SysLoadFuncs' /* 06 */ call SysLoadFuncs /* 07 */ /* 08 */ /*-----------*\ /* 09 */ | Constants | /* 10 */ \*-----------*/ /* 11 */ bksp = '08'x /* 12 */ crlf = '0D0A'x /* 13 */ progress_list = '\|/' /* 'C45C7C2F'x */ /* 14 */ progress_subscript = 1 /* 15 */ white_on_cyan = '1B'x || '[1;37;45m' /* bright white on cyan */ ansi_off = '1B'x || '[0m' /* reset to normal */ /* 18 */ /*----------------------------------*\ /* 19 */ | Write string and turn off cursor | /* 20 */ \*----------------------------------*/ /* 21 */ output_string =, /* 22 */ COPIES( ' ', 25 ) ||, /* 23 */ white_on_cyan ||, /* 24 */ 'Watch the spinner! ' ||, /* 25 */ ansi_off /* 26 */ call CHAROUT 'CON:', output_string /* 27 */ call SysCurState 'OFF' /* 28 */ /* 29 */ /*------------------------------*\ /* 30 */ | Loop writing spin characters | /* 31 */ \*------------------------------*/ /* 32 */ call TIME 'E' /* reset elapsed timer */ /* 33 */ do until TIME( 'E' ) > 10 /* run for n seconds */ /* 34 */ call CHAROUT 'CON:', bksp ||, /* 35 */ SUBSTR( progress_list,, /* 36 */ progress_subscript,, /* 37 */ 1 ) /* 38 */ progress_subscript = progress_subscript + 1 /* 39 */ if progress_subscript > LENGTH( progress_list ) then /* 40 */ do /* 41 */ progress_subscript = 1 /* 42 */ end /* 43 */ end /* 44 */ /* 45 */ /*----------------------------------*\ /* 46 */ | Close CON to release file handle | /* 47 */ \*----------------------------------*/ /* 48 */ call LINEOUT 'CON:', bksp || ' ' /* clear spinner character */ /* 49 */ call STREAM 'CON:', 'C', 'CLOSE' /* 50 */ call SysCurState 'ON' /* turn cursor back on */ /* 51 */ exit /* 52 */