Chapter 8

Disk Files

When you tell OS-9 or BASIC09 to store (save) data on a disk, it stores the data in a logical block called a file. The term logical means that, although the system might store portions of a file's data in several different disk locations, it keeps track of every location and treats the scattered data as though it occupied a single block. It does this automatically and you never need to worry about how the data is stored. File data can be binary data, textual data (ASCII characters), or any other useful information.


Because OS-9 handles all hardware input/output devices (disk drives, printers, terminals, and so on) in the same manner, you can send data to any of these devices in the same way. This means you can send the same information to several devices by changing the path the data follows. Fbr example, you can test a procedure that communicates with a terminal by transferring data to and from a disk drive.


BASIC09 normally works with two types of files-sequential files and random access files. The following chart shows fileaccess options, their purposes, and the keywords with which to use them:


Types of Access for Files

Access Function Use with
Type

DIR Opens a directory file for reading. OPEN
Use only with READ.
EXEC Specifies that the file to open or OPEN
create is in the execution directory, CREATE
rather than the data directory.

READ Lets you read data from the OPEN
specified file or device. CREATE
WRITE Lets you write data to the specified OPEN
file or device. CREATE



OPEN
CREATE
BASIC09 Reference

Sequential Files

Sequential files send or receive (WRITE or READ) textual data in order, the second item following the first, and so on. You can access sequential data only in the same order as you originally stored it. To read from or write to a particular section of a file, you must first read through all the preceding data in the file, starting from the beginning.


BASIC09 stores sequential file data as ASCII characters. Each block of data is separated by a delimiter consisting of a carriagereturn character (ASCII Character 13). Because BASIC09 uses this delimiter to determine the end of a record, sequential files can contain records of varying length.


Use the WRITE and READ commands to store and retrieve data in sequential files. A WRITE command causes BASIC09 to transfer specified data to a specified file, ending the data with a carriage return. A READ command causes BASIC09 to load from the specified file the next block of data, stopping when it reaches a carriage return.


Sequential File Creation, Storage, and Retrieval

BASIC09 uses the CREATE command to establish both sequential and random access files. A CREATE statement contains:








An optional colon, followed by the access mode. If you do not specify an access mode, BASIC09 automatically opens the created file in the UPDATE mode.


8-2


The following procedure shows how to create a file and write data into it:


PROCEDURE makefile
ODIM PATH;BYTE 0 establishes a variable
OREM for the path number to the file
OCREATE #PATH,"test";WRITE 0 creates the file TEST
OWRITE #PATH,"This i5 a test" 0 writes data to the file
pWRITE #PATH,"of sequential files."(* writes another line of data
pCLOSE #PATH 0 closes the path to the file
OSHELL "LIST TEST" (* displays the file contents
pEND

The first line of the procedure dimensions a variable (Path) to hold the number of the path that CREATE opens. This variable should be of byte or integer type.


When you establish a new file with CREATE, you automatically open a path to the file. You do not need to use the OPEN command.


The preceding procedure writes two lines into a file named Test. It then closes the path and uses the OS-9 LIST command to display the contents of the newly created file. You see that the data is successfully stored on disk.


The next procedure shows how to reopen an existing file for sequential access, read the contents of the file, and append data to the end of the file.


The only way to move the file pointer to the end of a sequential file is to read all the data already in the file. Once the pointer is at the end of the file, you can add data.


PROCEDURE append
ODIM PATH;BYTE 0 dimension variable to hold the number of the
OREM path to the opened file.
DOPEN #PATH,"test";UPDATE (* open file for reading and writing,
DREAD #PATH,line$ 0 read the first element of the file.
DREAD #PATH,line$ 0 read the next (the last) element.
OWRITE #PATH,"This is a test" 0 write one new line to the file.
OWRITE #PATH,"of appending to a sequential file." 0 write another.
OCLOSE #PATH 0 close the path
OSHELL "LIST TEST" 0 display the file with the new lines.
REND


BASIC09 Reference

Because the Test file already exists, this procedure uses OPEN to establish a path to the file. It uses the UPDATE mode of file access because it needs to both read from and write to the file.


The two READ statements read the file's contents and, as a result, move the file pointer to the end of the file. The WRITE statements then append two new lines. After closing the path, the procedure calls on the OS-9 LIST command to display the contents of the file, with its appended lines.


Changing Data in a Sequential File

You can also change data anywhere in a sequential file. However, if your changes are longer than the original data, the operation destroys part of the file. To change data in a sequential file, read the data preceding what you want to change, and write the new data to the file in this manner:


PROCEDURE replace
ODIM PATH:BYTE
OOPEN #PATH,"test";UPDATE
DREAD #PATH,line=
DREAD #PATH,lines
OWRITE #PATH,"Let's put new" (* write over existing 3rd and
OWRITE #PATH,"words into the old sequential file." (* 4th lines.
OCLOSE #PATH
OSHELL "LIST TEST"
REND

Notice that the total amount of data in the two new lines is exactly the same as in the two old lines. You can replace an existing line with fewer characters by padding the new data with spaces. However, if you try to replace existing lines with longer lines, the new lines write over and destroy other data in the file.


8-4
                              Disk Files / 8


    INPUT and Sequential Files


    Although you can also use the INPUT command with sequential

`' files, doing so might put unwanted data into them. When a pro
- cedure encounters INPUT, it suspends execution and sends a
    question mark (?) to the screen. This feature makes INPUT both

    an input and output statement. Therefore, if you open a file

    using the UPDATE mode, INPUT writes its prompts to the file,

    destroying data. If you specify text to be displayed with the

    INPUT command, INPUT writes this text to the file also.


    Random Access Files


    Random access files store data in fixed- or equal-length blocks. Because each record in a specific file is the same size, you can easily calculate the position of a record.


    For instance, suppose you have a file with a record length of 50bytes (or characters). To access Record 10, multiply the record number (10) by the record length (50) and move the file pointer to the calculated position (500).


    A random access file sends and receives data (using PUT and GET) in a binary form, exactly as BASIC09 stores it internally. This feature minimizes the time involved in converting the data to and from ASCII representation, as well as reducing the file space required to store numeric data. You position the random access file pointer using SEEK. Compared to sequential file access, random file access using GET and PUT is very fast.


    Using random access commands, you can store and retrieve individual bytes, strings of bytes, individual elements of arrays or total arrays with one PUT or GET command. When you GET a structure, you recover the number of bytes associated with that type of structure.


      This means when you GET one element of byte type data, you read one byte. When you GET one element of real type data, you read five bytes. If you GET an array, you read all the elements of the array. This potential for reading entire arrays at once can greatly speed disk access.


      As well as moving the file pointer to the beginning of individual records, you can also move it to any position within a record and begin reading or writing one or more bytes from that point.


                                    8-5

BASIC09 Reference
Creating Random Access Files
You create and open random access files in the same way you
create and open sequential files. The only differences are in the
commands you use to store and retrieve the data and in the
manner you keep track of where elements, or records, of a file
begin and end.
Before you can write data to a random access file, you must
either CREATE it or open it in the WRITE or UPDATE mode.
Once you have a path open to an existing file, use PUT to write
data into the file. If you open the file in the READ or UPDATE
mode, you can then use the GET command to retrieve data from
the file.
The PUT command can use only one parameter, the name of the
data element to store. The parameter can be a string, a variable,
an array, or a complex data structure.
Before storing data, you must devise a method to store it in
blocks of equal size. Knowing the unit size lets you later retrieve
the data in its original form. The following procedure shows one
way to do this: w

PROCEDURE putget OREM This procedure creates a file named Test1, reads 10 data lines, OREM PUTS them into the file, then closes the file. Next it OREM opens the file in the READ mode, GETS stored lines and lists OREM them on the display screen.

ODIM LENGTH:BYTE
ODIM NULL:STRINGf253
ODIM LINE:STRINGI25l
ODIM PATH:BYTE
OLENGTH=25
ONULL-""
OBASE 0
DON ERROR GOTO 10
ODELETE "test1" ( if the file exists, delete it.
1000N ERROR

OCREATE #PATH,"te5t1":WRITE ( create a file named test1.
OFOR T=0 TO 9
OSEEK #PATH,LENGTH*T ( find beginning of each file.
DREAD LINE: (* read a line of data.
OPUT #PATH,LINES ( store the line in the file.
ONEXT T
s-s
Disk Files / 8

OCLOSE #PATH ( close the file.
pOPEN #PATH,"test1";READ ( open the file for reading.
OFOR T=0 TO 9
OSEEK #PATH,LENGTH*T ( find the beginning of each file.
OGET #PATH,LINE ( get a line from the file.
OPRINT LINE t display the line.
ONEXT T
OCLOSE #PATH ( close the file.
REND
ODATA "This is test line #1"
ODATA "This is test line #2"
ODATA "This is test line x3"
ODATA "This is test line x4"
ODATA "This is test line #5"
ODATA "This is test line x6"
ODATA "This is test line x7"
ODATA "This is test line x8"
ODATA "This is test line x9"
ODATA "This is test line x10"

This procedure creates a file named Testl. The variable named Length stores the length of each line in the file (25 characters). The string variable Null, is a string of 25 space characters. The variable Line contains the data to store in each element (record) in the file. The variable Path stores the path number of the file.


Next, the procedure contains an ON ERROR routine that deletes the file Testl, if it already exists. Without this routine, the procedure produces an error if you execute it more than once.


Next, the routine uses CREATE to open the file Testl. The line SEEK # P A T H , LENGTH * T sets the file pointer to the proper location to store the next line. Because Length is established as 25, the file lines are stored at 0, 25, 50, 75, and so on.


After the routine initializes storage space, it begins to store data by reading the procedure data lines one at a time, seeking the proper file location, and putting the data into the file. After storing all 10 lines, it closes the file.


                                8-7

BASIC09 Reference

The last part of the routine opens the new file, uses the same SEEK routine to position the file pointer, and reads the lines back, one at a time, to confirm that the store routine is successful.


The next short routine shows how you can use a procedure to read any line you select in the file, without reading any preceding lines:


PROCEDURE randomread
ODIM LENGTH:BYTE
pDIM LINE;STRING(251
pDIM SEEKLINE;HYTE
ODIM PATH;HYTE
pLENGTH=25

pOPEN #PATH,"test1":READ ( open the file for reading.
DINPUT "Line number to display",SEEKLINE (* type a line to get.
pEXITIF SEEKLINE)10 OR SEEKLINE<1 THEN (* test if record i5 valid.
DENDEXIT ( exit loop if not.
pSEEK #PATH,(SEEKLINE-1)*LENGTH ( find the requested record.
pGET #PATH,LINE ( read the record.
pPRINT LINE ( display the record.
DPRINT
pENDL00P
pPRINT "That's all " ( end session.
pCLOSE #PATH ( close path.
pEND

The procedure asks for the record number of the line to display. When you type the number (1-10) and press ENTER , SEEK moves the file pointer to the beginning of the record you want, GET reads it into the variable Line, and PRINT displays it. The calculation ( S E E K L I N E -1 ) * L E N G T H determines the beginning of the line you want. If you type a number outside the range of lines contained in the file (1-10), the procedure drops down to Line 100 and ends.


By changing this procedure slightly, you can replace any line in the procedure with another line. The altered procedure below demonstrates this:


8-8
              Disk Files l 8


PROCEDURE random-replace
ODIM LENGTH:BYTE
ODIM LINE:STRINGf251
ODIM SEEKLINE:BYTE
ODIM PATH:BYTE
OLENGTH=25

OOPEN #PATH,"test1":UPDATE(* open the file.

OLOOP
DINPUT "Line number to display ...",SEEKLINE (* type record to find.
DEXITIF SEEKLINE)10 OR SEEKLINE<1 THEN (* test if valid number.
OENDEXIT ( exit loop if not
OSEEK #PATH,(SEEKLINE-1)*LENGTH ( find the requested record.
OGET #PATH,LINE (* get the data.
OPRINT LINE (* print the record.
OPR I NT
OINPUT "Type new line... ",LINE (* type a new line.
OSEEK #PATH,(SEEKLINE-1)*LENGTH (* find beginning of the record.
OPUT #PATH,LINE (* store the new line.
OENDLOOP (* do it all again.

OPRINT "That's all " (* terminate procedure.
OCLOSE #PATH (* close path.
REND

This time, the file is opened in the UPDATE mode to allow both reading and writing. You type the line you want to display. A prompt then asks you to type a new line. The procedure exchanges the new line for the original line, and stores it back in the file.

Using Arrays With Random Access Files

BASIC09's random access filing system is even more impressive when used with data structures, such as arrays. Instead of using a loop to store the 10 lines of the Random-replace procedure, you could store them all at once, into one record, using an array. The following procedure illustrates this:

                                8-9

BASIC09 Reference

PROCEDURE arraywrite ODIM LENGTH:HYTE ODIM LINE:STRINGt25l ODIM RECORD(10):STRINGf25l ODIM PATH:BYTE OLENGTH=25

DON ERROR GOTO 10 DELETE "testl" 1000N ERROR

(* delete Testl if it exists,

OCREATE #PATH,"testl":WRITE ( create Test1,
OHASE 0
OFOR T=0 TO 9

DREAD RECORD(T)
ONEXT T
OSEEK #PATH,O
OPUT #PATH,RECORD
OCLOSE #PATH

DOPEN xPATH,"testl":READ
OFOR T=0 TO 9
OSEEK xPATH,LENGTH*T
OGET xPATH,LINE
OPRINT LINE
ONEXT T
OCLOSE PATH
REND
ODATA "This is test line 01"
ODATA "This is test line x2"
ODATA "This is test line x3"
ODATA "This is test line x4"
ODATA "This is test line 6"
ODATA "This is test line x6"
ODATA "This is test line 01
ODATA "This is test line x8"
ODATA "This is test line #9"
ODATA "This is test line x10"

Read data lines into RECORD array,

set pointer to beginning of file, Store the entire array into file, close path to file,

open the file to read,

find each element, read an element, print the element,

8-10
Disk Files / 8

This procedure reads the 10 lines into an array named Records. Then it places the entire array in the Testl file, using one PUT statement. To show that the structure of the file is still the same, the original FOR/NEXT loop reads the lines, one at a time, and displays them.


Notice that, because you need to write only one element, you can set the file pointer to 0 ( s E E K # P A T H , 0). You can rewind a file pointer (set it to 0) at any time in this manner.


You could save additional programming space by also reading the 10 lines back into memory as an array. The following procedure uses a new array, Readlines, to call the file back into memory, and displays the lines.


PROCEDURE arrayread
DHASE 1
DDIM READLiNES(10);STRINGL25l
DDIM PATH;HYTE

DOPEN xPRTH,"te5t1";READ (~ open file,
DGET xPATH,READLINES (~ read file into array,

` DCLOSE #PATH
DFOR T=1 TO 9
DPRINT READLINES(T) (~ print each element of the array,
DNEXT T
[]END

      Using Complex Data Structures


      In the previous section, you stored and retrieved elements of an array that were all the same size, 25 characters. Often you need to store elements of varying sizes, such as when you create a data base program with several fields in one record.


      The following examples create a simple inventory system that requires a random access file having 100 records. Each record includes the name of the item (a 25-byte string), the item's list price and cost (both real numbers), and the quantity on hand (an integer).


8-11
BASIC09 Reference

First, you use the TYPE command to define a new data type that describes such a record. For example:


TYPE INV-ITEM= NAME: STRINGf25J;LIST,COST:REAL;
QTY: INTEGER

Although this statement describes a new record type called Inv-item, it does not assign variable storage for the record. The next step is to create two data structures: an array of 100 records of type Inv-item named Inv-array and a working record named

Work-rec. The following lines do this:

DIM INV_ARRAY(1 00): INV-ITEM
DIM WORK-REC : I NV-ITEM

To determine the number of bytes assigned for each type, you can use BASIC09's SIZE command. SIZE returns the number of bytes assigned to any variable, array, or complex data structure. For example, the command line SIZE (W 0 R K I -R E C) returns the number 37. The command S I Z E ( I N V-A R R A Y ) returns the number 3700.


You can use SIZE with SEEK to position a file pointer to a specific record's address.


The following procedure creates a file called Inventory and immediately initializes it with zeroes and nulls strings. Five INPUT lines then ask you for a record number and the data to store in each field of the record. You can fill any record you choose, from 1 through 100.


When one record is complete, the procedure uses PUT to store the record. Then, it asks you for a new record number. If you wish to quit, enter a number either larger than 100 or smaller than 1.


PROCEDURE inventory OREM Create a data type consisting of a 25-character name field, OREM a real list price field, a real cost field, and an integer OREM quantity field.
OTYPE INV-ITEM=NAME:STRINGt25l; LIST,COST:REAL; QTY:INTEGER.

pDIM INV-ARRAY(100):INV_ITEM (* dimension an array using new type,

8-12
                              Disk Files l 8


      pDIM WORK-REC; INV-ITEM

      OREM (* dimension a working variable of the new type.

,,,~-~ pDIM PATH;HYTE

      DON ERROR GOTO 1e

      pDELETE "inventory"

      ICON ERROR


      pCREATE OPATH,"inventory" ( create a file named Inventory,

      OWORK-REC.NAME=" ( set all data elements to null or 8.

      OWORK-REC,LIST=0

      OWORK-REC. COST=0

      OWORK-REC.QTY=8

      OFOR N=1 TO 188

      OPUT #PATH,WORK-REC

      ONEXT N

      pL00P

      OINPUT "Record number? ",RNUM ( enter number of record to write.


OF RNUM<1 OR RNUM>188 THEN ( check if number is valid.
pPRINT
OPRINT "End of Session" ( if not, end session.
OPRINT
OCLOSE #PATH
OEND
DENDIF
OINPUT "Item name? ",WORK-REC.NAME ( type data for record.
OINPUT "List price? ",WORK-REC.LIST
OINPUT "Cost price? " WORK-REC. COST
OINPUT "Quantity? ",WORK-REC.QTY
OSEEK #PATH,(RNUM-1)*SIZE(WORK-REC) (* find record.
pPUT #PATH,WORK-REC ( write record to file.
OENDLOOP

Notice that the INPUT statements reference each field separately, but the PUT statement references the record as a whole.

The next procedure lets you read any record in your Inventory file, and displays that record. If you ask for a record you have not yet filled with meaningful data, the display consists of a null string and zeroes.

PROCEDURE readinv
OTYPE INV-ITEMzNAME;STRING(25I; LIST,COST;REAL; QTY;INTEGER
ODIM WORK-REC; INV-ITEM

                                8-13

BASIC09 Reference

[]DIM PATH:BYTE
[]OPEN OPATH,"INVENTORY";READ
DLOOP ,,
[]INPUT "Record number to display? ",RNUM
OF RNUM<1 OR RNUM>180 THEN
OPRINT "End of Session"
[]PRINT
OCLOSE #PATH
[]END
DENDIF
[]SEEK #PATH, (RNUM-1)*SIZE(WORK-REC)
[]GET #PATH,WORK-REC
[]PRINT "x","Item","List Price","Cost Price","Quantity"
pPRINT ·'--____--______-___________-____________________________________________

[]PRINT RNUM,WORK-REC.NAME,WORK-REC.LIST,WORK-REC.COST,WORK-REC.QTY
pPR I NT
pENDLOOP
OEND

This procedure accesses the file one record at a time. It is not necessary to do so. You can read the entire file into memory at once by dimensioning an inventory array and getting the whole file into it:


[]TYPE INV-ITEM-NAME;STRINGf251; LIST,COST:REAL; QTY:INTEGER
ODIM INV-ARRAY(108);INV-ITEM
[]SEEK #PATH, 6 (rewind the file*)
[]GET #PATH, INV-.ARRAY

The examples in this section are simple, yet they illustrate the combined power of BASIC09 complex data structures and the random access file statements. They show that a single GET or PUT statement can move any amount of data, organized in any way you want. Other advantages are of using complex data structures are:


    The procedures are self-documenting. You can see easily what a procedure does because its structures can have descriptive names.


    · Execution is extremely fast.


    Procedures are simple and usually require fewer statements to perform I/O functions than other BASICS.


    8-14

            Disk Files / 8


The procedures are versatile. By creating appropriate data structures, you can read or write almost any kind of data from any file, including files created by other programs or languages.


8-15