7. File Access

Introduction

/* ------------------------------------------------------------------ */
/* REXX sports, as an ANSI Standard feature, a highly simplified file */
/* I/O model. Features:                                               */
/*                                                                    */
/* * File name is used as the 'handle'                                */
/* * Implicit file opening on first use                               */
/*                                                                    */
/* This model certainly promotes ease-of-use. It is also designed to  */
/* be platform agnostic, so that the same I/O code *should* work on   */
/* widely differing platforms [at least that's the theory ;) !]. On   */
/* the other hand, it:                                                */
/*                                                                    */
/* * Is quite 'alien' to those accustomed to file descriptor-based I/O*/
/*   as found in *NIX / C / Perl/Ruby/Python                          */
/* * Makes it impossible to have multiple 'views' [via multiple handl-*/
/*   es] of the same file, or to redirect I/O within the program [that*/
/*   is, without 'shelling out' or using temporary files]             */
/*                                                                    */
/* Consequently, many of the examples in this chapter are not directly*/
/* implementable in REXX. However, wherever possible, the task will be*/
/* performed with some other approach even if it comes across as some-*/
/* what contrived.                                                    */
/* ------------------------------------------------------------------ */

filename = "data.txt"                    /* ANSI-standard I/O */

/* Explicit OPEN, CLOSE, and stream status check */
if STREAM(filename, 'C', "OPEN READ") == "READY:" then do
  do while LINES(filename) > 0
    line = LINEIN(filename) ; if line == NULL then ; leave
    spos = POS("blue", line)
    if spos > 0 then say SUBSTR(line, spos)
  end
  call STREAM filename, 'C', "CLOSE"
end

/*
   Alternative: implicit OPEN, CLOSE; 'null' check - terminates on
   either EOF or 'empty' line [use 'LINES(...) == 0' check to verify
   EOF]
*/

line = LINEIN(filename)
do while line <> NULL
  spos = POS("blue", line)
  if spos > 0 then say SUBSTR(line, spos)
  line = LINEIN(filename)
end

/* ----------------------------- */

filename = "data.txt" ; fh = "data"      /* AREXX-compatible I/O */

if OPEN(fh, filename, "READ") then do
  line = READLN(fh)
  do until EOF(fh)
    spos = POS("blue", line)
    if spos > 0 then say SUBSTR(line, spos)
    line = READLN(fh)
  end
  call CLOSE fh
end

/* ----------------------------- */

/*
   LINEIN's default stream is "<stdin>", LINEOUT's is "<stdout>";
   'null' check - terminates on either EOF or 'empty' line [use 
   'LINES(...) == 0' check to verify EOF]
*/

line = LINEIN()
do while line <> NULL
  if VERIFY("0123456789", line, 'M') == 0 then
    call LINEOUT "<stderr>", "No digit found"
  call LINEOUT , line
  line = LINEIN() ; if LINES() == 0 then ; leave
end

/* Alternative: STREAM to check stream status, PARSE VALUE LINEIN */

do while STREAM("<stdin>", 'S') \= "NOTREADY"
  parse value LINEIN() with line
  if line <> NULL then do
    if VERIFY("0123456789", line, 'M') == 0 then
      call LINEOUT "<stderr>", "No digit found"
    call LINEOUT , line
  end
end

/* Alternative: Data extracted from STACK - REXX idiomatic */

SYSCMD = 'type data.txt | rxqueue'   /* Platform-specific [Win32] */
'SYSCMD'                             /* Direct data into STACK */

do while QUEUED() > 0
  parse pull line
  if line <> NULL then do
    if VERIFY("0123456789", line, 'M') == 0 then
      call LINEOUT "<stderr>", "No digit found"
    call LINEOUT , line
  end
end

/* ----------------------------- */

call STREAM(logfile, 'C', "OPEN WRITE")

/* ----------------------------- */

call STREAM(logfile, 'C', "CLOSE")

/* ----------------------------- */

