Chapter 12 - Files for Data Input and Output

Programs commonly use files to save data for later retrieval or to share data with another program. This chapter describes the use of files to store information produced by programs and to retrieve information stored by other programs. It also describes the ExecLib library of subroutines that give your programs added control over files and directories. Since sending textual output to a printer uses many of the same statements and techniques, this chap- ter also discusses how to print textual output.

A file is a unit of information saved on a diskette, a hard disk, or some other “permanent” storage device. A file may contain data, or it may contain a program or a library. Because they continue to exist after your program stops and even after you turn off your computer, files provide long-term storage. A program may access a file to read information from it or write information to it.

Files store information in a variety of ways. Some can contain only the text characters that you commonly enter at the keyboard and display on the screen. Others can store information in more efficient formats that cannot be dis- played directly on the screen. True BASIC can create and use text files and four forms of internal files — stream, random, record, and byte.

————————————————————————–––———————————————
[ ! ] Note: Different operating systems use different terminology for the organization of their storage
media. Throughout this chapter, the term “directory” refers to the organizational component of a disk
that may contain one or more files or other directories. Directories that are contained within another directory are “subdirectories” of that directory. Some operating systems refer to these as “folders” and “subfolders,” but this chapter uses the terms directory and subdirectory throughout.
——————————————————————–––—————————————————
True BASIC programs may use files stored anywhere in any directory or disk accessible to the computer running the program (this includes files and disks that are accessible across a network).

When you need to access a file, you must first open it using a file name that is appropriate to your computer’s oper- ating system. That name may be a simple file name, indicating that the file is stored in your current directory, or it may be a complete path name that indicates the file’s location. See “The True BASIC Environment” chapter in the introductory section of this manual for information on file and path names appropriate for the different com- puter operating systems.

The OPEN statement opens a file and assigns it a channel number. All other statements that operate on files use the channel number rather than the file name.

The first few sections in this chapter describe statements and operations that apply to all of True BASIC’s file types. The later sections discuss each file type and additional statements particular to that file type. Because the internal files share many characteristics, they are described as a group and then individually. The final section describes ExecLib routines that let your programs get information about files and directories and create, rename, and remove directories.

A Summary of File Types
True BASIC uses five kinds of data files. The basic differences between the types lie in how information is stored within the file and how you may access that information.

Text files use display format — they mimic the display that appears on your screen or printer. In text files, both string and numeric values are represented as characters; numeric values are automatically converted to the string of digits used to display them. To get information to and from text files, you use a channel number with the same PRINT and INPUT statements that you use with the screen, printer, or keyboard.

The other four file types store information in the internal format that the computer uses to represent values in memory. Each string value is stored as a series of one-byte characters (similar to text files), but each numeric value is stored in the internal IEEE eight-byte format that preserves its full precision. Because of the way information is stored within them, you must use WRITE and READ statements, with a channel number, to get information to and from internal files.

The five file types may be summarized as follows:
A text file is one that you may create and save using any text editor capable of saving a text-only file. As such, text files may be displayed on the screen. Every program you create and save with the True BASIC Editor is a text file. Since a compiled program cannot be displayed, it is not a text file. True BASIC views the printer as a text file with the restriction that a program cannot read from it (for obvious reasons). You may, however, write to the printer; hence, you may use file operations to produce hard copy on your printer. Text files are sequential-access files, meaning that you must access each record (or item) in the order in which it appears in the file.

A stream file is stored in internal format and cannot be displayed on the screen. Like a text file, a stream file is organized sequentially. That is, the elements must be read in exactly the same order in which they were written.

A random file is stored in internal format and cannot be displayed on the screen. The file is organized into records of fixed length, and you can jump to any record in the file and read it or change it. (Text or stream files do not per- mit this “random access.”) The records may contain any number of string and numeric values, in their internal for- mat, as long as their cumulative length does not exceed the record’s maximum length.

A record file is like a random file except that each record may contain only one numeric or string value.

A byte file is the most general type of file. You may access any file as a byte file, or simply a sequence of bytes. Approaching a file on the byte level lets you manipulate files created by any application, such as a word processor or spreadsheet, provided you know how that application has represented the information within the file. Because byte files let you operate on each byte individually, they also provide the flexibility required for effectively pack- ing information to conserve storage space.

Basic File Operations
This section and the next discuss operations and concepts common to all five data-file types. Each file type is then described more fully along with statements and operations particular to that file type.

Opening Files
Before you may use the information stored within a file, you must first open a channel to that file. This process uses an OPEN statement as follows:
OPEN #3: NAME “USEFUL”

