5.4. Naming Streams Unlike most programming languages, REXX does not use file handles; the name of the stream is also in general the handle (although some implementations add an extra level of indirection). You must supply the name to all I/O functions operating on a stream. However, internally, the REXX interpreter is likely to use the native file pointers of the operating system, in order to improve speed. The name specified can generally be the name of an operating system file, a device name, or a special stream name supported by your implementation. The format of the stream name is very dependent upon your operating system. For portability concerns, you should try not to specify it as a literal string in each I/O call, but set a variable to the stream name, and use that variable when calling I/O functions. This reduces the number of places you need to make changes if you need to port the program to another system. Unfortunately, this approach increases the need for PROCEDURE EXPOSE, since the variable containing the files name must be available to all routines using file I/O for that particular file, and all their non-common ancestors. Example: Specifying file names The following code illustrates a portability problem related to the naming of streams. The variable filename is set to the name of the stream operated on in the function call. filename = ‘/tmp/MyFile.Txt’ say ‘ first line is’ linein( filename ) say ‘second line is’ linein( filename ) say ‘ third line is’ linein( filename ) Suppose this script, which looks like it is written for Unix, is moved to a VMS machine. Then, the stream name might be something like SYS$TEMP:MYFILE.TXT, but you only need to change the script at one particular point: the assignment to the variable filename; as opposed to three places if the stream name is hard-coded in each of the three calls to LINEIN(). If the stream name is omitted from the built-in I/O functions, a default stream is used: input functions use the default input stream, while output functions use the default output stream. These are implicit references to the default input and output streams, but unfortunately, there is no standard way to explicitly refer to these two streams. And consequently, there is no standard way to refer to the default input or output stream in the built-in function STREAM(). However, most implementations allow you to access the default streams explicitly through a name, maybe the nullstring or something like stdin and stdout. However, you must refer to the implementation-specific documentation for information about this. Also note that standard REXX does not support the concept of a default error stream. On operating systems supporting this, it can probably be accessed through a special name; see system-specific information. The same applies for other special streams. Sometimes the term “default input stream” is called “standard input stream,” “default input devices,” “standard input,” or just “stdin.” The use of stream names instead of stream descriptors or handles is deeply rooted in the REXX philosophy: Data structures are text strings carrying information, rather than opaque data blocks in internal, binary format. This opens for some intriguing possibilities. Under some operating systems, a file can be referred to by many names. For instance, under Unix, a file can be referred to as foobar, ./foobar and ././foobar. All which name the same file, although a REXX interpreter may be likely to interpret them as three different streams, because the names themselves differ. On the other hand, nothing prevents an interpreter from discovering that these are names for the same stream, and treat them as equivalent (except concerns for processing time). Under Unix, the problem is not just confined to the use of ./ in file names, hard-links and soft-links can produce similar effects, too. Example: Internal file handles Suppose you start reading from a stream, which is connected to a file called foo. You read the first line of foo, then you issue a command, in order to rename foo to bar. Then, you try to read the next line from foo. The REXX program for doing this under Unix looks something like: signal on notready line1 = linein( ‘foo’ ) ‘mv foo bar’ line2 = linein( ‘foo’ ) Theoretically, the file foo does not exist during the second call, so the second read should raise the NOTREADY condition. However, a REXX interpreter is likely to have opened the stream already, so it is performing the reading on the file descriptor of the open file. It is probably not going to check whether the file exists before each I/O operation (that would require a lot of extra checking). Under most operating systems, renaming a file will not invalidate existing file descriptors. Consequently, the interpreter is likely to continue to read from the original foo file, even though its has changed. Example: Unix temporary files On some systems, you can delete a file, and still read from and write to the stream connected to that file. This technique is shown in the following Unix specific code: tmpfile = ‘/tmp/myfile’ call lineout tmpfile, ‘’ call lineout tmpfile,, 1 ‘rm’ tmpfile call lineout tmpfile, ‘This is the first line’ Under Unix, this technique is often used to create temporary files; you are guaranteed that the file will be deleted on closing, no matter how your program terminates. Unix deletes a file whenever there are no more references to it. Whether the reference is from the file system or from an open descriptor in a user process is irrelevant. After the rm command, the only reference to the file is from the REXX interpreter. Whenever it terminates, the file is deleted—-since there are no more references to it. Example: Files in different directories Here is yet another example of how using the filename directly in the stream I/O functions may give strange effects. Suppose you are using a system that has hierarchical directories, and you have a function CHDIR() which sets a current directory; then consider the following code: call chdir ‘../dir1’ call lineout ‘foobar’, ‘written to foobar while in dir1’ call chdir ‘../dir2’ call lineout ‘foobar’, ‘written to foobar while in dir2’ Since the file is implicitly opened while you are in the directory dir1, the file foobar refers to a file located there. However, after changing the directory to dir2, it may seem logical that the second call to LINEOUT() operates on a file in dir2, but that may not be the case. Considering that these clauses may come a great number of lines apart, that REXX has no standard way of closing files, and that REXX only have one file table (i.e. open files are not local to subroutines); this may open for a significant astonishment in complex REXX scripts. Whether an implementation treats ././foo and ./foo as different streams is system-dependent; that applies to the effects of renaming or deleting the file while reading or writing, too. See your interpreter’s system-specific documentation. Most of the effects shown in the examples above are due to insufficient isolation between the filename of the operating system and the file handle in the REXX program. Whenever a file can be explicitly opened and bound to a file handle, you should do that in order to decrease the possibilities for strange side effects. Interpreters that allow this method generally have an OPEN() function that takes the name of the files to open as a parameter, and returns a string that uniquely identifies that open file within the current context; e.g. an index into a table of open files. Later, this index can be used instead of the filename. Some implementations allow only this indirect naming scheme, while others may allow a mix between direct and indirect naming. The latter is likely to create some problems, since some strings are likely to be both valid direct and indirect file ids.