/*
   There is no concept of 'default stream' in REXX. The I/O BIF's
   simply assume a default value of either "<stdin>" or "<stdout>"
   when called without an explicit stream argument.
*/

filename = logfile
call LINEOUT filename, "Countdown initiated ..."

filename = originalfile
call LINEOUT filename, "You have 60 seconds to reach minimum safe",
                       "distance ..."

Opening a File

call STREAM path, 'C', 'READ'  /* open file "path" for reading only */
call OPEN alias, path, 'READ'

call STREAM path, 'C', 'WRITE' /* open file "path" for writing only */
call OPEN alias, path, 'WRITE'

call STREAM path, 'C', 'BOTH'  /* open "path" for reading and writing */
call OPEN alias, path, 'WRITE' /* allows both read and write */

/*
   open file "path" write only, create it if it does not exist,
   truncate to zero length if exists
*/
call STREAM path, 'C', 'WRITE REPLACE'

/* open file "path" write only, fails if file exists */
/* Cannot do - must check for file existence and manually fail */

/* open file "path" for appending */
call STREAM path, 'C', 'WRITE APPEND'
call OPEN alias, path, 'APPEND'

/* open file "path" for appending only when file exists */
/* Cannot do - must check for file existence and then take action */

/* open file "path" for reading and writing */
call STREAM path, 'C', 'BOTH'
call OPEN alias, path, 'WRITE' /* allows both read and write */

/* open file for reading and writing, create file if doesn't exist */
call STREAM path, 'C', 'BOTH APPEND'
call OPEN alias, path, 'APPEND' /* allows both read and append */

/* open file "path" reading and writing, fails if file exists */
/* Cannot do - must check for file existence and manually fail */

Opening Files with Unusual Filenames

/* ------------------------------------------------------------------ */
/* REXX has no problem handling files with unusual filenames, thus    */
/* nothing beyond normal file handling need be done.                  */
/* ------------------------------------------------------------------ */

/* *** Translation skipped *** */

Expanding Tildes in Filenames

/* ------------------------------------------------------------------ */
/* This item is *NIX-specific; code examples reflect this.            */
/*                                                                    */
/* The general approach taken in REXX to such a task is to construct  */
/* a 'command string', that is, a sequence of characters that can be  */
/* sent to the platform's command interpreter [a.k.a. command         */
/* processor or shell] for execution. In most cases generated output  */
/* is captured and used as the 'result' of the command. Depending on  */
/* the platform, too, there may also be an 'command status code'      */
/* available that may be used for diagnostic purposes.                */
/*                                                                    */
/* REXX supports two modes of 'command execution':                    */
/*                                                                    */
/* * Implicit i.e command is passed directly to the default shell     */
/* * Explicit, via the ADDRESS instruction; allows choice of shell,   */
/*   and output handling                                              */
/* ------------------------------------------------------------------ */

filename = "/myfile.dat"

/* ----------------------------- */

/* Implicit Command Execution [output redirected to system STACK] */
'echo ~ >LIFO' ; parse pull expandedTilde

filename = expandedTilde || filename

/* ----------------------------- */

/* Explicit Command Execution (1) [same as previous example] */
address SYSTEM 'echo ~' with OUTPUT LIFO "" ; parse pull expandedTilde

filename = expandedTilde || filename

/* ----------------------------- */

/* Explicit Command Execution (2) [output directed to stem variable] */
address SYSTEM 'echo ~' with OUTPUT STEM expandedTilde.

filename = expandedTilde.1 || filename

/* ----------------------------- */

/* Explicit Command Execution (3) [output directed to file] */
TMPFILE = './exp.$$$' ; DELCMD = 'rm -f' TMPFILE

address SYSTEM 'echo ~' with OUTPUT STREAM TMPFILE

expandedTilde = LINEIN(TMPFILE) ; filename = expandedTilde || filename

address SYSTEM DELCMD

Making Perl Report Filenames in Errors

parse source . . sourcefile

/* Trap file I/O conditions */
signal on NOTREADY

