Jump to content

REXX Tips & Tricks:General hints for REXX: Difference between revisions

From EDM2
Created page with "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 ...."
 
Line 69: Line 69:


Source: [[Ian Collier]]
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)
<PRE>
/* ------------------------------------------------------------------ */
/*                                                                    */
/* 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
</PRE>
==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
<PRE>
/* ------------------------------------------------------------------ */
/* 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 )
</PRE>
==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):
<PRE>
ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
³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                        ³
ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
</PRE>
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.
<PRE>
/* 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
</PRE>
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:
<PRE>
/* 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!"
</PRE>
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.

Revision as of 18:26, 4 November 2012

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.