The OPEN statement obtains access to the named file (through the current operating system) and associates that file with the specified channel number. Channel numbers always consist of a pound sign (#) followed by an inte- ger between 0 and 999 (or a numeric expression that evaluates to such a value). Note, however, that channel #0 is reserved for the default logical window, which is always open. (The default logical window is the default output window automatically opened by True BASIC; see Chapter 13 “Graphics” for more information.) After you’ve opened a file, you must always refer to it by its associated channel number.

The file name in the OPEN statement may be a simple name referring to a file in the current directory, or it may include a path name specifically indicating the location of the file. Legal file and path names vary between oper- ating systems, so be sure to check “The True BASIC Environment” chapter in the introductory section for the rules of any operating systems you or your program’s users will be using.

You may specify the file name as a string constant, a string variable, or a string expression as long as it evaluates to a legal file name for the current operating system. If a file with the specified name does not exist, an error results.

You may add several options after the file name in an OPEN statement to specify how the file should be opened and accessed. Each option consists of an option keyword and an option specifier, and each option is separated from its neighbors by a comma. For example:
OPEN #1: NAME “DATA”, ORG TEXT, CREATE OLD, ACCESS INPUT

The options allowed in the OPEN statement may be summarized as follows:

OPEN Statement Options
———————————————————————––———————————————
Option Effect
ORGANIZATION TEXT Open as text file ORGANIZATION STREAM Open as stream file ORGANIZATION RANDOM Open as random file ORGANIZATION RECORD Open as record file ORGANIZATION BYTE Open as byte file
CREATE NEW Create a new file
CREATE OLD Open an existing file (default) CREATE NEWOLD Open if exists, else create
ACCESS OUTIN Allow read/input or write/print (default) ACCESS INPUT Allow read or input only
ACCESS OUTPUT Allow write or print only
RECSIZE n Set record length for random, record, or byte files
——————————————————————————––————————————
You may abbreviate the option keyword ORGANIZATION as ORG.

If you specify the ORG TEXT, ORG STREAM, ORG RANDOM, or ORG RECORD option when opening an exist- ing file, True BASIC checks the file and gives an error if it does not match the specified type. If you use the ORG TEXT, ORG STREAM, ORG RANDOM, or ORG RECORD option when opening a new or empty file, the file becomes that file type. Any file may be opened with the ORG BYTE option; it will be treated as a byte file as long as it remains open, no matter what type it really is.
If you do not use an ORG option, True BASIC assumes the ORG TEXT option for new or empty files. Otherwise, it checks the file and uses its current type (compiled True BASIC programs are opened as ORG BYTE).
The CREATE options control what happens if the file does or does not already exist. The CREATE NEW option instructs True BASIC to create a new file with the specified name; an error occurs if a file with that name already exists. The CREATE OLD option opens an existing file; an error occurs if the file does not exist. The CREATE NEWOLD option opens the file if it already exists and creates it if it does not. The CREATE NEWOLD option is useful when a program will be run repeatedly; the first time it is run it creates a new file, afterwards it uses the existing one.
Omitting a CREATE option is the same as using the CREATE OLD option; that is, True BASIC looks for an exist- ing file by default.
The ACCESS options let you limit what the program can do with the open file. The ACCESS INPUT option lets the program read from the file but not change its contents — often an important safety feature. The ACCESS OUTPUT option lets the program modify a file, but not read it — this can protect the confidentiality of the file. The default is the ACCESS OUTIN option, which gives the program complete access to the file for both reading and writing.

For example:
OPEN #7: NAME “FILE22.TRUE”, CREATE NEWOLD, ORG TEXT
either will open an existing file called FILE22.TRU and make sure that it is a text file, or it will create one. Since an ACCESS option is not specified, both reading and writing will be allowed.

Even if you use ACCESS OUTPUT to limit access to a file to output, the operating system must permit True
BASIC to read that file to examine its type.

The RECSIZE option sets the record length for record, random, or byte files to the numeric value given with it. Records are components of random-access files and are discussed more fully in the sections on random and record files later in this chapter. If you use a RECSIZE option when opening a text or stream file, it will be ignored.

Although you must type out the option keywords, you may use string variables and string expressions in place of the option specifiers. This can be extremely useful when writing subroutines. Also, note that channel numbers may serve as parameters to subroutines (but not to functions). The channel-number parameter must consist of a pound sign (#) followed by an integer — not an expression. As usual, the channel number passed as the corre- sponding argument in the invocation need not be the same as the channel number used as the parameter in the subroutine. For example:
SUB FileOpen(org$, cr$, acc$, #9) ! Open specified file
PRINT “File name”; INPUT f$
OPEN #9: NAME f$, ORG org$, CREATE cr$, ACCESS acc$ END SUB
This may be invoked using a statement such as:
CALL FileOpen(“record”, “old”, “outin”, #1)
Invoking FileOpen in this way will get the name of a file from the user and open the existing record file with that name with full access privileges. The open file will be associated with channel #1 throughout the program unit that contains the CALL statement.

Note that in the above subroutine, errors could occur if the user names a file that does not exist or is not a record file. The following version of the subroutine provides better protection by “trapping” any errors. The WHEN struc- ture and other error-handling techniques are discussed in Chapter 16 “Error Handling.”
SUB FileOpen(org$, cr$, acc$, #9) ! Protected file opener
DO
CLOSE #9 ! In case channel is open
INPUT PROMPT “File name: “: fname$ WHEN ERROR IN
OPEN #9: NAME fname$, CREATE cr$, ORG org$, ACCESS acc$ EXIT SUB ! Success
USE

PRINT “Cannot open that file.”
END WHEN LOOP
END SUB
There are several reasons why an OPEN statement may fail. Therefore, it is generally a good idea to use an error handler whenever appropriate to give the user more than one chance to specify a file name, as in the above example.
Remember that you must open every file before you may access it. If you try to access a channel number that has not been properly opened (or that has been closed), an error will result.
Once a channel has been opened, it will remain open as long as it remains in existence or until you specifically close it, as described in the next section. Channels obey the same scope rules as variables. How long a channel “remains in existence” depends on where the channel is opened.

A channel that is opened in the main program (or in a procedure internal to the main program) remains in exis- tence throughout the remainder of the main program and may only be accessed within the main program. When the program stops running, all open channels are closed and destroyed.

A channel that is opened in an external procedure remains in existence throughout the remainder of that proce- dure’s invocation. However, when the procedure returns to its caller, any channels opened by the procedure (except those passed to it) will be automatically closed and destroyed.

If an external subroutine needs to access a channel opened by its caller, the channel should be passed to the sub- routine as a parameter (as in the previous example). When a previously opened channel is passed to a subroutine as a parameter, the subroutine may not contain an OPEN statement for that channel. However, if an unopened channel is passed to a subroutine as a parameter, the subroutine may open that channel. In this case, the channel used as the corresponding argument in the CALL statement will be open when the subroutine returns to its caller.

Procedures contained in a MODULE structure follow the same rules as external procedures. However, if a chan- nel number appears within a SHARE statement in that module’s header, that channel will be brought into exis- tence when the module is initialized, and it will remain in existence until the program terminates. It will be avail- able only to procedures contained within the module, and of course it must be explicitly opened before it may be used. Once opened, though, a shared channel will remain open until the program terminates (or until it is specif- ically closed, as described in the next section). (See Chapters 10 and 11 for more information on internal and exter- nal procedures and on modules.)

Closing Files
Although you may use any channel number from 1 to 999 (#0 is reserved for the default logical window), True BASIC does not allow more than twenty-five channels to be open at any one time (channel #0 is not counted). Gen- erally, you will not need this many channels, but if your program opens several files (remember that the printer and logical windows require channels too) you may find that you are running into this limit.

It is therefore a good practice to specifically close open channels when you no longer need them. You do this with the CLOSE statement. For example:
CLOSE #3
closes channel #3. If channel #3 was not open, the CLOSE statement would simply be ignored. Channels are auto- matically closed when the program terminates, and channels local to a procedure are closed when that procedure terminates.

Once a channel has been closed, you may reuse the channel number previously associated with it. Note, however, that if you attempt to open a channel that is still open, an error will result.

Erasing Files
True BASIC provides two means of erasing a file. The ERASE statement erases a file’s contents, and the
UNSAVE statement destroys the file itself.

The ERASE statement erases the entire contents of the file associated with the specified channel number. For example:
ERASE #3
! Erase whole file’s contents
Of course, the channel must be open with the ACCESS OUTIN option. The ERASE statement simply erases the file’s contents; the file continues to exist and the channel to it remains open.

The ERASE statement does not change any of the file’s attributes (as specified by the associated OPEN state- ment); however, once the file is empty some of these attributes (such as file type and record size) can be changed intentionally or incidentally. For example, you may use the SET RECSIZE statement to change the record size of an empty random or record file. And if you use a PRINT statement to send output to an empty file, that file will become a text file.

A variation of the ERASE statement lets you erase the portion of the file following the current position of the file pointer. (File pointers are described fully in the following section.) For example:
ERASE REST #3
! Erase rest of file’s contents
If you wish to remove a file completely from the storage medium, use the UNSAVE statement. For instance, the statement:
UNSAVE “FILE22.TRU”
! Delete the file itself
would completely delete the file named FILE22.TRU. If the file does not exist, an error occurs. The UNSAVE state- ment requires a file name rather than a channel number. In fact, you must close any channels associated with the file before the UNSAVE statement is executed; if a channel to the file is open, an error occurs.

SET and ASK Statements
There is a lot of information involved in the maintenance of files, and True BASIC provides convenient ways to access that file-related information. Several SET and ASK statements let you manipulate files and get informa- tion about them. This section discusses those SET and ASK statements that work with all files. More specialized SET and ASK statements are described in the sections about the individual file types.
————————————————————————————–––———————————
[ ! ] Note: Most of the ASK statements described in this section can also provide information about logical
windows and printers. For details on opening and using logical windows see Chapter 13 “Graphics.”
Details on opening and using printers are provided later in this chapter. You will also find information on the appropriate SET and ASK statements in those sections.
——————————————————————————–––—————————————

File Pointers
For each currently open file there is an associated file pointer that indicates where the next information read from or written to that file is to begin. When you first open a file, True BASIC places the file pointer at the begin- ning of the file. As your program reads items from the file, or writes information to it, True BASIC automatically moves the pointer to the end of the last item read or written.

In general, you do not need to move the file pointer yourself. However, there are occasions when you will need to move it. For instance, you are allowed to write information only to the end of text and stream files. Since the file pointer is at the beginning of a file when the file is opened, you must move the file pointer to the end of an existing text or stream file before you can add information to it.

You control the position of the pointer with the SET POINTER statement, as follows: SET #3: POINTER BEGIN
! Go to beginning of file SET #3: POINTER END ! Go to end of file
The following forms of the RESET statement are equivalent to these SET POINTER statements:
RESET #3: BEGIN
! Go to beginning of file
RESET #3: END
! Go to end of file
You can reread a file if you reset the file pointer at the beginning, or append information to the file if you move the file pointer to the end.

For each attribute that can be set with a SET statement, there is a corresponding ASK statement (the reverse is not always the case). Thus, you may easily find out the current position of a file pointer with the ASK POINTER statement, as follows:
ASK #3: POINTER ptr$
! Where is the pointer?
In this statement, the variable ptr$ will be assigned one of the values “BEGIN”, “MIDDLE”, or “END”
depending on the current position of the file pointer within the file associated with channel #3.

You can test whether you have reached the end of a file using the logical expressions END or MORE. For example, this program fragment ensures that the file pointer is at the beginning of a text file and then prints the file’s contents:
Files for Data Input and Output

115

RESET #3: BEGIN DO
LINE INPUT #3: line$ PRINT line$
LOOP UNTIL END #3
! Is TRUE if at end of #3
The following program fragment is equivalent except that it prevents the error that would occur if the file is empty:
RESET #3: BEGIN
DO WHILE MORE #3
! Is TRUE if not at end of #3
LINE INPUT #3: line$ PRINT line$
LOOP
With random-access files such as random, record, and byte files, you can also move the file pointer to individual records and ask for the current record number; see the descriptions for those files.

Names and Directories
You may use the ASK NAME statement to find out the name of an open file:
ASK #5: NAME filename$
! Get name of file #5

The ASK NAME statement will report the full path name of the file associated with that channel number.
—————————————————–––——————————————————————
[ ! ] Note: The ASK NAME and SET NAME statements without channel numbers are provided for com-
patibility with earlier versions of True BASIC. In earlier versions, ASK NAME without a channel num-
ber reported the name of the current program. This version of True BASIC assigns the null string if
ASK NAME is used without a channel number; SET NAME is ignored.
————————————————–––——————————————————————— As noted earlier, you may use a path name in the OPEN statement to access files that are not stored in the cur- rent directory. If you will be opening several files in the same directory, you may prefer to use a SET DIREC- TORY statement to change the current directory, thus avoiding the need for path names in the OPEN state- ments. For example, the statement:
SET DIRECTORY dir$
! Change directory
would set the current directory to the directory specified by the value of dir$. The value of dir$ must be a legal directory name, and it may include a disk name. See “The True BASIC Environment” chapter in the introductory section for information on specifying directories within various operating environments.
The ASK DIRECTORY statement lets you find out the name of the current directory. Thus, if you change direc- tories in your program and wish to be able to return to the starting directory before the program ends, you could store the name of your starting directory before changing to a new directory as follows:
ASK DIRECTORY old_dir$
! Starting directory
SET DIRECTORY dir$
! Change to new directory
Then, later switch back to the original directory with the following statement:
SET DIRECTORY old_dir$
! Change to starting directory
When the program terminates, you are returned to the directory you were in when you ran the program. The final section in this chapter describes ExecLib library routines that accomplish the same thing as the ASK DIREC- TORY and SET DIRECTORY statements.

File Characteristics
Other ASK statements provide information about the file itself or how the file was opened. The following state- ments may be used regardless of the file type; see the specific file types for additional statements.

The ASK ORG statement finds out the type of file that is currently associated with an open channel. For instance, the statement:

ASK #3: ORG org$
assigns a value of “TEXT”, “STREAM”, “RANDOM”, “RECORD”, or “BYTE” to the variable org$. If the channel number refers to a printer, the value “TEXT” is assigned to org$; and if the channel number refers to a logical window, “WINDOW” is assigned to org$.

The ASK RECTYPE statement finds out the nature of a file’s records. The statement:
ASK #3: RECTYPE rectype$
assigns a value of “DISPLAY” or “INTERNAL” to the variable rectype$. If the channel number refers to a text file, a printer, or a logical window, the value “DISPLAY” is assigned to rectype$. For all other types of files, “INTERNAL” is assigned to rectype$.

The ASK ACCESS statement finds out the access available for the file associated with the specified channel num- ber. For instance, the statement:
ASK #3: ACCESS acc$
assigns to acc$ a value of “INPUT”, “OUTPUT”, “OUTIN”, “NETIN”, “NETOUT”, or “NETWORK”, as deter- mined by the ACCESS option used when channel #3 was opened. If channel #3 refers to the printer, a value of “OUTPUT” is assigned to acc$. If channel #3 refers to a logical window, “OUTIN” is assigned.

The ASK FILESIZE statement lets you find out the size of a file. For example:
ASK #3: FILESIZE fs
! Length in bytes (in records for random & record);
! 0 for printer or logical window
If the file associated with channel #3 is a text, stream, or byte file, then the number of bytes in the file is assigned to fs. If the file is a record or random file, then the number of records in the file is assigned to fs. If the channel refers to a printer or a logical window, a size of 0 is returned.

The ExecLib routines Exec_ReadDir and Exec_ClimbDir (described in the last section of this chapter) provide additional information about files and directories including size, date and time last modified, and access permissions.

Text Files
A text file consists of lines that you can create on the keyboard and display on the screen using the True BASIC Editor (or any other application that can create and read “text-only” files). You can also create a text file entirely from within your program. True BASIC puts information into text files in the same way it displays information on the screen or printer, and it gets information from them just as it gets input from the keyboard. Thus, you use the same PRINT and INPUT statements — along with an appropriate channel number — with text files.

Text files are easy to understand and use. In fact, the PRINT and INPUT statements work just as they normally do when used with the screen and the keyboard — all the same rules apply. Because you can create and view text files with any screen editor, you can see the file structure and understand how it interacts with your programs. Text files often provide input data to a program or store output for later display or printing.

Text files, however, are not as efficient as the other types of files for large amounts of data. It is often hard to out- put information (such as strings or arrays) to a text file in a format that programs can easily read. Also, you may lose some numeric precision when you store numeric information in text files.
————————————————————–––———————————————————
[ ! ] Note: To understand the loss of numeric precision within text files (and the major difference between text files and internal files), let’s take a brief look at what happens when a program takes input from the keyboard and displays it on the screen. At the keyboard, you type characters that True BASIC interprets based on a standard character set. If you input a string value, True BASIC stores the actual characters you type (less leading and trailing spaces) in internal memory; each character occupies one byte of memory. When you use a PRINT statement to display a string value, you get exactly what is stored in memory.

If you input a numeric value, however, True BASIC converts the characters you type into the number they represent and stores that value in an internal format. In that internal format, numeric values have a precision of at least 14 significant digits, and each value occupies eight bytes of memory. True BASIC per- forms all calculations using the full precision of the internal numeric format.
When a PRINT statement displays a numeric value, however, you may not see the value to its full preci- sion. Unless you specify otherwise with a PRINT USING statement, the PRINT statement displays characters representing the numeric value according to the rules described in Chapter 3 “Output State- ments.” For example, the program:
LET x = 296445886
! Population
LET y = 1.37
! Growth rate PRINT x * y
! New population END
displays the value:
4.0613086e+8
even though the internal value is calculated to be 406130863.82.
If you use a PRINT statement to store this value in a text file, the same series of characters that rep- resent the value on the screen would be used to represent it in the file. A subsequent INPUT state- ment would retrieve the value with its reduced precision. While this may not be a problem for many applications, you should be aware of it.
———————————————————–––————————————————————
Let’s look now at a simple example that gets information from one text file and prints some of that information to another file. The INPUT and PRINT statements work just as they normally do except that you specify a channel number to indicate the file to be used:
OPEN #1: NAME “WAGES”, ORG TEXT, ACCESS INPUT OPEN #2: NAME “NAMES”, ORG TEXT, CREATE NEWOLD RESET #2: END

DO WHILE MORE #1
! While there is more to read
INPUT #1: name$, age, salary
PRINT #2: name$, “Age:”; age
LOOP

END
Each time the INPUT statement in this example is executed, it reads a line from the first file, treating it as if it had been typed at the keyboard. The line must have just the right number of items, of the right type (i.e., using numbers for numeric variables), separated by commas. If the value to be assigned to the name$ variable contains a comma, the string must be enclosed in double quotes. For example, the following line in the file would be legal:
“Williams, Pat”, 34, 28500
while this one would cause an error:
Williams, Pat, 34, 28500
because True BASIC would interpret Williams as the value of name$, and attempt to assign the string value
Pat to the numeric variable age.

Likewise, if a line in the file contains too few or too many items or the types do not match, an error occurs, since there is no way of “re-asking” the file for input.

Lines being input from a file may end with a comma to indicate that there is more input on the next line. Along with the INPUT statement, you may use the LINE INPUT, MAT INPUT, and MAT LINE INPUT statements with text files. However, the various forms of the INPUT PROMPT statement are not allowed, since a file cannot be prompted.

If you attempt to use the INPUT statement with a file opened with the ACCESS OUTPUT option, an error occurs. You’ll also get an error if the file pointer is at the end of the file (i.e., if there is no more information to input).

Remember that you can use the SET POINTER or RESET statements to move the pointer to the beginning of the file, and you can use the MORE or END logical clauses to test for more data in the file (see earlier section).

The PRINT statement in the example above:
PRINT #2: name$, “Age:”; age
also follows all the conventions for a PRINT statement used to display values on the screen, including commas and semicolons. The file has a margin and a zonewidth, whose default values are 80 and 16, respectively, as they are for logical windows on the screen. You may change these settings with the SET MARGIN and SET ZONEWIDTH statements as follows:

SET #3: MARGIN 70
SET #3: ZONEWIDTH 10
Similarly, your program can find out the current margin and zonewidth of a file with the ASK MARGIN and ASK ZONEWIDTH statements:
ASK #2: MARGIN m
ASK #2: ZONEWIDTH z
Since there is no cursor in a file, the SET CURSOR statement does not make any sense when applied to a file. Similarly the two-argument version of the TAB function is forbidden with text files. You may, however, use the TAB function with a single argument:
PRINT #2: name$; Tab(45); “Age:”; age
You may also use the MAT PRINT or PRINT USING statements to print to a text file. Here’s an example of the
PRINT USING statement used with a text file:
LET form$ = “###########################> Age: ##” PRINT #2, USING form$: name$, age

If you attempt to use the PRINT statement with a file that has been opened with the ACCESS INPUT option, an error occurs. You’ll also get an error if you attempt to overwrite the existing contents of a text file. To avoid attempts to overwrite, erase the contents of a file with the ERASE statement or reset the pointer to the end of the file with a SET POINTER or RESET statement before printing to it.

As shown in the above example, it is easy to copy all or part of one file to another. Here’s another example that changes all letters in a file to lowercase:
DIM line$(1000)
OPEN #3: NAME “Program5.Tru”
LET i = 0
DO WHILE MORE #3
! Read lines into array
LET i = i + 1
LINE INPUT #3: line$(i) LOOP
ERASE #3
! Erase the file
FOR j = 1 to i
! Rewrite in lowercase
PRINT #3: Lcase$(line$(j)) NEXT j
END
The program reads the file into an array, erases the file, and then writes lowercase versions of the lines back into the file.

A word of caution about using the MAT PRINT and MAT INPUT statements with text files: while both work with text files, the MAT PRINT statement does not write information in a format that will work with the MAT INPUT statement. The MAT INPUT statement expects items of a row to be separated by commas, but the MAT PRINT statement separates the items of a row by spaces. There are two ways to solve this problem:
Files for Data Input and Output

119

(1) Create the file’s contents by printing individual elements, putting a comma after each item except the last:
...
FOR i = 1 to Ubound(array) - 1
PRINT #7: array(i); “, “; NEXT i
PRINT array(Ubound(array))
...
(2) Use the LINE INPUT statement to input an entire line from the file and then “parse” the line into its com- ponent items using the ExplodeN subroutine provided in the StrLib library.
LIBRARY “C:\TBSilver\TBLIBS\STRLIB.TRC”
! Use appropriate path name
...
LINE INPUT #4: line$
CALL ExplodeN(line$, array(),” “)
...
You should also be cautious when printing strings to text files for later input. Remember that the INPUT state- ment requires double quotes around strings containing commas or leading or trailing spaces. To overcome this problem you could print such strings with enclosing quotes or, better yet, print just one string value per line and then use the LINE INPUT statement to read the entire line. The latter solution is the best if your strings contain double-quote marks, as you would have to repeat the double quotes within the string for the INPUT statement to read the string correctly!

Internal Files — Stream, Random, Record, & Byte
The important differences between text files and the other types of data files are the statements you use to get data to and from the files and the way in which the files store numeric values.

Within text files, both numeric and string values are stored as series of characters. Numeric values are converted to strings of digits that represent the value (with possible loss of full precision). Any application that can read text can print or display such files. Because the format of text files is the same as for keyboard input or displays to the screen, text files use the normal INPUT and PRINT statements with the addition of channel numbers.

The remaining file types are all internal files — numeric and string values are stored in the same internal format used by the computer’s memory when it runs your programs. String values are stored internally as characters just as they are displayed, with one byte per character. Numeric values, however, are stored in the standard IEEE eight-byte format that cannot be displayed. Because of the storage format, internal files require READ and WRITE statements to input and output data. While internal files cannot usually be displayed directly on the screen or printer, they do have several advantages:

• The numeric values retrieved from an internal file are read with exactly the same precision as the values written to the file. With a text file, numeric values may lose precision when the PRINT statement converts them from the computer’s internal format to a sequence of characters; any greater precision is lost and can- not be retrieved when that sequence of characters is input from the file.

• Reading and writing operations are faster with internal files, because there is no need to convert numeric values between internal and display formats.

• True BASIC internal files may be used with programs on any computer type. The internal format is the same no matter where you run your programs. Also, the ability to read a file as a byte file lets you read any file created by any application on any computer. Text files, however, must often be translated when they are moved between operating systems because of the variations in how operating systems view end-of-line characters within text files.

• Three types of internal files — random, record, and byte — permit the more efficient random access of records within the files. With random access you can jump directly to any part of the file, rather than having to work through the file from start to finish. Text and stream files permit only sequential access — the items in the file must be retrieved in exactly the same order in which they were stored.

Internal files come in four types: stream, random, record, and byte files, all of which are explained below. Ran- dom and record files are organized by records. A record is a storage location of fixed-length within a file. All the records within a file are numbered so that you can move easily to any record in the file with a SET RECORD state- ment. The exact structures of random and record files are explained below.

As noted above, you use WRITE and READ statements with internal files. The exact usage of these statements varies depending on the type of file, as described below.

The OPEN, CLOSE, ERASE, and UNSAVE statements work for internal files just as they do for text files. Remember, however, that the default organization for a newly created file is text, so you must specify the type of file when you are creating a new internal file. The SET and ASK statements have several additional forms that are described with the different file types below.

Stream Files
A stream file is simply a sequence of values. These values must be read back in the same order in which they were written to the file. For example:
OPEN #1: NAME “VALUES.STR”, CREATE NEW, ORG STREAM WRITE #1: Pi, Exp(1), “This is a string.”, 3.14
...
SET #1: POINTER BEGIN READ #1: a, b, c$
READ #1: d
! At this point, a is exactly equal to PI
! b is exactly equal to EXP(1)
! c$ is the string “This is a string.”
! d is exactly equal to 3.14
Notice that the WRITE and READ statements need not have the same number of variables — there is no concept of a line of data as in text files or a record as in random and record files. The one requirement is that the type (numeric or string) of a variable in the READ statement must match the type of the next value in the file. If the type is wrong, an error occurs.

Although it is up to the programmer to keep track of the type and purpose of the values in a stream file, you can
“peek” at the next value’s type with an ASK DATUM statement. For example:
ASK #1: DATUM type$ SELECT CASE type$ CASE “NUMERIC”
READ #1: n
CASE “STRING” READ #1: s$
CASE else
! type$ = “NONE” if at the end of the file
! type$ = “UNKNOWN” if can’t tell
END SELECT

Random Files
Random files are composed of records. All the records within a single file have the same maximum length which is called the record size of that file.

Each record in a random file may contain any number of string and/or numeric values, provided that the cumula- tive length of the items (and their associated “bookkeeping” as explained below) does not exceed the file’s record size. In fact, different records within the same file may contain different numbers and types of items.

Any record whose actual length is less than the record size of the file will be automatically “padded” to the proper record size before being written to the file. This padding will be ignored when the values are subsequently retrieved from the file. Thus, you need not worry about padding records yourself.

Although True BASIC will automatically move the file pointer to the next record each time a record is read, allow- ing you to easily process a random file from beginning to end, you can also move the file pointer to any existing record within the file arbitrarily. The record to which the file pointer currently points may be retrieved and/or overwritten as necessary.

Before you can write records to a new or empty random file, you must first set the file’s record size. You may do this using a RECSIZE option in the OPEN statement, as in:

OPEN #1: NAME “NEWDATA.RDM”, ORG RANDOM, RECSIZE 50, CREATE NEW
or by using a SET RECSIZE statement after the file has been opened, as in: OPEN #1: NAME “NEWDATA.RDM”, ORG RANDOM, CREATE NEW SET #1: RECSIZE 50
Note, however, that you may set or change the record size only for a new or empty file — if the file contains any records you must erase it (with the ERASE statement) before you can change the record size.

If a file already exists and contains one or more records, it already has a record size which you cannot change with- out first erasing the file. You may use the ASK RECSIZE statement to find out the record size of a file as follows:
OPEN #1: NAME “DATA”, ORG RANDOM, CREATE OLD ASK #1: RECSIZE rsize
Here, the record size of the file named DATA would be assigned to rsize.

If you attempt to write more bytes to a random file record than its defined record size, an error results. The record size must be large enough to hold both the data that will be stored in each record and some additional “bookkeep- ing” information. This bookkeeping information keeps track of the kinds of information in each record (remember that random files allow an arbitrary number of values of arbitrary types within each record) and indicates the end of the record. Although you need not worry about this information when using the file, it does require storage space, and you must account for it when you set the record size for a new random file (or if you need to figure out how much you can write to new records in an existing random file).

A string item stored in a random file record will occupy one byte for each character in the string plus four bytes of bookkeeping information. On the other hand, a numeric value stored in a random file record will always occupy exactly nine bytes — eight bytes for the internal representation of the number and one byte for bookkeeping. In addition, you must always allow one byte in the record size for the end-of-record marker.

As an example, consider a situation in which you plan on storing two strings and three numbers in each record. First, you need to know the maximum length of the strings that you will store. Let’s assume that the first string will never be longer than 30 characters and the second string will never exceed 14 characters. Thus, you need to reserve 30 + 4 bytes for the first string and its bookkeeping information and 14 + 4 bytes for the second string and its bookkeeping information. Each of the three numeric values will occupy 8 + 1 bytes with its bookkeeping infor- mation. And don’t forget to reserve 1 byte for the end-of-record marker. By adding all of these requirements together, you know the proper record size for this random file is 34 + 18 + 9 + 9 + 9 + 1 = 90.

If the records in the random file will contain varying numbers and types of items, calculate the length based on the longest record you will need. If you attempt to write more bytes to a random file record than its defined record size, an error results.
———————————————–––————————————————————————
[ ! ] Note: True BASIC does not know how you arrived at a random file’s record size; it simply checks to be sure total size of the record does not exceed the established record size. You might exceed a record size because you attempted to write more items than you had planned on, or because a string in the record is longer than you planned. True BASIC won’t know the difference; it will simply report that the record size was exceeded. You may want to use the DECLARE STRING statement to define a maximum length for string variables used in random file records. This lets True BASIC provide more specific diagnostics should a problem arise.
——————————————————–––—————————————————————

Each READ and WRITE statement reads or writes one complete record in a random file. Because individual records may contain different numbers and types of values, the pattern of the READ statement must mirror the pattern of the WRITE statement that produced the record; otherwise, an error will occur. In the following exam- ple, each record contains three values: a string value, a numeric value, and another string value:
! A new RANDOM file
OPEN #1: NAME “STUFF”, CREATE NEW, ORG RANDOM, RECSIZE 100
...
WRITE #1: name$, age, occupation$
Later on, perhaps in a different program, you can retrieve that information, as follows:
! File already exists
OPEN #1: NAME “STUFF”, ORG RANDOM
...
! True BASIC figures out the RECSIZE by looking at the file.
! CREATE option not needed, or use CREATE old.
...
! The READ statement must mirror the earlier WRITE
READ #1: person$, a, occ$
The READ statement typically reads all the values in the record, and the variable types must match the value types in the record. However, if the record contains many items and you want only the first few, you may use a SKIP REST clause in the READ statement as follows:
READ #1: person$, a, SKIP REST
The SKIP REST clause instructs True BASIC to ignore the remaining values in the record.

Remember that the records within a random file need not have the same shape — they may have different numbers and types of values of varying lengths (as long as they don’t exceed the record size). For example, a random file that contains a student’s grade record might contain different information in the first few records:
OPEN #5: NAME “SMYTHE”, ORG RANDOM, ACCESS INPUT
READ #5: last$, first$, middle$, class
! First record
READ #5: street_address$
! Second record
READ #5: city$, state$, zip$
! Third record

PRINT “Grade Report for “; first$ & last$; “. Class of”; class
DO WHILE MORE #5
READ #5: course$, grade, credits
! Remaining records
PRINT course$; tab(20); grade, credits; “credits”
LOOP
...
Random files are so called because they permit random access. That is, you can access any particular record regardless of the order in which records were created. The records are automatically numbered starting at 1. The file pointer normally moves to the next record after a record has been read or written — remember that each READ or WRITE statement reads or writes an entire record in a random file. But you may also jump around to arbitrary records within a file using the SET POINTER and SET RECORD statements:
SET #3: POINTER SAME
! Go back to the record just read or written
SET #3: POINTER NEXT
! Skip the current record
SET #3: RECORD r
! Go to record number r
You may also use the keyword RESET as follows:
RESET #3: SAME
! Go back to the record just read or written
RESET #3: NEXT
! Skip the current record
RESET #3: RECORD r
! Go to record number r
Clearly, the last option is the most powerful one. You may find the current file pointer position, or the number of the current record, with the ASK RECORD statement as follows:
ASK #3: RECORD r

As an example, consider a simple computer-based dictionary. Suppose that one random file contains a list of words and another random file contains the corresponding definitions in the same order. If you open these two files as #1 and #2, respectively, you could look up words as follows:
DO
INPUT PROMPT “Word: “: w$
CALL Find (#1, w$, n)
! Word in record n
IF n = 0 then
PRINT “Word not found” ELSE
SET #2: RECORD n
! Find definition
READ #2: def$ PRINT def$
END IF LOOP
The program-defined subroutine Find searches file #1 for the word and returns its record number (or 0 if it finds no word).
SUB Find (#9, word$, rec)
RESET #9: 1
! Start at beginning of file
ASK #9: FILESIZE last_rec
! How many records? FOR r = 1 to last_rec
READ #9: next$
! Examine each record
IF next$ = word$ then EXIT FOR NEXT r
IF r > last_rec then LET rec = 0 else LET rec = r
END SUM
If the word is found, the program jumps to the same record number in file #2 and reads the definition. This is not possible with text files.

Changing an existing record in a random file is just as easy. Simply jump to the record and use a WRITE state- ment. You can add to the end of the file by first using:
SET #3: POINTER END
You may also use the MAT READ and MAT WRITE statements to read or write an entire array from or to a ran- dom file. With random files, the MAT WRITE statement puts all the array elements in the same record, provided the record is long enough. You may then recover the elements with a MAT READ statement — or with a READ statement that includes a variable for each element.

Record Files
Record files are like random files, except that you can place only one value — numeric or string — in a record. Although you will often find that a random file is better suited for a particular task, record files may be used if you are storing a single item per record.

When used with a record file, a WRITE statement stores each value in a separate record. And a MAT WRITE state- ment will use as many records as there are elements in the array. For example, the WRITE statement in:
! A new RECORD file
OPEN #2: NAME “STUFF1”, CREATE NEW, ORG RECORD, RECSIZE 50
...
WRITE #2: name$, age, occupation$
will use three records to store the three quantities. Later, you may retrieve these values with:
READ #2: person$, a, occ$
or with:
READ #2: person$ READ #2: a
READ #2: occ$

The READ statement need not mirror the WRITE statement, but the variable type — numeric or string — must be correct.
In contrast to a random file, calculating the proper record size for a record file is easy. Each record in a record file contains four bytes of bookkeeping information. However, since the size of this information is the same for all records, you do not need to account for it in the record size (as you would for a random file). Thus, the record size of a record file need only reflect the length of a number (which is 8 bytes) or the length of the longest string value you expect to store in a single record. Remember that you may freely mix numeric and string values in a single record file, so the record size must reflect the length of the longest value you plan to store in a record.
————————————————–––———————————————————————
[ ! ] Note: The bytes actually included in the record size are different for random and record files. For random files, the record size must include the extra, bookkeeping bytes along with the data bytes. For record files, however, the record size need include only the length of the data item to be stored. The bookkeeping bytes are there, but you don’t need to account for them.
—————————————————–––——————————————————————
In all other respects, record files are like random files. They permit random access, and you may use the same SET and ASK statements to move around and find out information about them.

Byte Files
A byte file is not a special kind of file but rather a way of looking at a file. When a file is viewed as a byte file, it is considered simply as a sequence of bytes with no special format. That is, True BASIC does not make any assump- tions about a byte file, and it will not perform any of the “housekeeping” tasks that it performs for other files (other than advancing the file pointer).

You may view any True BASIC file as a byte file by specifying the ORG BYTE option in the OPEN statement used to open that file. Indeed, you may view any file as a byte file, including compiled True BASIC programs, files cre- ated by other applications, or files created on another type of computer or under a different operating system.
As with other internal files, you use READ and WRITE statements to access byte files. The number of bytes read by a single READ statement depends on the type of variable being read.

A READ statement used to access a byte file may have only one variable, which is normally a string variable, since the contents of the file may be any sequence of bytes. Although byte files do not recognize records, True BASIC uses the current record size to decide how many bytes to read to a string variable.

You may set the record size using a RECSIZE clause in the OPEN statement, as you would for random or record files, or you may use a SET RECSIZE statement. Similarly, you may use an ASK RECSIZE statement to find the current record size of a byte file, as you would for random or record files. Because byte files are reading an arbi- trary number of bytes, not actual records, you may use the SET RECSIZE statement to change the record size of a byte file as many times as necessary.
Alternatively, you may specify the number of bytes to be read to a specific string variable by including a BYTES
clause in the READ statement. For example:
READ #7, BYTES 32: y$
would read the next 32 bytes in the file associated with channel #7 into the string variable y$.

This method of overruling the file’s record size within an individual READ statement is commonly used with byte files, since you may need to read strings of different lengths from a single file. Often, you might want to read an entire file to a single string, as follows:
ASK #7: FILESIZE fs
READ #7, BYTES fs: y$
If you use a READ statement with a numeric variable, the next eight bytes in the file will be read as a numeric value stored in the IEEE eight-byte format. When a numeric value is read, the file’s record size is ignored. Like- wise, the BYTES clause is not allowed in a READ statement that specifies a numeric variable.

If the file pointer is near the end of the file and the number of bytes remaining is less than the current record size, a READ statement simply reads all the remaining bytes. If the pointer is at the end of the file, however, a READ statement causes an error.

The WRITE statement may also be used with string or numeric values. With a string value, it writes as many bytes as there are characters in the string. Numeric values are written to byte files in the IEEE eight-byte format.
———————————————————–––————————————————————
[ ! ] Note: The IEEE eight-byte representation used to store numeric values in a byte, random, or record file is identical to the IEEE eight-byte representation produced by the NUM$ built-in function (see
Chapter 18). This means that numbers may be read from a byte file as eight-byte string values and con- verted to numeric values using the NUM function. This may be a useful alternative to reading those values directly into numeric variables.
——————————————————————–––—————————————————
Within a byte file, each byte is numbered as if it were a separate record (regardless of the current “record size”) beginning with 1 at the first byte. Thus, the SET and ASK statements that require or return a record number actually refer to a byte number. For example, the statement:
SET #3: RECORD 120
when applied to a byte file, moves the file pointer to byte number 120. A program may read any consecutive sequence of bytes, and it may overwrite any such portion of the file. You may also use the WRITE statement to add to the end of the file, provided that the file pointer is at the end of the file.

The following examples illustrate some instances when byte files are helpful. The first is a routine that will copy any file, no matter what its format or content:
SUB FileCopy(from$, to$)
! Copy any file OPEN #3: NAME from$, ORG BYTE
! Open two files OPEN #4: NAME to$, CREATE NEWOLD, ORG BYTE
ERASE #4

SET #3: RECSIZE 1024
! Copy in 1K pieces
DO WHILE MORE #3
READ #3: x$
WRITE #4: x$
LOOP
END SUB
This procedure uses 1024 bytes (1K) as a convenient unit to read and write at one time. (A record size that is a power of two may allow your program to run faster.) If the file length is not a multiple of this, the last READ will result in a shorter string x$, but it will cause no error. The new file will have precisely the same content as the old one.

You may also use byte files to search a file for non-printing characters. Since True BASIC reads all bytes, includ- ing those such as a line feed, each byte can be identified by its character code. (See the ORD and CHR$ functions in Chapter 8 “Built-in Functions.”) You could therefore extract the text from any type of file by examining each byte and keeping only the printing characters, as follows:
SUB Text_extract (from$, to$)
OPEN #3: NAME from$, ORG BYTE
! Open two files
OPEN #4: NAME to$, CREATE NEWOLD, ORG TEXT
ERASE #4

SET #3: RECSIZE 1
! One byte at a time
DO WHILE MORE #3
READ #3: x$
IF 32<= Ord(x$) and Ord (x$) <=127 then
! Standard printing characters
PRINT #4: x$;
END IF

LOOP END SUB
Note that this example is presented in the simplest form possible. There is plenty of room for improvement. For instance, you might read larger sequences of bytes and build up an output string in memory, sending it to the file only when it reaches a certain length. Each file access takes time, and the fewer times your program accesses a file, the more quickly it will run.

As an illustration of how byte files can store any type of information, consider how you might store a screen image, such as a complex diagram. The BOX KEEP statement stores the image displayed within a specified area on the screen into a string variable, which you can later display with the BOX SHOW statement (as described in Chap- ter 13 “Graphics”). If you need to save these strings for later display, you can store them in byte files, as in the fol- lowing program fragment:
SET WINDOW 0,1,0,1
BOX KEEP 0,1,0,1 in keep$
OPEN #5: NAME “Image”, CREATE NEW, ORG BYTE WRITE #5: keep$
Another program fragment may then retrieve and display the image as follows:
OPEN #5: NAME “Image”, ORG BYTE
ASK #5: FILESIZE fs
! Number of bytes in file? READ #5, BYTES fs: keep$ ! Read entire file to string SET WINDOW 0,1,0,1
BOX SHOW keep$ at 0,0
Byte files in combination with the built-in PACKB subroutine and the built-in UNPACKB function provide an efficient means of packing information to conserve storage space. As you have seen, numeric values stored in internal files always occupy eight bytes — whether the value is 0 or 3.7836126523e287. Often, however, your pro- grams need to store only integers within a specific range. Eight bytes is generally much more storage than is nec- essary for integers, so storing many integers into an internal file can use much more disk space than would other- wise be required.

One way to eliminate this waste is to “pack” the integer values into string values, using the PACKB subroutine, before storing them to the file. The PACKB subroutine allows you to represent an integer value as a specific series of bits within a string variable. For instance, the following program fragment writes a list of integers into a byte file. It assumes that each integer fits into 16 bits (integers from 0 to 65,535) and there are n of them in the array list:
LET x$ = “” LET j = 1

FOR i = 1 to n
CALL Packb(x$,j,16,list(i)) LET j = j+16
NEXT i

WRITE #1: x$
Each integer is packed into x$ using the PACKB subroutine. Once all the numbers have been packed into x$, x$
is written to the byte file.
Rather than maintaining the variable j as the starting bit position within the string x$, you may find it simpler to use the following trick:
CALL Packb(x$,Maxnum,16,list(i))

If the starting bit position provided to the PACKB subroutine is beyond the end of the string value, the resulting series of bits will begin next to the last bit in the current string value. In other words, by specifying a ridiculously large value as the starting bit position, you pack the integer value in list(i) into the 16 bits immediately follow- ing the end of the current value of x$. This eliminates the need for the variable j to keep track of the bit position.

You could recover the resulting list from the byte file using the UNPACKB function as follows:
ASK #1: FILESIZE fs READ #1, BYTES fs: x$ LET j = 1
FOR i = 1 to Len(x$)/2
LET list(i) = Unpackb(x$,j,16) LET j = j+16
NEXT i
The first two lines are the standard way of reading an entire byte file into the string. The first statement discov- ers how many bytes are in the file, and the second reads them all with a single READ statement.

You would save storage and gain speed by packing each number into two bytes (16 bits). Such packing is particu- larly important for storing large amounts of information. For example, if you have one million “yes/no” replies, they can be packed into one million bits, or 125,000 bytes. A byte file is the only reasonable way of storing such information.

Sending Textual Output to a Printer
You may use a printer as you would a text file opened with the ACCESS OUTPUT option. That is, you may send output to a printer by opening a channel to it and using a PRINT statement with that channel number, but you may not use any form of INPUT statement with that channel for obvious reasons.

You open a channel to the printer with a special form of the OPEN statement, without any options, as follows:

OPEN #7: PRINTER
After the above statement has been executed, channel #7 will be associated with the printer. Of course, the printer must be turned on, placed on-line, and properly connected to the computer so that True BASIC is able to use it. If a printer is not available, True BASIC will generally generate an error.

If the current operating environment has access to more than one printer, True BASIC opens the channel to the printer that the operating environment identifies as the default choice. Refer to the documentation for your oper- ating environment for more information.

Once you have associated a printer with a channel number, you may send textual output to it with the PRINT
statement as follows:
OPEN #1: PRINTER FOR i = 1 to 100
PRINT #1: i
! Prints to temporary spool file
NEXT i
CLOSE #1
! Spool file sent to printer
END
The OPEN statement associates the specified channel with a special file called a spool file. The spool file is a temporary file that True BASIC creates automatically on the disk. When you send output to the printer channel, that output is stored in the spool file, which continues to accumulate output until the printer channel is closed. Once the printer channel has been closed, True BASIC sends the contents of the complete spool file to the printer and deletes the spool file from the disk. (The channel is closed by a CLOSE statement or when the program ends.)

A temporary spool file is used because of the prevalence of page-oriented and networked printers. Page-oriented printers, such as most laser and ink-jet printers, print their output one page at a time, rather than one line at a time, like most dot-matrix printers. Page-oriented printers often do not behave gracefully when they are sent sin- gle lines of text at odd intervals. Networked printers often handle the demands of several users at once, and as such they do not cooperate when one program attempts to claim sole ownership for a significant time. By storing your output temporarily in a spool file, True BASIC can send the entire output as a single document, avoiding problems with page-oriented and networked printers.

As with a text file, True BASIC opens a printer with a default margin of 80 characters and a default zonewidth of 16 characters. As with a text file, you may access these settings with the ASK MARGIN, SET MARGIN, ASK ZONEWIDTH, and SET ZONEWIDTH statements. Beware, however, that most printers have a physical limitation on the width of the lines that they can print, and setting the margin larger than this value may not have any effect.

Basic Directory Operations
True BASIC statements such as OPEN, READ, PRINT, SET, ASK, and so on open and access files. For dealing with directories, True BASIC provides the built-in subroutine SYSTEM. This subroutine lets a program find out the current directory, change it, create and remove directories, rename files, and get information on the contents of a directory and possibly all its subdirectories.

The SYSTEM subroutine, however, is complex and not easy to use. Thus True BASIC also includes the ExecLib library of subroutines that provide easier methods of performing directory operations. This section describes the use of the ExecLib library of subroutines; for information on the built-in SYSTEM subroutine, see Chapter 18.

To use these convenience routines, you must include a library statement in your program, such as:
LIBRARY “C:\TBSilver\TBLIBS\ExecLib.Trc” !Use appropriate path name
The ExecLib library contains six subroutines that let your programs find out the current directory, change to a new directory, create a new directory, and find out about the contents of a directory including all its subdirectories if desired. A seventh subroutine lets you to rename a file. (Use the True BASIC statement UNSAVE to delete a file.)

Identifying and Changing Directories
The Exec_AskDir and Exec_ChDir subroutines provide the same functionality as the ASK DIRECTORY and
SET DIRECTORY statements. For example:
CALL Exec_AskDir (dir$)
returns the path name of the current directory in the string variable dir$. Similarly, the Exec_ChDir subroutine:
CALL Exec_ChDir (newdir$)
will change the current directory to the one specified by the contents of newdir$. (This should be equivalent to using the CD command on most systems.) If the argument does not specify a valid directory, an error occurs.

As with the SET DIRECTORY statement, when the program terminates the current directory returns to what it had been before the run began.

Creating and Deleting Directories
The Exec_MkDir subroutine lets your programs create new directories. For example:
CALL Exec_MkDir (newdir$)
creates a new directory in a location determined by the pathname conventions. If the new directory name contains a path name that starts at root level, the new directory is placed with respect to that root directory. If the new directory path name does not start at root level, the new directory will be placed in the current directory. If the argument does not specify a valid directory name, an error occurs.

The Exec_RmDir subroutine removes the named directory:
CALL Exec_RmDir (dir$)
Some systems may require that the directory be empty before allowing it to be removed. You could use the Exec_ReadDir routine described below along with the UNSAVE statement to get the names for all the files in a given directory and delete them.

If the argument does not specify a valid directory, an error occurs.

Finding Out About Files in a Directory
The Exec_ReadDir and Exec_ClimbDir subroutines provide list of names and statistics about the files saved within a directory. Exec_ReadDir provides information on the files directly saved in the current directory, while Exec_ClimbDir provides information on the files in the designated directory along with those in subdirectories within that directory.

Calls to the routines take the following formats:
CALL Exec_ReadDir (template$, name$(), size(), dlm$(), tlm$(), type$(), vname$) CALL Exec_ClimbDir (dir$, template$, name$(), size(), dlm$(), tlm$(), type$())
For the template$ argument, you may pass a string to select a subset of files (such as “*.TRU”). Specify an empty string if you do not wish to limit the search. For Exec_ClimbDir, you must specify the topmost directory to search in the dir$ argument; Exec_ReadDir searches the current directory.

Most of the information is returned in a series of one-dimensional arrays as follows:

name$() the names of the files (and possibly directories) in the current or specified directory (for Exec_ReadDir the names are simple names of files in the current directory; for Exec_ClimbDir the names are returned as full path names)

size() the sizes of the files in bytes

dlm$() the date last modified, in the True BASIC DATE$ function format “YYYYMMDD” where “YYYY” is the four-digit year number, “MM” is the two-digit month number, and “DD” is the two-digit day number

tlm$() the time last modified, in the True BASIC TIME$ function format “HH:MM:SS” where “HH” is the two-digit 24-hour number, “MM” is the two-digit minute number, and “SS” is the two-digit second number

type$() the type or access permissions given as a four-character string of the form:
“drwx”
where the first character is “d” if the entity is a directory and “-” if it is a file; the second character is “r” if reading the file or directory is permitted and “-” otherwise; the third character is “w” if writing or appending to the file or directory is permitted and “-” oth- erwise; and the fourth character is “x” if the file is directly executable and “-” if not (directories are not executable)

Renaming Files
You can change the name of existing files with the Exec_Rename routine:
CALL Exec_Rename (oldname$, newname$)

The above statement will rename the file specified in oldname$, giving it the name specified in newname$. If either oldname$ doesn’t exist or newname$ is not valid, an error occurs.

An Example
The following example shows how you could use some of the ExecLib subroutines to selectively delete or rename files in a given directory:
LIBRARY “C:\TBSilver\TBLIBS\ExecLib.TRC”
! Use appropriate path name
DIM name$(1), size(1), dlm$(1), tlm$(1), type$(1) DIM dirnames$(1)

DO
MAT REDIM dirnames$ (100)
INPUT PROMPT “Give full path name for directory to be examined”: dir$

CALL Exec_ChDir (dir$)
! Change to directory to be removed
CALL Exec_ReadDir (“”, name$(), size(), dlm$(), tlm$(), type$(), vname$)

FOR i = 1 to Ubound(name$)
IF type$(i)[1:1] = “-” then ! A file
PRINT name$(i), size(i), dlm$(i), tlm$(i)
INPUT PROMPT “Rename (r), delete (d), or continue(c)?”: action$ LET action$ = lcase$(action$[1:1])
SELECT CASE action$
CASE “r”
! Rename it
INPUT PROMPT “New name? “: newname$ CALL Exec_Rename (name$(i), newname$)
CASE “d”
UNSAVE name$(i)
! Remove it
CASE ELSE END SELECT
ELSE ! A directory
LET num_dirs = num_dirs + 1
LET dirnames$(num_dirs) = name$(i)
! Store the name
END IF NEXT i

IF num_dirs > 0 then
! Subdirectories were found
MAT REDIM dirnames$(num_dirs)
LET num_dirs = 0
! Reset directory counter
PRINT “The following subdirectories were found within the directory:” MAT PRINT dirnames$
END IF

INPUT PROMPT “Examine other directories (y or n)?”: more$
IF lcase$(more$[1:1]) = “y” then let flag = 1
! Repeat loop

LOOP WHILE flag = 1

END