filename = "..."
call STREAM filename, 'C', 'OPEN READ'

/* Success */
say filename "was opened ok"
exit 0

/* Open error */
NOTREADY :
  say "In line" SIGL "of source file" sourcefile
  say "a" CONDITION('C') "condition was trapped."
  say "Could not open file" CONDITION('D') "for reading"
  exit 1

Creating Temporary Files

/*
   Utilise 'tmpnam' functionality via 'mktemp' utility
*/

tmpnam : procedure expose (globals)
  address SYSTEM 'mktemp' with OUTPUT STEM tmpnam.
  if RC \= 0 then ; tmpnam.1 = NULL
  return tmpnam.1

/* ----------- */

/*
   Use 'rexxUtil's' 'sysTempFileName'
*/

tmpnam : procedure expose (globals)
  tmpnam = "/tmp/" || sysTempFileName('??tmp???')

  if tmpnam \= NULL then do
    call STREAM tmpnam, 'C', 'OPEN WRITE'
    call STREAM tmpnam, 'C', 'CLOSE'
  end

  return tmpnam

/* ----------------------------- */

tmpnam = tmpnam()

if tmpnam == NULL then do
  say "Unable to create temporary file" ; exit 1
end

/* ----------- */

tmpnam = tmpnam()

do while tmpnam <> NULL
  tmpnam = tmpnam()
end

/* ... use file ... */

/* Delete file before exiting program ... */
call sysFileDelete tmpnam

/* ----------- */

tmpnam = tmpnam()

if tmpnam == NULL then do
  say "Unable to create temporary file" ; exit 1
end

do i = 1 for 10
  call CHAROUT tmpnam, i
end

call STREAM tmpnam, 'C', 'SEEK' 1 'READ' 'CHAR'

say "Tmp file has:" LINEIN(tmpnam)

Storing Files Inside Your Program Text

/* Data residing within a comment block */
signal DATA /*
Line 1 ...
Line 2 ...
Line 3 ...
*/

/* Load data into 'data' as a table of lines */
DATA:
  data = NULL
  do i = SIGL + 1
    line = SOURCELINE(i)
    if line = "*/" then leave
    if data == NULL then
      data = line
    else
      data = data || NEWLINE || line
  end

/* Use data */
say data

Writing a Filter

/* ------------------------------------------------------------------ */
/* STDIN, STDOUT and STDERR are implemented as the 'special' file nam-*/
/* es, "<stdin>", "<stdout>", and "<stderr>", respectively. In additi-*/
/* on, the ANSI Standard I/O routines use the first two of these as   */
/* defaults where a filename is not provided. This, together with the */
/* PARSE instruction, and an extensive set of string manipulation BIFs*/
/* makes the writing of filter programs quite straightforward in REXX.*/
/* ------------------------------------------------------------------ */

/* priming read */
line = LINEIN()

/* terminates on both 'empty' line and EOF - do LINES() check for EOF */
do while line <> NULL
  /* do something with 'line' */
  /* ... */

  /* let's now get another one ... */
  line = LINEIN()
end

/* ----------- */

/* priming read */
line = LINEIN()

/* check for data availability */
do while LINES() > 0

  /* if data was extracted i.e. not an empty line */
  if line <> NULL then do
    /* do something with 'line' */
    /* ... */
  end

  /* let's now get another line ... */
  line = LINEIN()
end

/* ----------- */

/* check for data availability */
do while STREAM("<stdin>", 'S') \= "NOTREADY"

  /* let's now get a line, optionally parsing it into fields ... */
  parse value LINEIN() with line

  /* if data was extracted i.e. not empty fields */
  if line <> NULL then do
    /* do something with 'line' */
    /* ... */
  end
end

/* ----------------------------- */

/* Processing a number of files passed on the command line */

/* No filename arguments, so assume working with STDIN */
if ARG() < 1 then
   call do_with "<stdin>"
else
  /* Process each filename argument in turn */
  do i = 1 for ARG()
    call do_with ARG(i)
  end

