home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
ftp.barnyard.co.uk
/
2015.02.ftp.barnyard.co.uk.tar
/
ftp.barnyard.co.uk
/
cpm
/
walnut-creek-CDROM
/
JSAGE
/
ZSUS
/
PROGPACK
/
MEYERTUT.LBR
/
MEYER11.TZT
/
MEYER11.TXT
Wrap
Text File
|
2000-06-30
|
25KB
|
778 lines
CP/M Asssembly Language: Part XI
(a) Enhanced Console I/O
(b) The Disk Directory
by Eric Meyer
This month's installment is mostly about the structure of
the CP/M disk directory, and how to use it. As an exercise we
will construct a utility, XREN, that can rename a group of files
using wildcards. But first, I want to return to the subject of
console I/O and offer some enhancements to what we have
previously covered.
1. Console Status
Long ago, we described how to use BDOS 1 and 2 to get
console input, and to send a character to the console. However,
there are a few refinements of which you should be aware.
First, it is useful to be able to tell whether or not a key
has been pressed, without actually reading it in if it has.
Suppose a program is displaying a real-time clock on screen
while waiting for user input. If it does this:
CALL CKDISP ; Display the clock time
MVI C,1
CALL BDOS ; Get key input
everything (including its clock display) will stop until a
key is pressed! The solution is to use BDOS 11 (Console
Status) in a loop that only goes to read a character when it
knows there's one waiting:
LOOP: CALL CKDISP ; Display the clock time
MVI C,11 ; Check console status
CALL BDOS ; Has a key been pressed?
ORA A ; If not, loop and
JZ LOOP ; redisplay until one has
BDOS 11 returns zero in A if there is no key waiting to
be read from the keyboard, and nonzero if there is one. Similar
tricks are used by text editors that mustn't take the time to
redisplay the screen while you're still typing in text, games
that want to keep the enemy closing in on you while you're still
deciding which direction to fire in, and so on.
2. Direct Console I/O
BDOS 1 and 2 are not "direct" I/O functions: they both
do some extra processing, which you may sometimes not want.
BDOS 1 echos the character it gets to the console;
BDOS 2 does some interpreting of the outgoing character
(e.g., expanding tabs to spaces). The BDOS 6 function
provides "direct" console I/O, for times when you want to read in
a character without echo, or to send out a character without
translation.
For output, you place the character in the E register; for
input, you put 0FFh there. Thus the sequence:
MVI E,CHAR
MVI C,6 ; Direct console I/O
CALL BDOS
will send "CHAR" directly to the console.
The input function is kind of a combination of BDOS 1
and 11 (input and status):
MVI E,0FFH
MVI C,6
CALL BDOS
will return 0 if no key has been pressed. If there is a key,
it will return it without echoing it.
So if you want to wait and get a character without echo, you
can do this:
LOOP: MVI E,0FFH
MVI C,6
CALL BDOS
ORA A ; Keep trying until key ready
JZ LOOP
This is very useful: there are times when you don't want
the character typed to show on the screen at all, or at least not
until you determine whether it was legal.
The above method works under both CP/M 2.2 and 3.0.
In addition, CP/M 3.0 adds two more features to BDOS
6: if you MVI E,0FEH, you get a plain console status function
(like BDOS 11), and if you MVI E,0FDH, you get plain
console input.
You may be tempted to use the "FD" option in place of the
LOOP above, as it's simpler, but remember that CP/M
2.2 doesn't support this. In fact, if you run either of these
options under CP/M 2.2, they will send a funny character
(probably "}" or "~") to the console, and not read in anything at
all! So it may be best to stick with the "FF" option alone.
3. The Disk Directory
Now back to our main topic: the CP/M disk directory.
You need to know how this is put together if you want to write
programs to display a directory, rename files, etc. You also can
use a "disk doctor" program like DU to UNerase files, move them
to different user areas, and so on, if you know how a directory
is organized.
CP/M divides a disk into three parts:
* The "system tracks", used to "boot" the CP/M system;
* The "directory"; and
* The "data area".
On an Osborne SSDD (200k) disk, for example, the first 15K
is the system tracks, then 2K for the directory, then 183K of
data space, allocated in 1K blocks. Each directory entry takes 32
bytes, so 2K will hold 64 entries.
The entry looks very much like the File Control Block (FCB)
whose structure we've already examined. (This is no coincidence;
when you open a file, CP/M uses the FCB as a "working copy" of
the directory entry.)
FCB: d F I L E N A M E T Y P e x x x
x x x x x x x x x x x x x x x x
c r r r
The actual directory entry structure is:
u F I L E N A M E T Y P e x x f
g g g g g g g g g g g g g g g g
The filename and "e"xtent byte are in the same place --
we'll talk about the "f"illed byte in a minute. The "d"rive byte
has turned into a "u"ser byte. This disk may be logged in as any
CP/M drive, so this position is used to store the user area of
the file. These run from 00 to 0F (15). If you see "E5" in the
user byte, the file has been erased, and its allocated groups may
now be in use by another program, so better not mess with it, for
now.
CP/M Plus also uses some other values, like "20" (32), to
indicate disk labels, password XFCBs, and time/date stamp areas,
all of which occupy directory space. We will ignore these.
The "g"roup bytes record where on the disk the file data has
been stored. An allocation "group" is a block of space on the
disk, excluding the system tracks. On an Osborne SSDD disk, each
block is 1k; the directory occupies groups 0-1, and the rest (2 .
. .) are used for file storage.
When you first copy files onto a disk, the groups will be
nicely sequential, e.g.,
0 P I P _ _ _ _ _ C O M 0 x x x
02 03 04 05 06 07 00 00 00 00 00 00 00 00 00 00
0 X D I R _ _ _ _ C O M 0 x x x
08 09 00 00 00 00 00 00 00 00 00 00 00 00 00 00
But after you've erased a few files and overwritten them,
causing the groups to be reassigned, things will get more
complicated. Notice that there's room for 16 groups, or 16K, also
called one "extent". (It is possible for a larger disk to have a
2K or 4K block size, in which case each entry holds 32K or 64K,
two or four extents.) What happens when a file grows larger than
this? It has to continue into another directory entry.
0 H U G E _ _ _ _ F I L 0 x x 80
02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11
0 H U G E _ _ _ _ F I L 1 x x 17
12 13 14 15 00 00 00 00 00 00 00 00 00 00 00 00
Note how the "e"xtent byte shows that the first entry is
extent 0, the second is extent 1. Observe also the "f"illed byte:
this tells you how many records in the extent are occupied. If
the extent is full, you'll see 80h (128), if not (as is usually
the case for the last extent of a file), you'll see something
less.
One final caveat. You may find that the high bit is set on
one or more of the file name characters in the directory. This is
where CP/M stores the file "attributes" like SYStem. You
will often want to mask this off when manipulating filenames
gotten from a directory.
4. Searching for Files
You can't read or write the directory tracks directly using
BDOS calls. All you can do is search for particular
filenames using the BDOS functions 17 (Search First) and 18
(Search Next).
Suppose you want to know whether HUGE.FIL is in the
directory of disk A:. You would set up an FCB for
A:HUGE.FIL:
FCB: 1 H U G E _ _ _ _ F I L 0 x x x
x x x x x x x x x x x x x x x x
0 x x x
Now you could try to open the file; an error would
indicate that it didn't exist. But let's use a more
sophisticated method:
LXI D,FCB
MVI C,17 ; Search First
CALL BDOS
Now we get a whole wealth of information!
If the file didn't exist, the Accumulator will contain 0FFh.
But if it did, A will contain a value from 0-3, and the current
DMA (0080h, if you haven't changed it) will contain the record
from the directory that contains this entry (and three others).
The code 0-3 tells you which one of these is the one you
wanted: multiply by 32, then add the DMA address, and you will be
pointing to it. Here's the whole sequence (assuming
DMA=0080):
LXI D,FCB
MVI C,17 ; Search First
CALL BDOS
CPI 0FFH
JZ NOFILE ; Quit if not found
ADD A
ADD A
ADD A
ADD A
ADD A ; Code * 32
MOV E,A
D,0 ; Put it in DE
LXI H,0080H
DAD D ; Add DMA, pointer to entry
At this point, HL points to the start of the desired
directory entry. You could now do all sorts of things: examine
the attribute bits to see whether the file is SYStem or R/O; try
to figure out how big the file is . . .
But this is most commonly used in conjunction with the
Search Next command, because you can find any number of specific
files that match some ambiguous (general) pattern. The character
"?" in an FCB serves as a "wildcard" to match any letter of a
filename. When you type a "*" in a filename, CP/M fills out
all the remaining places with "?".
So for example, if you type:
A>DIR B:*.COM
CP/M parses this into the default FCB (at 005Ch) as follows:
005C: 2 ? ? ? ? ? ? ? ? C O M 0 x x x
x x x x x x x x x x x x x x x x
0 x x x
It will then use BDOS 17 and 18 to find, one at a
time, every file that matches that description. After BDOS
17 is used once to establish that some files exist, BDOS 18
can be used repeatedly to find all the rest that match. This is
how CP/M's "DIR" function works.
It's important to note that most BDOS file functions
do not work with "ambiguous" filenames (wildcards). The only ones
that do work are these two Search functions, and Delete File. (Be
careful with that one!) As an exercise, you might consider
writing your own little "XDIR" program. Use the code above
(remember to substitute BDOS 18 for 17 after the first
find) to find each file and point to the name, then print out the
string.
Once it works, you can refine it by deciding for yourself
how to separate one from another, whether to add a "." between
the file name and type, and how to keep track of when to start a
new line to keep the display neat.
If you get lazy, peek ahead to parts of the XREN program
coming up. If you really want a challenge, consider trying to
print the names out in alphabetical order.
5. A File by Any Other Name . . .
Of course, you already have at least one program like XDIR
that does this, and more. So our programming example here will be
something more complicated -- an XREN command, that allows
renaming groups of files.
As you may already know, there is a BDOS function 23,
Rename File. To use it, you put the original filename at the
start of the FCB, and the new one in the middle, e.g.,
FCB: d O L D N A M E _ T Y P x x x x
d N E W N A M E _ T Y P x x x x
x x x x
and then you call BDOS 23:
LXI D,FCB
MVI C,23 ; Rename File
CALL BDOS
This should return 0 if successful. You will get an
error if the old name didn't exist, or the new one already does,
or the file is read/only, and so forth. (A word about these
errors: you want to avoid them wherever possible, because they
will often cause an immediate BDOS error message and warm
boot. CP/M is rather uncivil about these things.)
There are times when you would like to rename files en
masse; e.g.,
A>REN B:*.COM=B:*.OBJ
really ought to be able to rename all the .OBJ files on B: to COM.
Unfortunately this won't work: the simple CP/M REN command
(and BDOS 23, which it uses) only work with "unambiguous"
names.
Of course, now we know enough to write our own program to
successively find each specific filename that matches, and rename
them one at a time with BDOS 23. We'll call it XREN, and
its syntax will be nearly the same as REN:
A>XREN NEWNAME OLDNAME
except that now the names can contain wildcards. (Note that no
"=" is used; more on this below.)
But before the listing, a few preliminary remarks. First,
one thing about "Search Next". It has to be used more or less
consecutively, i.e., most disk operations will disturb the search
sequence if used. This includes trying to open files, or starting
another search. (XREN, for example, is going to have to check
each time whether a file by the new name already exists, and ask
whether you want to overwrite it.)
For this reason, the best technique is to get all the names
at once, saving them in a list to be processed afterwards. Mass
renaming presents some problems that aren't apparent at first
glance. One is the fact just alluded to, that (some) of the
destination file names may already exist.
Besides this, unless both source and destination names have
wildcards in exactly the same places, it can be challenging to
decide exactly what you meant to do! For example, consider:
A>XREN MAIL*.* LETTER*.*
When this comes upon the file LETTER06.BAK, what
should it call it? You might expect MAIL06.BAK, but can you
see that this requires some guesswork, and moving characters from
one position to another in the filename?
You can try to implement this if you want, but for now I
will stick with a simpler alternative: a one-to-one
correspondence between letters. So XREN in this case will
rename LETTER06.BAK to MAILER06.BAK. Similarly, what
are we to do when there are more wildcards in the source, for
example:
A>XREN LETTER*.* MAIL*.*
Again, you might want this to rename MAIL06.BAK to
LETTER06.BAK, but this is not straightforward. What would
happen to MAIL1234? (LETTER1234 won't fit.) Would you
want it to produce LETTER12? LETTER34?
Again I will opt for a simple correspondence, so that in
this case MAIL1234 will be renamed to LETTER34. (Note
a potential problem: XREN also would try to rename
MAIL6534, etc., to the same thing!)
I will leave you the option of playing it really safe, and
aborting with an error if the source has more wildcards than the
destination. See the optional bit of code in the VERIFY
routine below.
I've gone into all this detail so you will understand why
the simple CCP "REN" command can't handle mass renaming. Since
the whole process can be so confusing, we will have XREN
list out each file renamed at the console, like this:
A:MAIL01 .BAK = A:LETR01 .BAK
If the destination already exists, we will ask "Overwrite?"
and get a "Y"es/"N"o answer. And in any case we will keep an eye
on the keyboard so you can abort at any time with ^C if you don't
like what you see. (Note how our new BDOS 6 call is ideal
for this.)
One final remark: if you have CP/M 3.0, the "transient"
portion of the REN command (RENAME.COM) is in fact capable of
mass renaming, and you may not want to go to the trouble of
generating XREN for yourself. But you may still find it
interesting to see how it's done.
6. Mass Renaming: XREN
Here, then, is the complete source code for XREN. Most
of it should seem familiar by now. Comments are still provided,
but not as extensively as in the past.
;*** XREN.ASM - Global Rename utility
;*** Version 1.0 - for 8080 CP/M 2.2
;
BDOS EQU 0005H ; Page Zero equates
FCB EQU 005CH
FCB2 EQU 006CH
DMA EQU 0080H
;
ORG 0100H
;
CALL VERIFY ; Ensure legal arguments
JC ERROR
LXI H,FCB2 ; copy source into SFCB
LXI D,SFCB
LXI B,16
CALL LDIR
SUB A ; Initialize count to 0
STA FILES
MVI C,17 ; Search First
JMP FIND0
;
FIND: MVI C,18 ; Loop to find the files (Search next)
;
FIND0: LXI D,SFCB ; Entry for first time
CALL BDOS
CPI 0FFH
JZ GOTALL ; Quit if not found
LXI H,DMA ; Add 32*A to DMA to
CALL A32HL ; point to found file
XCHG
LXI H,FILES ; Get file count
MOV A,M
INR M ; Increment it
JZ TOOMNY ; Error if overflow past 255
LXI H,FLIST
CALL A16HL ; Point to next list entry
XCHG
LXI B,16 ; Put the name there
CALL LDIR
JMP FIND
;
GOTALL: LDA FILES ; Were there any?
ORA A
JZ NOFMSG ; If not, say so and quit
;
RENAME: LDA FILES ; Loop to rename the files
DCR A
LXI H,FLIST
CALL A16HL ; Point to last source
LXI D,RFCB
LDA FCB ; Copy drive number from FCB,
STAX D ; NOT user from directory
INX D
INX H
LXI B,15 ; Copy rest of source entry
CALL LDIR
LXI H,FCB ; Copy in destination mask
LXI D,RFCB2
LXI B,16
CALL LDIR
CALL NEWNAM ; Construct new name from mask
CALL SHOWIT ; Show "NEWNAME=OLDNAME"
LXI H,RFCB2
LXI D,SFCB ; Copy new name to SFCB for test
LXI B,16
CALL LDIR
CALL EXTEST ; Free to overwrite exiting file?
JC NORENM ; Skip following if "No"
LXI D,RFCB
MVI C,23 ; Rename File
CALL BDOS
ORA A
JNZ ERROR
;
NORENM: CALL INKEY ; Check keyboard
CPI 3 ; Abort on ^C
JZ ABORT
CALL CRLF ; Show new line
LXI H,FILES
DCR M ; Count down on FILES
JNZ R ENAME
;
DONE: RET ; All finished, exit quietly
;
; --- Messages ---
;
NOFMSG: CALL SPMSG ; Source file was not found
DB '<No File>',0
JMP DONE
;
ERROR: CALL SPMSG ; Something wrong w/ arguments
DB '<Error>',0
JMP DONE
;
TOOMNY: CALL SPMSG ; Over 255 files match source
DB '<Too many>',0
JMP DONE
;
ABORT: CALL SPMSG ; You pressed ^C
DB '<Abort>',0
JMP DONE
;
; --- subroutines ---
;
VERIFY: MVI C,25 ; Verify argts are legal, Carry=bad
CALL BDOS ; Get logged drive
INR A ; Adjust from 0 to 1=A:
MOV C,A
LXI D,FCB
LXI H,FCB2
LDAX D
ORA A ; If either argt has no drive,
JNZ VERF01 ; (0 = default drive)
MOV A,C ; Insert the correct drive
STAX D
;
VERF01: MOV A,M
ORA A
JNZ VERF02
MOV M,C
;
VERF02: LDAX D
CMP M ; Drives must be same
JNZ VERFNG
;
;---Omit or include as desired: ---
;
; INX H ; "Play it safe" code
; INX D
; MVI B,11
;
;VERFLP:MOV A,M ; If "?" in source,
; CPI '?'
; JNZ VERFOK
; LDAX D
; CPI '?' ; Must have in dest too
; JNZ VERFNG
;
;VERFOK:INX H
; INX D
; DCR B
; JNZ VERFLP
;----------------------------------
ORA A ; OK, clear Carry
RET
;
VERFNG: STC ; No good, set Carry
RET
;
NEWNAM: LXI H,RFCB ; Construct new name from dest. mask
MVI B,32
;
NEWN00: MOV A,M ; First strip parity from both
ANI 7FH ; (for matching and display)
MOV M,A
INX H
DCR B
JNZ NEWN00
LXI H,RFCB+1 ;Then check mask for "?"s
LXI D,RFCB2+1
MVI B,11
;
NEWNLP: LDAX D ; Got a "?"?
CPI '?'
JNZ NEWN01
MOV A,M ; Yes, replace from old name
STAX D
;
NEWN01: INX H ; Continue
INX D
DCR B
JNZ NEWNLP
RET
;
SHOWIT: LXI H,RFCB2 ; Show "NEWNAME=OLDNAME" msg
CALL SHOSUB ; Do first name
CALL SPMSG ; Then separator
DB ' = ',0
LXI H,RFCB ; Then second name
;
SHOSUB: MOV A,M ; Subroutine to show one name
ADI 'A'-1
CALL CONOUT ; Show drive
MVI A,':'
CALL CONOUT ; Then colon
INX H
MVI B,8 ; Then name
CALL SHOSTR
MVI A,'.' ; Then period
CALL CONOUT
MVI B,3 ; Finally, type
;
SHOSTR: MOV A,M ;Subroutine to show string
CALL CONOUT
INX H
DCR B ; Do B characters
JNZ SHOSTR
RET
;
EXTEST: LXI D,SFCB ; Check for existing file NEWNAM
MVI C,17 ; (return Carry to avoid overwrite
CALL BDOS ; Search First
CPI 0FFH
RZ ; Okay, doesn't exist (carry clear)
LXI H,SFCB
LXI D,RFCB ; Hmm, does exist...
MVI B,11
;
EXTLP: LDAX D ; Are names same?
CMP M
JNZ EXTST0
INX H
INX D
DCR B
JNZ EXTLP
CALL SPMSG ; Yes, don't try to change
DB ' (Unchanged)',0
STC ; Carry set
RET
;
EXTST0: CALL SPMSG ;Different, ask what to do
DB ' Overwrite? ',0
CALL CONIN
CPI 3 ; CTL-C
JZ ABORT
CALL UCASE ; Uppercase "y"="Y"
CPI 'Y'
JZ EXTST1
STC ; Said No, don't overwrite
RET
;
EXTST1: LXI D,SFCB ; Said YES, overwrite
MVI C,19 ; Delete File
CALL BDOS
CPI 0FFH
CMC ; Set carry if error
RET
;
SPMSG: XTHL ; Print inline message
SUB A
ADD M
INX H
XTHL
RZ
CALL CONOUT
JMP SPMSG
;
CRLF: MVI A,0DH ; Print a CR,LF
CALL CONOUT
MVI A,0AH
JMP CONOUT
;
INKEY: MVI A,0FFH ; Get key, if waiting
;
CONOUT: MOV E,A ; Show character in A
MVI C,6 ; Direct console I/O
PUSH B
PUSH H
CALL BDOS ; Call BDOS preserving BC,HL
POP H
POP B
RET
;
CONIN: MVI C,1 ; Console input
JMP BDOS
;
UCASE: CPI 'a' ; Uppercase char in A
xRC
CPI 'z'+1
RNC
ANI 5FH ; If it was "a...z"
RET
;
A32HL: ADD A ; Add 32*A to HL
;
A16HL: ADD A ; Add 16*A to HL
ADD A ; (used to point to FCBs)
ADD A
ADD A ; 32 (or 16) *A
ADD L ; +L
MOV L,A
RNC
INR H ; May carry 1 into H
RET
;
LDIR: MOV A,M ; Move BC bytes from HL to DE
STAX D ; (can replace with Z80 LDIR)
INX D
INX H
DCX B
MOV A,B
ORA C
JNZ LDIR
RET
;
;--- uninitialized storage ---
;
;SFCB: DS 36 ; FCB for source
;
RFCB: DS 16 ; Dual FCB for renaming
RFCB2: DS 20
;
FILES: DS 1 ; Count files
FLIST EQU $ ; and list them starting here
;
END
7. Further Details
Briefly, here's how XREN works: the source filespec is
copied from FCB2 to SFCB, where it is used with Search First/Next
(in the loop at FIND) to build up a list of filenames that
match the source. This is kept at FLIST, and
a count kept in FILES (note that we actually
copy the whole top row, 16 bytes, of the FCB for
convenience).
Then we come to the second loop, at RENAME, where we
go back through the list (in reverse order, as it happens) to
rename each file. The new filename is constructed
by combining the old one and the ambiguous
destination, as discussed above. If a file by this
name already exists, we ask whether you want to
overwrite it. (If the new name is the same as the
old, we pass on to the next, to avoid destroying
the file.)
A few points are worth noting. Pay attention to the use of
flags on return from subroutines. Setting the Carry flag to
indicate an error condition (and making sure it's clear
otherwise) is a common convention.
In the RENAME loop, remember that the directory
entries found contain user numbers, not the drive numbers we want
in the FCBs, so we have to replace these. In
VERIFY, we have to decide whether the source
and destination drives are the same. Remember that
the first byte of an FCB holds 1=A:, 2=B:, and so
forth, but you can also have 0, for "logged
drive".
So we ask BDOS what the logged drive is (it will
return 0 for A:, 1 for B: etc, so we have to increment this to
agree with the 1=A: FCB convention), and if either FCB has a 0 we
replace it with the actual logged drive. This also
ensures that the drive names will display
correctly.
We use both types of console input here: BDOS 1 for
the answer to the "Overwrite?" question, when we want to wait for
a key and have it echo; and BDOS 6 to check for ^C from the
console, where we don't want to wait or to echo it.
All the storage areas are labeled at the end, where they
won't add to the size of the COM file. You
might wonder whether the FLIST may grow so
long that it will overwrite the CCP in high
memory, but at 16 bytes per entry, there's room
for thousands of filenames.
Actually, XREN is going to give up if it encounters
more than 255 files, because FILES is a one-byte variable,
but you could change it to two bytes easily
enough. (Note the test and branch to TOOMNY:
if you INcRement a byte and it goes past 255 to
zero, the Zero flag is set.)
8. Conclusion
You'll notice that we didn't include an "=" in the
XREN command line, as we would with REN. In fact, if you
try:
A>XREN NEWFILE=OLDFILE
you'll probably see the message "<No File>", regardless.
This is because most CP/M 2.2 systems don't parse an
argument into the second FCB unless it's separated from the first
by a space. (Astute readers will recall that earlier [Part VI] I
said that they do . . . my mistake. Interestingly enough,
CP/M 3.0 does parse the second argument when an "=" is
used; this is just one of many subtle differences.)
We've been handling arguments the lazy way up to now, by
letting the CCP parse them for us. But there are
times when you may need to do this yourself (e.g.,
if you are writing a program that needs more than
two filenames as arguments, or that wants
something other than a filename, like a long text
string).
We may cover the subject of parsing arguments later, but for
now, if you want to try to modify XREN to accept the "=",
here's what you have to know. In addition to the
FCBs, the whole command line as entered is
available for examination when a program begins.
It's in the DMA at 0080, and it starts with a byte telling
you the length of the string, for example:
0080: 0A 202A2E4F424A3D2A2E434F4D [...]
(12) * . O B J = * . C O M
You could add code to the beginning of XREN to
scan this string for an "=", and then process the rest into FCB2
as the second filename. You'll want to experiment
with variations on the theme of displaying and
renaming files from the disk directory.
These are constantly needed tasks, and difficult to
accomplish in higher level languages. It's good to
be able to write utilities to get them done, with
minimal effort, the way you want.