exit 0

/* ----------- */

do_with : procedure expose (globals)
  file = ARG(1)

  if STREAM(file, 'C', 'OPEN READ') \= 'READY:' then do
    say "Can't open" file ; return
  end

  line = LINEIN(file)

  do while LINES(file) > 0
    /* do something with line ... */
    say line

    line = LINEIN(file)
  end

  return

/* ----------------------------- */

argv = NULL ; argc = ARG()

/* Either grab file list from command-line, or ... */
if argc > 0 then
  if argc > 1 then
    /* filename(s) as separate argument strings [-a option] */
    do i = 1 for ARG() ; argv = argv ARG(i) ; end
  else
    /* filename(s) as single argument string */
    argv = ARG(1)
else
  /* ... get it yourself */
  argv = glob("*.[cCh]")

argv = STRIP(argv)

/* ----------------------------- */

/*
   The following are 'quickie' solutions matching the Perl examples;
   REXXToolkit has a 'getopt' routine offering functionality similar
   to *NIX 'getopt', and it would be the preferred approach
*/

/* arg demo: 1 [assume Regina '-a' option used] */
if ARG() > 0 & ARG(1) == "-c" then ; chop_first = chop_first + 1

/* ----------- */

/* arg demo: 2 [assume Regina '-a' option used] */
if ARG() > 0 & match(ARG(2), "^-[[:digit:]]+$") then
  parse value ARG(2) with "-" columns .

/* ----------- */

/* arg demo: 3 [assume Regina '-a' option used] */
parse SOURCE . . source

do i = 1 for ARG()
  parse value ARG(i) with "-" option .
  if option == NULL then ; iterate
  if VERIFY("ainu", option, 'M') == 0 then do
    call LINEOUT "<stderr>", "usage:" source "[-ainu] [filenames...]"
    exit 1
  end
  options = options || option
end

append = 0 ; ignore = 0 ; nostdout = 0 ; unbuffer = 0

if POS("a", options) > 0 then ; append = append + 1
if POS("i", options) > 0 then ; ignore = ignore + 1
if POS("n", options) > 0 then ; nostdout = nostdout + 1
if POS("u", options) > 0 then ; unbuffer = unbuffer + 1

/* ----------------------------- */

/* undef $/ not applicable; do following to load entire file */

/* STDIN - doesn't have a 'size', so use arbitrary 'large' value */
file_contents = CHARIN(,, 9999999)

/* Regular file - use actual file size */
file_contents = CHARIN(file,, CHARS(file))

/* ----------------------------- */

line = LINEIN()

do i = 1 while LINES() > 0
  say "-:" || i || ":" || line
  line = LINEIN()
end

/* ----------------------------- */

line = LINEIN()

do while LINES() > 0
  if match(line, "login") then ; say line
  line = LINEIN()
end

/* ----------- */

do while LINES() > 0
  /*
    'parse lower' is Regina-specific. Can otherwise use:
    line = TRANSLATE(line, "abc...", "ABC...")
  */
  parse lower LINEIN line
  if line <> NULL then ; say line
end

/* ----------- */

line = LINEIN() ; chunks = 0

do while LINES() > 0
  if match(line, "^#") then ; iterate
  if match(line, "_     _(DATA|END)_     _") then ; leave
  chunks = chunks + WORDS(line)
  line = LINEIN()
end

say "Found" chunks "chunks"

Modifying a File in Place with Temporary File

old = "..." ; new = "..."

/* Explicit file opening optional */
call STREAM old, 'C', 'OPEN READ' ; call STREAM new, 'C', 'OPEN WRITE'

/* Priming read */
line = LINEIN(old)

do while LINES(old) > 0
  if line <> NULL then do
    /* Change line ... */
    line = line || 3
  end ; else do
    /* Handle 'empty' line */
    nop
  end

  /* Write it to new */
  call LINEOUT new, line

  /* Get another line */
  line = LINEIN(old)
end

call STREAM old, 'C', 'CLOSE' ; call STREAM new, 'C', 'CLOSE'

call sysMoveObject old, "old.orig" ; call sysMoveObject new, old
call sysFileDelete "old.orig"

/* ----------------------------- */

/* ... */

line = LINEIN(old)

do while LINES(old) > 0
  if STREAM(old, 'C', 'QUERY SEEK READ LINE') == 20 then do
    call LINEOUT new, "Extra line 1 ..."
    call LINEOUT new, "Extra line 2 ..."
  end

  call LINEOUT new, line

  line = LINEIN(old)
end

/* ... */

/* ----------- */

/* ... */

line = LINEIN(old)

do while LINES(old) > 0
  line_number = STREAM(old, 'C', 'QUERY SEEK READ LINE')
  if line_number >= 20 & line_number <= 30  then ; iterate

  call LINEOUT new, line

  line = LINEIN(old)
end

/* ... */

Modifying a File in Place with -i Switch

/* ------------------------------------------------------------------ */
/* AFAIK, no REXX interpreter has an '-i' switch to force in-place    */
/* modification of files. The file modification has to be programmed  */
/* in, and it is this approach that will be used here.                */
/* ------------------------------------------------------------------ */

/*
   In-place modification not possible since replacement is not the
   same size. The 1st command-line is assumed to be the file name
*/

file = ARG(1) ; tmpnam = tmpnam()

call STREAM file, 'C', 'OPEN READ'
call STREAM tmpnam, 'C', 'OPEN WRITE'

/* Use REXXToolkit's 'strftime' */
today = strftime("+%Y-%m-%d", makeYMD()) ; line = LINEIN(file)

do while LINES(file) > 0
  line = subst("DATE", line, today) ; call LINEOUT tmpnam,, line
  line = LINEIN(file)
end

call STREAM file, 'C', 'CLOSE' ; call STREAM tmpnam, 'C', 'CLOSE'

call sysMoveObject file, "file.orig" ; call sysMoveObject tmpnam, file
call sysFileDelete "file.orig"

Modifying a File in Place Without a Temporary File

/* ------------------------------------------------------------------ */
/* While it's possible, using the ANSI Standard I/O routines, to alter*/
/* the contents of a file in-place, including appending additional da-*/
/* ta, it isn't possible to truncate the file. Therefore, in order to */
/* ensure file intergrity is maintained [i.e. file contains only any  */
/* necessary (not extraneous) data], a new file should be created, and*/
/* necessary data copied into it. Of course, this could be done as a  */
/* later step - in the interim, the extraneous data could be overwrit-*/
/* ten with some arbitrary value marking it as such. Messy, yes, but  */
/* doable :) !                                                        */
/* ------------------------------------------------------------------ */

/* [1] In-place modification of same-or-greater-length data */

file = "..."

call STREAM file, 'C', 'OPEN BOTH'

/* Move write pointer to start of file */
call STREAM file, 'C', 'SEEK' '1' 'WRITE' 'CHAR'

/* Locate and read required data */
data = CHARIN(file, some_offset, some_amount)

/* Do something to data ... */
data = ...

/* Write it back out, in-place, exactly replacing old data */
call CHAROUT file, some_offset, data

call STREAM file 'C', 'CLOSE'

/* ----------- */

/*
   [2] In-place modification of less-length data - file contents
   [bar the 'padded' items] should later be copied to a new file
*/

file = "..."

call STREAM file, 'C', 'OPEN BOTH'

/* Record initial size of file */
bytes = CHARS(file)

/* Move write pointer to start of file */
call STREAM file, 'C', 'SEEK' '1' 'WRITE' 'CHAR'

/* Locate and read required data */
data = CHARIN(file, some_offset, some_amount)

/* Do something to data ... */
data = ...

/* Write it back out partly replacing old data */
call CHAROUT file, some_offset, data

/* Pad out rest of file with arbitrary byte value */
call CHAROUT file, (some_offset + LENGTH(data)), D2C(0)

call STREAM file 'C', 'CLOSE'

/* ----------------------------- */

/* Preferred approach - copy data to new file then rename / delete */
old = "..." ; new = "..."

call STREAM old, 'C', 'OPEN READ' ; call STREAM new, 'C', 'OPEN WRITE'

/* Read, process, and write new data to new file */
/* ... */

call STREAM old, 'C', 'CLOSE' ; call STREAM new, 'C', 'CLOSE'

call sysMoveObject old, "old.orig" ; call sysMoveObject new, old
call sysFileDelete "old.orig"

Locking a File

/* ------------------------------------------------------------------ */
/* The ANSI Standard I/O BIF's, STREAM, LINE[IN|OUT], CHAR[IN|OUT],   */
/* don't implement file locking: a file may be opened by multiple     */
/* scripts for both read and write access, and there is no means of   */
/* specifiying, for example, that exclusive file access is needed.    */
/*                                                                    */
/* Where synchronised file update is required, say in updating a shar-*/
/* ed log file, the choice is to:                                     */
/*                                                                    */
/* * Use low-level routines that offer file locking [e.g. via library */
/*   or (Regina-only) GCI facility]                                   */
/* * Use an inter-process mutual exclusion mechanism [e.g. process th-*/
/*   at needs to write to the file acquires exclusive access (no other*/
/*   process can open the file for any purpose until it is released), */
/*   then releases it when done]                                      */
/* * Use some other inter-process signaling mechanism [e.g. access is */
/*   available to all processes, and any process that updates the file*/
/*   (e.g. appends to it) signals that the file has been updated. The */
/*   other processes will, on next attempt to use the file detect its */
/*   'status' change, so will close and reopen it, thus 'refreshing'  */
/*   their view of the file]                                          */
/*                                                                    */
/* The latter two approaches are possible via the mutex and event sem-*/
/* aphore facilities of the 'rexxUtil library. However, only an examp-*/
/* le of the former will be shown here.                               */
/* ------------------------------------------------------------------ */

/*
   Canonical example of mutex semaphore use in REXX ['rexxUtil'
   library] an approach that can be applied to ensure a process
   has exclusive access to a file. However, in order for this
   to work reliably all processes must follow the same protocol:

   - acquire lock
   - use file, then close
   - release lock

   Disadvantage is that only one process can use the file at any
   one time regardless of whether it is a read or update operation
*/

/* Attempt to acquire handle to existing semaphore */
sem = sysOpenMutexSem("SEMNAME")

/* If failed, then no semaphore exists, so create one */
if sem == 0 then ; sem = sysCreateMutexSem("SEMNAME")

timeout = 3000 /* ms */

/* Attempt to acquire exclusive access to resource */
if sysRequestMutexSem(sem, timeout) == 0 then do

  /* Ok, resource is acquired; so something with it */
  /* ... */

  /* All done with resource, so release it */
  call sysReleaseMutexSem sem

end ; else do

  /* Could not acquire resource - locked by other process */
  /* ... */

end

/* Close handle to semaphore - last 'close' will destroy it */
call sysCloseMutexSem sem

/* ----------------------------- */

/*
   Implementations of Perl examples
*/

/* ... prologue code omitted ... */

numfile = "..." ; timeout = 2

if sysRequestMutexSem(sem) \= 0 then do
  say "Cannot immediately write-lock the file" numfile "blocking..."
  call sysSleep timeout

  if sysRequestMutexSem(sem) \= 0 then do
    say "Can't get write-lock on" numfile
  end ; else do
    /* ... do something with 'numfile' ... */

    /* All done ... release lock */
    call sysReleaseMutexSem sem
  end
end ; else do
  /* ... do something with 'numfile' ... */

  /* All done ... release lock */
  call sysReleaseMutexSem sem
end

/* ----------- */

/* Can't implement 'select' example */

/* ----------- */

/* ... prologue code omitted ... */

numfile = "..." ; timeout = 2

if sysRequestMutexSem(sem) \= 0 then do
  say "Cannot write-lock the file" numfile "exiting ..."
  exit 1
end

if STREAM(numfile, 'C', 'OPEN BOTH') \= "READY:" then do
  say "Cannot open the file" numfile "exiting..."
  exit 1
end

/* ... do stuff with 'numfile' ... */

/* Close file and release semaphore */
call STREAM numfile, 'C', 'CLOSE' ; call sysReleaseMutexSem sem

Flushing Output

/* ------------------------------------------------------------------ */
/* There is, in the ANSI Standard I/O routines, no user control over  */
/* file buffering - it is all handled internally - thus most of the   */
/* examples in this section are not implementable.                    */
/* ------------------------------------------------------------------ */

/* It *is* possible to flush any file, including STDOUT */
call STREAM "<stdout>", 'C', 'FLUSH'

/* ----------------------------- */

/*
   See PLEAC 18 for examples of socket-based code
*/

Reading from Many Filehandles Without Blocking

/* ------------------------------------------------------------------ */
/* No such functionality is available natively in REXX. However, it   */
/* might be possible to use Regina's GCI facility to make available   */
/* the *NIX 'select' function [and any support functions it may need] */
/* in order to perform this task.                                     */
/* ------------------------------------------------------------------ */

@@INCOMPLETE@@
@@INCOMPLETE@@

Doing Non-Blocking I/O

/* ------------------------------------------------------------------ */
/* No such functionality is available natively in REXX. However, it   */
/* might be possible to use Regina's GCI facility to make available   */
/* the *NIX 'fcntl' function [and any support functions it may need]  */
/* in order to perform this task.                                     */
/* ------------------------------------------------------------------ */

@@INCOMPLETE@@
@@INCOMPLETE@@

Determining the Number of Bytes to Read

/* ------------------------------------------------------------------ */
/* The number of bytes in a file may be determined:                   */
/*                                                                    */
/* * Via the 'STREAM' BIF [which probably uses 'ioctl' on *NIX]       */
/* * Via the 'CHARS' BIF                                              */
/* * Opening the file, and seeking to the end                         */
/* ------------------------------------------------------------------ */

file = "..."

say "File" file "is" STREAM(file, 'C', 'QUERY SIZE') "bytes in size."

/* ----------------------------- */

file = "..."

call STREAM file, 'C', 'OPEN READ' ; bytes = CHARS(file)
call STREAM file, 'C', 'CLOSE'

say "File" file "is" bytes "bytes in size."

/* ----------------------------- */

file = "..."

call STREAM file, 'C', 'OPEN READ'
call STREAM file, 'C', 'SEEK' '<0' 'READ' 'CHAR'

bytes = STREAM(file, 'C', 'QUERY SEEK READ CHAR')

call STREAM file, 'C', 'CLOSE'

say "File" file "is" bytes "bytes in size."

Storing Filehandles in Variables

/* ------------------------------------------------------------------ */
/* REXX, through its ANSI Standard I/O functions, does not support fi-*/
/* le descriptor-based I/O; instead, the file name is used as the han-*/
/* dle. However, it is possible to query an open file's handle, though*/
/* this is of little practical use unless a library of low-level rout-*/
/* ines allowing file handle manipulation, is used. Thus, most of the */
/* code in this section is not implementable.                         */
/* ------------------------------------------------------------------ */

filename = "..."

/* Store file handle in a variable */
variable = STREAM(filename, 'C', 'QUERY HANDLE')

/* Pass file handle as argument to subroutine */
call subroutine STREAM(filename, 'C', 'QUERY HANDLE'), filename

/* ----------- */

subroutine : procedure
  fh = ARG(1) ; filename = ARG(2)
  say "File handle for file" filename "is" fh
  return

/* ----------------------------- */

/* Should display 0, 1, 2, respectively, the 'standard' I/O handles */
say STREAM("<stdin>", 'C', 'QUERY HANDLE')
say STREAM("<stdout>", 'C', 'QUERY HANDLE')
say STREAM("<stderr>", 'C', 'QUERY HANDLE')

Caching Open Output Filehandles

/* ------------------------------------------------------------------ */
/* REXX, through its ANSI Standard I/O functions, does not support fi-*/
/* le descriptor-based I/O; instead, the file name is used as the han-*/
/* dle. Therefore, the task of caching 'open file handles' is not app-*/
/* licable. Thus, the code in this section is not implementable.      */
/* ------------------------------------------------------------------ */

/* *** Translation skipped *** */

Printing to Many Filehandles Simultaneously

/* ------------------------------------------------------------------ */
/* REXX, through its ANSI Standard I/O functions, does not support fi-*/
/* le descriptor-based I/O. Some of the tasks in this section may be  */
/* performed by substituting file names for file descriptors, whilst  */
/* those involving shell invocation may be performed by constructing  */
/* a command string, and passing it to the ADDRESS instruction for ex-*/
/* ecution.                                                           */
/* ------------------------------------------------------------------ */

filenames = "a.txt b.txt c.txt" ; stuff_to_print = "..."

do while filenames <> NULL
  parse var filenames file filenames
  call LINEOUT file, stuff_to_print
end

/* ----------------------------- */

/* Generate data file */
datain = "..."
call LINEOUT datain, "..."

/* Output file names */
f1 = "..." ; f2 = "..." ; f3 = "..."

/* Build command string - redirects data to several files */
cmd = "tee" f1 f2 f3

/*
   Execute command string through the shell, with data
   directed to STDOUT, and 'tee'd to the output files
*/
address SYSTEM cmd with INPUT STREAM datain,
                        OUTPUT STREAM "<stdout>"

/* Delete the input data file */
call sysFileDelete datain

Opening and Closing File Descriptors by Number

/* ------------------------------------------------------------------ */
/* REXX, through its ANSI Standard I/O functions, does not support fi-*/
/* le descriptor-based I/O, thus most of the examples in this section */
/* cannot be implemented.                                             */
/*                                                                    */
/* The only descriptor-based task that may be performed is the use of */
/* the 'STREAM' BIF to query the file descriptor of currently-open    */
/* file [example shown below]. It is, of course, possible to pass this*/
/* information on to a shell script [invoked via the ADDRESS instruct-*/
/* ion], but this would merely be a, probably useless, contrivance.   */
/* ------------------------------------------------------------------ */

/* Open the file */
call STREAM file, 'C', 'OPEN READ'

/* Print file descriptor of this open file */
say STREAM(file, 'C', 'QUERY HANDLE')

/* Close the file */
call STREAM file, 'C', 'CLOSE'

/* Print [invalid] file descriptor of this, now-closed, file */
say STREAM(file, 'C', 'QUERY HANDLE')

Copying Filehandles

/* ------------------------------------------------------------------ */
/* REXX, through its ANSI Standard I/O functions, does not support fi-*/
/* le descriptor-based I/O, thus the notion of copying file handles is*/
/* moot. However, whilst STDIN, STDOUT, and STDERR cannot be directly */
/* altered, they can be temporarily mapped to files within the context*/
/* of the ADDRESS instruction [an example is shown below].            */
/* ------------------------------------------------------------------ */

RANDOMCMD = "cat" /* Platform-specific [*NIX] */

INFILE = "program.in" ; OUTFILE = "program.out"

/* Redirect command output */
address path RANDOMCMD with input STREAM INFILE,
                            output STREAM OUTFILE,
                            error STREAM "<stdout>"

/* Reset redirected streams to default values */
address path with input NORMAL output NORMAL error NORMAL

Program: netlock

/* ------------------------------------------------------------------ */
/* Program: netlock                                                   */
/* ------------------------------------------------------------------ */

@@INCOMPLETE@@
@@INCOMPLETE@@

Program: lockarea

/* ------------------------------------------------------------------ */
/* Program: lockarea                                                  */
/* ------------------------------------------------------------------ */

@@INCOMPLETE@@
@@INCOMPLETE@@