|
|
|
Anyone fortunate enough to have extended access to a VAX computer usually builds up an extensive repertoire of DCL symbols, command procedures, utility programs, and other assorted paraphenalia. The VMS operating system provides a plethora of system services and utilities to make the life of a computer user easier. Although the big name utilities such as the Forms and Screen Management Systems (FMS and SMG) get all the attention, they're mainly applicable to the once-a-year programming tasks. There are a number of more mundane utilities that take some of the drudgery and frustration out of the everyday computer tasks.
Two functions in particular, F$PARSE
and
F$SEARCH
, I have found to be very
useful; without exaggeration, they make a world of difference in the
user-friendliness of the system. These two functions are available at both
DCL and program levels. In their DCL form, the functions are useful to
both programmers and non-programmers alike. In their program-callable
form, they enable a programmer to simply and quickly provide a
sophisticated user interface.
As its name implies, F$PARSE
parses VAX/VMS file
specifications, in the sense of breaking a file specification down into its
constituent parts. Because of its powerful translation and substitution
capabilities, however, F$PARSE
can not only divide, but also
conquer, VMS file names.
Given a file specification, F$PARSE
expands the file
specification, translating logical names and filling in defaults for
missing fields. F$PARSE
returns either the fully-expanded
file specification or specified parts of it.
F$PARSE
is available as a lexical function in the VAX/VMS
Digital Command Language (DCL) and is invoked as follows:
result = F$PARSE (primary [,default] [,related [,field [,parse_type]]])
The primary file specification is required, but can be a simple null string
(""); the other arguments are optional. Program 1
presents a FORTRAN version of F$PARSE
that is callable from
any programming language that conforms to the VAX/VMS Procedure Calling
Standard. It is invoked in this manner:
status = F$PARSE ([primary], [default], [related], [field], [parse_type], result)
The primary file specification is optional and defaults to the null string if not specified.
Parsing utilizes three file specifications: primary, default, and related
file specifications (FS). Any of these specifications is optional (the DCL
function requires the primary FS). First, each of the three specifications
is expanded; this involves translating logical names embedded in the file
specifications. Then, missing fields in the primary FS are substituted
from the corresponding fields in the default FS. After that, missing
fields in the primary-default FS are substituted from the related FS. If
any missing fields remain, they are filled with system defaults, e.g.,
SYS$DISK
, the user's current directory, etc. A schematic
representation of this substitution process is presented in
Figure 1a.
The substitution process makes F$PARSE
very powerful. For
example, suppose that an input file is interactively specified for a
program and that the program needs to generate an output file of the same
name as the input file, but with a different extension and in a different
directory. No more scanning file names for brackets and periods - simply
call F$PARSE
:
Input file specification - "DISK:[INPUT_DIRECTORY]DATA.INP" Output file directory - "[OUTPUT_DIRECTORY]" Output file extension - ".OUT" Call F$PARSE (primary_spec => ".OUT", default_spec => "[OUTPUT_DIRECTORY]", related_spec => "DISK:[INPUT_DIRECTORY]DATA.INP", field => null, parse_type => null, result)
Substitution in the primary FS from the default FS produces a
primary-default FS of [OUTPUT_DIRECTORY].OUT
. Substitution in
the primary-default FS from the related FS yields the final result,
DISK:[OUTPUT_DIRECTORY]DATA.OUT
. Figure
1b graphically shows the workings of F$PARSE
in this
example. As you can see, components of higher-level file specifications
drop down into the result through "holes" in lower-level file
specifications.
The field argument controls the result returned to the caller. With this
argument, the caller can ask that the entire, fully-expanded file
specification be returned, or that just the node, device, directory, name,
type, or version field of the specification be returned. (NOTE:
NODE
does not work unless you explicitly include the node in
one of the input file specifications.)
Normally, parsing checks that the disk and directory in the fully-expanded
file specification physically exist; an error is returned if they don't
(blanks are returned in DCL). By specifying a parse type of
SYNTAX_ONLY
, this check is prevented. Also, concealed logical
names are normally not translated further. By specifying a parse type of
NO_CONCEAL
, concealed logical names will be expanded. (A
concealed logical name is an occasionally useful trick that allows you to
assign a logical name to a disk and directory and then reference
subdirectories relative to the logical name; look up
ASSIGN/TRANSLATION_ATTRIBUTES
in the DCL dictionary.)
The full power of F$PARSE
is best illustrated by example. The
example presented earlier is an obvious and frequent application of
F$PARSE
, that of substituting file extensions. Undoubtedly,
VAX/VMS compiler writers are all familiar with F$PARSE
:
list_file =F$PARSE
(".LIS", source_file) object_file =F$PARSE
(".OBJ", source_file)
Field extraction is another feature of F$PARSE
that has many
practical applications. For example, users on our system are partitioned
into functional groups, both within our organization and in the disk
directory structure; i.e., software engineering accounts are located in the
[SWENGR...]
directory tree, systems engineering accounts in
the [SYSENGR...] tree, etc. Naturally, disk space is always at a premium,
so whenever the number of free blocks drops down into the 3-digit range, we
generate and distribute disk usage reports. An overall summary of disk
usage is easily obtained using a DIR/SIZE/TOTAL [000000...]
command, but an accounting based on functional groups is more desirable.
Typing in a separate DIR/SIZE/TOTAL [group...]
command for
each group becomes unmanageable as the number of groups and the number of
disks increase. The following DCL procedure puts the offending groups on
public display (F$SEARCH
, described in more detail later,
returns the file specification of each directory file in
[000000]
):
$ INQUIRE disk "Disk" $ $ loop: $ dir_file =F$SEARCH
("''disk':[000000]*.dir") $ IF dir_file .EQS. "" THEN EXIT $ dir_name =F$PARSE
(dir_file, , , "NAME") $ IF dir_name .NES. "000000" THEN - DIR/SIZE/TOTAL 'disk':['dir_name'...] $ GOTO loop
An earlier issue of DEC Professional discussed the problem of
how to determine your current directory from within a program.
F$PARSE
provides a simple solution. In DCL:
$ WRITE SYS$OUTPUT F$PARSE
("")
displays the following on my terminal:
DISK$T2B0:[AMEASDAY].;
In FORTRAN:
CALL F$PARSE
( , , , , , RESULT)
LENGTH = INDEX (RESULT, ']')
CURRENT_DIRECTORY = RESULT(1:LENGTH)
gives your current directory. An alternative to the FORTRAN
INDEX
function is to call VMS Run-Time Library routine
STR$TRIM
, which trims trailing blanks from strings; subtract 2
from the STR$TRIM
length to strip the ".;" from the
disk/directory specification.
So how is F$PARSE
used in the real world? A final example
presents F$PARSE
in multiple roles and illustrates the ability
of F$PARSE
to provide a powerful, easily-implemented, user
interface. Our latest project processes LANDSAT and SPOT satellite
imagery. The image data, ingested from high-density tapes, is stored on
any 1 of 4 "image" disks, IDA0
, IDA1
,
IDB0
, or IDB1
. On a disk, the image data is
spread over 512 image files, IMAGEFILE.001
through
IMAGEFILE.512
. An interactive, image display utility was
written to display the ingested data on video monitors; the program was
heavily used during the testing and verification of the system.
Heavy usage of the image display utility necessitated a sophisticated
operator interface. The first step in using the program is to specify the
base file of the image data to be displayed. Normally, this is
IMAGEFILE.001
, but for some data types, this is not the case;
also, the user may wish to begin the display further into the imagery. The
operator can change the base file at any time and is prompted for the new
file name. The following FORTRAN code processes the file name input by the
user (LIB$FIND_FILE
, alias F$SEARCH
, is described
in more detail later):
old_file_name = base_file_name CALLF$PARSE
(new_file_name, old_file_name, , , , base_file_name) new_file_name = base_file_name context = 0 CALLLIB$FIND_FILE
(new_file_name, base_file_name, context)
This unimpressive piece of code results in a complex transformation of the file name input by the user. Suppose the current, base file name is
IDA0:[IMAGERY]IMAGEFILE.001
After the display utility puts up garbage on the video monitor, the user
realizes that the desired data is on disk IDB0. Must the user now enter
all 27 characters of IDB0:[IMAGERY]IMAGEFILE.001
simply to
correct the disk name? Fortunately, no. By entering only
IDB0:
and letting F$PARSE
work its magic, the
base file name is changed to
IDB0:[IMAGERY]IMAGEFILE.001
The display utility now throws what appears to be a very regular pattern up
on the monitor. The user, trying to display subsampled image data,
displayed timecode data instead; the real image data starts in
IMAGEFILE.002
. Must all 27 characters of
IDB0:[IMAGERY]IMAGEFILE.002
be entered when you only want to
change the file extension? Thank goodness for F$PARSE
! Simply
enter .002
and your new base file name is
IDB0:[IMAGERY]IMAGEFILE.002
Another option in the image display utility, not shown in the code fragment
above, is to change to your process' current disk and directory by entering
$
. A call to F$PARSE ( , , , , , new_file_name)
,
followed by the usual file name processing, gives me
DISK$T2B0:[AMEASDAY]IMAGEFILE.002
The use of LIB$FIND_FILE
in the base file name processing
allows the use of wildcards in file specifications (i.e., saying
*.001
instead of IMAGEFILE.001
); see the section
on F$SEARCH
below.
The multitude of image files posed a file handling problem to the image
display utility, but again, F$PARSE
comes to the rescue.
Typically, the display program steps through the image files, reading and
displaying the data, beginning with the base file. To determine the base
file number, do the following:
CALL F$PARSE
(image_file_name, , , %DESCR('TYPE'), , extension)
READ (extension(2:4), '(I3)') file_number
As you sequence through the files (incrementing the file number by 1), each succeeding file name is constructed as follows:
extension = '.NNN;0'
WRITE (extension(2:4), '(I3.3)') file_number
CALL F$PARSE
(extension, image_file_name, , , , file_name)
You can see that a relatively insignificant investment in the use of
F$PARSE
is returned many-fold. Not only does
F$PARSE
reduce some complex file name processing to one or two
lines of code, it also extends the realm of possible functions attempted by
the software writer.
As its name implies, F$SEARCH
searches for VAX/VMS file
specifications that match a given template, usually a wildcard file
specification such as *.FOR
, etc. Like F$PARSE
,
F$SEARCH
does more for less, but when used in unexpected ways,
F$SEARCH
becomes invaluable.
F$SEARCH
searches a directory file for the next file name that
matches a wildcard file specification (knowledge of the last file name
found is maintained internally by F$SEARCH
). For example, an
F$SEARCH
of LOG*.COM
would find
LOGIN.COM
, LOGOUT.COM
, etc. The fully-expanded
file specification for the matched file is returned by
F$SEARCH
.
F$SEARCH
is available as a lexical function in VAX/VMS DCL and
is invoked as follows:
result = F$SEARCH (wildcard [,stream_ID])
The wildcard file specification is required; the stream ID is optional.
The VAX/VMS Run-Time Library includes a version of F$SEARCH
that is callable from any programming language that conforms to the VAX/VMS
Procedure Calling Standard. Called LIB$FIND_FILE
, it is
invoked in this manner:
status = LIB$FIND_FILE (wildcard, result, context [,default] [,related] [,stv] [,flags])
The wildcard file specification and the result and context arguments are all required; the other arguments are optional.
The wildcard file specification is the template used for the file name
search. The wildcard character (*
) can be used freely
throughout the FS and matches zero or more characters in a file name (an
alternate wildcard character, %
, matches exactly one
character). Use of *
in a directory specification causes
F$SEARCH
to search multiple directories; the ellipsis
(...
) results in entire directory trees being searched. If no
version number is specified, F$SEARCH
always returns the
latest version of a particular file; a wildcard version number
(;*
) returns all the versions of a file, in order of
descending version numbers.
Before attempting to match file names, F$SEARCH
generates a
fully-expanded wildcard file specification, including translation of
logical names and field substitution from the default and related file
specifications (LIB$FIND_FILE
only). In essence,
F$SEARCH
is calling our old friend, F$PARSE
:
expanded_wildcard = F$PARSE (wildcard, default, related)
As explained earlier, the result returned by F$SEARCH
is the
fully-expanded file specification (disk, directory, and everything) of the
next file that matches the wildcard FS. There are some slight differences
in error handling, however, between F$SEARCH
and
LIB$FIND_FILE
. The DCL F$SEARCH
returns the full
file specification if the search was successful and an empty string ("")
otherwise.
Program-callable LIB$FIND_FILE
returns the full file
specification and a successful status (RMS$_NORMAL
) if the
search was successful. In the case of an unsuccessful search,
LIB$FIND_FILE
returns the fully-expanded wildcard file
specification and the reason for the lack of success: file not
found (RMS$_FNF
) if no matching files at all were
found, or no more files (RMS$_NMF
) if the set of
matching files was exhausted. If desired, the secondary status value
(STV
) provides additional error information received from the
Record Management Services (RMS).
Sometimes it is necessary to search multiple sets of file names
simultaneously (an example is given further on). To accomplish this, the
contexts of the different search "streams" must be kept separate.
LIB$FIND_FILE
provides this capability via its
CONTEXT
argument. LIB$FIND_FILE
stores the
address of a dynamically-allocated RMS file access block (FAB) in this
variable. CONTEXT
should be initialized to zero before the
first call to LIB$FIND_FILE
. Completion of a search due to a
no more files condition causes automatic deallocation of the
FAB and resetting of CONTEXT
to zero. If the search must be
terminated prematurely, the program can explicitly discard the context
using LIB$FIND_FILE_END
:
CALL LIB$FIND_FILE_END (context)
By using different context variables for the different search streams, say
CONTEXT_1
... CONTEXT_N
for streams 1 ...
N
, a program can perform parallel searches of different file
name sets.
LIB$FIND_FILE
's simultaneous search function looks rather
low-level when compared to that of DCL's F$SEARCH
. Avoiding
the bookkeeping involved with context variables, F$SEARCH
lets
you directly specify which set of file names you are searching, i.e., 1 for
the first search stream, 2 for the second, etc.
The remaining argument to LIB$FIND_FILE
, the user flags, has
no counterpart in F$SEARCH
. Setting and resetting specific
flag bits in the 32-bit longword passed to LIB$FIND_FILE
modifies the behavior of the search process. Like most of
LIB$FIND_FILE
's arguments, the flags are optional and assume a
value of zero if not specified.
Setting bit 0 of the flag argument raises the NOWILD
flag,
causing LIB$FIND_FILE
to reject true wildcard file
specifications! This feature could possibly be useful for interactive
applications in which the user may only enter the name of a single file and
not the name of a group of files; a status of no wildcard
permitted (SHR$_NOWILD
) is returned if the file
specification includes a wildcard character.
The MULTIPLE
flag, bit 1 of the flag argument, instructs
LIB$FIND_FILE
to retain the file search context across
multiple wildcard file specifications; this is known as temporary or
"sticky" defaulting. Normally, when you complete the search for one
wildcard file specification, the context of that search is discarded
(LIB$FIND_FILE
implicitly calls
LIB$FIND_FILE_END
). If implemented in this fashion, a DCL
command such as
$ PURGE [other_dir]*.OBJ, *.EXE
would be mis-interpreted as
$ PURGE [other_dir]*.OBJ, [current_dir]*.EXE
This misunderstanding occurs because the [OTHER_DIR]
context
of the *.OBJ
search is not carried over to the
*.EXE
search. If you set the MULTIPLE
flag,
however, LIB$FIND_FILE
makes the [OTHER_DIR]
context "stick" to you as you pass from the first wildcard specification to
the second, thereby giving you your intended
$ PURGE [other_dir]*.OBJ, [other_dir]*.EXE
One of the examples presented later extracts multiple wildcard file
specifications from a command line and conducts consecutive wildcard
searches. (A potentially simpler way of accomplishing this task, search
list logical names, does not work as advertised. In my own testing, the
MULTIPLE
flag did NOT cause temporary defaulting for search
list logical names, despite what the LIB$FIND_FILE
documentation says.)
Being easily awed by subprograms with large numbers of arguments, I
initially thought that F$PARSE
, with its 5 arguments, was
considerably more powerful and useful than F$SEARCH
, with its
meager allotment of 2 arguments (I hadn't yet discovered
LIB$FIND_FILE
and its 7 arguments!). Well,
F$PARSE
is powerful and useful, but its utility is no match
for F$SEARCH
. The following examples are taken from actual
applications and should convince you of F$SEARCH
's value.
F$SEARCH
is not restricted to scanning sets of many files; its
simplest application, in fact, is to test for the existence of an
individual file. Primarily of use in DCL command procedures, this
technique appears frequently in the pages of DEC Professional.
In a recent "Managing Your MicroVax" column by David W. Bynon, for
instance, a log file is created if one doesn't already exist:
$ IF F$SEARCH (file_name) .EQS. "" THEN CREATE file_name
(As F$SEARCH
is used to check for an existing file, so can
F$PARSE
be used to verify that a disk/directory exists in
which to create a new file.)
Notwithstanding its utility as a shorthand file exists? test,
F$SEARCH
is most often used to feed file names to an iterative
"file name cruncher" (see Figure 2; bold arrows mark
the files matched by F$SEARCH
). The disk space problem,
presented as an F$PARSE
example earlier, illustrates the
paradigmatic use of F$SEARCH
in DCL command procedures:
$ loop: $ next_file = F$SEARCH (wildcard_spec) $ IF next_file .EQS. "" THEN GOTO end_of_loop $ ... process the next file ... $ GOTO loop $ end_of_loop:
In FORTRAN:
DO WHILE (status) status = LIB$FIND_FILE (wildcard_spec, next_file, context) IF (status) THEN ... process the next file ... ENDIF ENDDO
A word of warning: if the wildcard file specification contains no
wildcards, DCL's F$SEARCH
loop never terminates; the same,
fully-expanded file specification is continually returned.
LIB$FIND_FILE
, more sensibly, returns the fully-expanded file
specification on the first iteration and a no more files
condition on the second.
F$SEARCH
, in its standard form, was used to great advantage on
our project. Within our image processing system, requests for work are
represented by work order files. The contents of a work order file
identify the imagery to be input, the corrections to be applied, and the
products to be output. After logging on the system, the operator can
browse through a list of available work orders and select one for
processing. The work order selection menu is programmed in FORTRAN using
our equally useful tools, FMS and LIB$FIND_FILE
.
The work order selection program performs two functions. First, it scans
the available work order files and extracts the information needed for
display (input tape ID's, output product media, etc.). Second, it displays
this information on the terminal screen and lets the operator initiate the
processing of any one of the work orders. F$SEARCH
immediately comes to mind as the natural choice for implementing the file
scan. A straightforward solution, yes, but some additional twists in the
problem lead us to more fully exploit the capabilities of
LIB$FIND_FILE
. (The second function of the work order
selection program is handled, of course, by FMS.)
Work orders are of two types: data screening (DS) work orders for
preliminary examination of satellite imaging passes and bulk/precision (BP)
work orders for generation of corrected scenes. Each type of work order is
further subdivided into normal production work orders (DSWO and BPWO),
special engineering work orders (DSWE and BPWE), and standard line test
work orders (DLWE and ILWE; used for system test and verification). Work
order file names are constructed from the work order type, the year and day
the work order was generated, and a sequence number:
DSWO8712301.DAT
, BPWE8824615.DAT
, etc. Available
work orders have a .DAT
extension; when a work order is
processed, the extension is changed to the node name of the computer on
which the work order is run. All work orders of all types are stored in a
single directory.
The work order selection program is invoked as a DCL foreign command and accepts a list of file specifications for the type of work order the operator wishes to process. To display data screening work orders, the following DCL command procedure is executed:
$ SELECT_WO = "$ R$SRIMAGES:XA_WO_SELECTION.EXE" $ DEFINE/USER_MODE SYS$INPUT: SYS$COMMAND: $ $ SELECT_WO R$WORKORDER:DSWO*.DAT, DSWE*.DAT, DLW*.DAT
Bulk/precision work orders are displayed with:
$ SELECT_WO R$WORKORDER:BPWO*.DAT, BPWE*.DAT, - SDWO*.DAT, SDWE*.DAT, - ILW*.DAT, RCW*.DAT
(The SDW*
and RCW*
work orders are special
calibration runs.) After the operator has selected a work order, the
selection program returns the chosen work order file name in a DCL symbol,
which is, in turn, passed to the appropriate DS or BP processing package.
Back in the days of mercury delay lines and magnetic drums, work order
selection was probably accomplished by performing a DCL
DIRECTORY/OUTPUT=...
command and then executing the selection
program; the program would read the directory listing file to determine the
available work order files. The code fragment in
Program 2 achieves the same effect more quickly and
more concisely.
Program 2 gets one or more file specifications from
the command line and builds a list of all the work order files that match
the wildcard file specifications. In the code,
LIB$GET_FOREIGN
gets the command line from a DCL foreign
command (i.e., SELECT_WO
). For example, the command line for
data screening work orders is returned as:
R$WORKORDER:DSWO*.DAT, DSWE*.DAT, DLW*.DAT
Subroutine GETWORD
(see Program 3)
extracts the next "word" in a string - in this case, wildcard file
specifications delimited by commas, spaces, or tabs. (GETWORD
is similar to, but more powerful than, DCL lexical function
F$ELEMENT
.) Each wildcard file specification is scanned by
LIB$FIND_FILE
and the set of matched files are added to the
list of available work orders. The work order selection program goes on to
read selected information from each file and to display that information on
the operator's screen.
Note the need for "sticky" defaults. R$WORKORDER:
in the
command line is meant to apply to each of the wildcard file specifications.
A seemingly attractive solution to this problem would be the use of search
list logical names, i.e., assign one logical name to the entire list of
wildcards and call the work order selection program:
$ DEFINE FILES "R$WORKORDER:DSWO*.DAT,DSWE*.DAT,DLW*.DAT" $ SELECT_WO FILES
This approach would make the code in Program 2 even
simpler, requiring only a single loop and no calls to GETWORD
.
Unfortunately, as I mentioned before, temporary defaulting does not work
properly for search list logical names. So we are forced to process the
wildcards one at a time, setting the MULTIPLE
flag to save the
search context from one wildcard specification to the next. Still, the
program code and surrounding DCL are considerably less complex than that
required by the DIRECTORY/OUTPUT=...
dinosaur.
Multiple search streams are another useful capability of
F$SEARCH
. As in many companies, our operational software is
maintained under configuration. Corrections and enhancements are made by
checking out the affected program, making the necessary changes to the
source code, and resubmitting the changed modules only to Configuration
Management (CM). When working with hundreds of modules, it is easy to
forget which routines you changed and which ones you didn't. Enter the
VERIFY program (it's called VERIFY because it's also used to
verify that your changes got into CM okay - SW personnel have a natural
distrust of CM personnel!). VERIFY provides a 2-column listing of
selected files in a programmer's personal directory and the corresponding
files in a CM directory; files that have been updated by the programmer are
flagged with an asterisk.
When I make changes to the work order selection program, for instance, I copy
the source code into [AMEASDAY.WOSEL]
, make the changes, run:
$ VERIFY *.FOR X$WOSELECT:
and out comes the list of updated files that I need to submit.
VERIFY uses F$PARSE
to append the first file
specification (*.FOR
) to the CM directory logical name
(X$WOSELECT:
). Then, two searches are conducted in parallel
of my *.FOR
and CM's X$WOSELECT:*.FOR
.
VERIFY compares file creation dates and times to determine if a file
has been updated. (Creation dates and times are obtained using - you
guessed it - a FORTRAN version of DCL lexical function
F$FILE_ATTRIBUTES
.)
As VAX/VMS increases in age and versions, so do the lengths of file names.
The large maximum length of files names under VMS version 4 is a mixed
blessing; the benefit of more meaningful file names must be weighed against
the tediousness of typing in these long file names. Would you rather edit
GET_SATELLITE_TELEMETRY.FOR
or GETTLM.FOR
? The
image display utility described earlier is faced with monstrosities like:
SX_WORK:SX_DSWO87146001_MICROFICHE.DAT
VMS's new EVE editor, programmed via the Text Processing Utility (TPU),
makes a step in the right direction by allowing wildcards in file names, as
long as there are no ambiguities. If the wildcard file specification
matches more than one file, the user is prompted to enter more information
(i.e., so that GET*.FOR
can differentiate between
GET_SATELLITE_IMAGERY.FOR
and
GET_SATELLITE_TELEMETRY.FOR
). This can be frustrating after
several iterations trying to locate one lousy file! (Incidentally,
F$PARSE
and F$SEARCH
are available in TPU as
FILE_PARSE
and FILE_SEARCH
, respectively.)
Is there a better way? Yes! We can type in $ E GET*.FOR
to
edit GET_SATELLITE_IMAGERY.FOR
and we can type in
SX_WORK:*HE.DAT
to display microfiche imagery. We can do
these things using any editor or from any program. The key is to look at
F$SEARCH
in a different light, from a viewpoint I have dubbed
LIB$FIND_FIRST_FILE
. Think of F$SEARCH
, not as a
utility that scans sets of file names, but as a function that returns the
first file name that matches a wildcard specification.
Editing files is probably the most common activity on computers, one in
which ease of use is of prime importance. Program 4 presents a DCL command
procedure, OMNIEDIT.COM
, that allows us to invoke any editor
using a wildcard input file name. (OMNIEDIT.COM
assumes
certain conventions in the naming of editor initialization files, i.e.,
EDTini.EDT
, EVEfor.EVE
, etc.) So that you don't
have to type in 20 extra characters to save 5, define these shorthand
editor symbols in your login file:
$ E == "@ALEX:OMNIEDIT.COM TPU INI WRITE" $ EF == "@ALEX:OMNIEDIT.COM EDT FOR WRITE" $ ER == "@ALEX:OMNIEDIT.COM TPU INI READ"
A FORTRAN file is then edited as follows:
$ EF GET*.FOR Editing DISK$T2B0:[AMEASDAY.PROGRAM]GET_SATELLITE_IMAGERY.FOR;3 ... the EDT editor comes up ...
To recover an aborted editing session:
$ E *.DAT /RECOVER Editing DISK$T2B0:[AMEASDAY]JUNK.DAT;1 ... the EVE editor comes up and runs through its recovery process ...
The basic idea behind OMNIEDIT.COM
is simply to
"F$SEARCH
" the input file name and pass the fully-qualified,
"first" file specification to the desired editor. Insert an
F$PARSE
before the F$SEARCH
to provide default
file extensions and the possibilities become endless. To compile FORTRAN
routines, create a command procedure, COMPILE.COM
:
! Compile FORTRAN Programs - ! P1 = source file name ! P2 = optional command qualifiers $ P1 = F$PARSE (P1, ".FOR") ! Append default extension. $ SOURCE_FILE = F$SEARCH (P1) ! Parse wildcard specification. $ WRITE SYS$OUTPUT "Compiling ''SOURCE_FILE'" $ FORTRAN'P2' 'SOURCE_FILE'
With the appropriate symbol defined, compiling is as simple as
F GET*
. In a like manner, you can define commands such as
L *
to automatically search for an options file and
LINK
a program, or a DOWN *
command that sets
your default to a wildcard subdirectory, and so on. Use your imagination!
While enclosing system tools like the editor in a DCL shell is no problem,
coupling a non-system program to a DCL command procedure is an awkward
mechanism for implementing LIB$FIND_FIRST_FILE
. Everywhere the
program goes, it has to drag along its DCL. A much better solution is to
have the program do its own file searching. One example, presented earlier
in the discussion of F$PARSE
, is the code that processes the
base file name for the image display utility.
Another example is a program that dumps LANDSAT telemetry data. The telemetry data is grouped in 16,384-byte packets called major frames (MJF). Each major frame is divided into 128 minor frames (MNF); a minor frame is made up of 128 8-bit "words". The program dumps 3 items of information for each MNF: the MNF ID (0-127), MNF word 0 (overwritten by a status byte from our telemetry extraction hardware), and user-selected MNF word N. Foreign DCL command DUMPTLM dumps a telemetry file:
$ DUMPTLM file_name MNF_word listing_file
Program 5 is the fragment of code that parses the
command line. The input file name can contain wildcards - a necessity, not
a luxury, for SX_WORK:SX_DSWO87146001_TELEMETRY.DAT
and its
ilk. If not specified, the MNF word defaults to 72 (word 72 contains
timecode information for the MJF). If no listing file is specified, the
telemetry data is displayed on the user's terminal.
From the simple substitution of file extensions by F$PARSE
to
the powerful parsing of wildcards by F$SEARCH
, we have learned
of the treasures buried in VAX/VMS and of the importance of seeking them
out. The ability to provide elegant solutions to practical problems is not
the exclusive province of F$PARSE
and F$SEARCH
.
Numerous other functions of the VMS operating system, from low-level system
services to high-level utilities, are at the service of the computer user.
Idle moments spent browsing through VAX/VMS documentation are amply repaid
in time and effort saved.
C**************************************************************************** INTEGER*4 FUNCTION f$parse (file_spec, default_spec, + related_spec, field, parse_type, + result) C**************************************************************************** C C Function F$PARSE emulates the DCL lexical function, "F$PARSE", C using the RMS SYS$PARSE service. Given a file specification, C F$PARSE will expand the file specification, translating logical C names and filling in defaults for missing fields. The fully- C expanded file specification, or parts of it if specified, are C returned to the calling routine. See the section on lexical C functions in the VAX/VMS DCL Dictionary for more information C about "F$PARSE". C C NOTE that although the input arguments are declared as records C of type descriptor, you should pass them in as CHARACTER strings. C This was necessary to allow F$PARSE to detect null arguments. C The descriptor type chosen, "DSCDEF1", was as close as I could C get to a character string descriptor type. C C NOTE also that there is a problem in either the FORTRAN compiler C or the Linker of VMS Version 4.2, related to argument type checking. C When passing a character string variable to F$PARSE, everything C works fine. When passing a character string literal to F$PARSE C (or to any routine), strange things happen. If the argument is C declared of type CHARACTER in the called subroutine, the string C literal is passed by descriptor. Good. If the argument is C declared to be of a non-CHARACTER type, the address of the text C is passed (essentially "%REF (literal)"). Aaagh! Until this C bug is fixed, be careful - specify "%DESCR (...)" explicitly when C passing string literals to a subroutine like F$PARSE. C C NOTE that you better appreciate the work that went into this C routine, aside from debugging the literal string bug above. C There is no clear information about how to use SYS$PARSE in C the VAX/VMS RMS Reference Manual. I had the routine partially C working when I finally found some information in the Guide to C VAX/VMS File Applications, Chapter 5, "Advanced Use of File C Specifications". C C C Arguments (see the NOTE above about arguments of type CHARACTER): C C FILE_SPEC (Character) C C The primary file specification being parsed. This C argument may be null (unlike the DCL function where C it is required); if not specified, the default and C related file specifications are applied as usual to C a blank, primary file name. C C DEFAULT_SPEC (Character) C C The default file specification. If the default file C specification is specified (this argument may be C null), it is "applied" to the expanded, primary file C specification (see the PURPOSE section above). C C RELATED_SPEC (Character) C C The related file specification. If the related file C specification is specified (this argument may be C null), it is "applied" to the expanded, primary/default C file specification (see the PURPOSE section above). C C FIELD (Character) C C Designates the field in the expanded file C specification that you want returned to you: C C "NODE" C "DEVICE" C "DIRECTORY" C "NAME" C "TYPE" C "VERSION" C C If this argument is null (or is an invalid field C name), the entire, fully-expanded file specification C is returned to the calling routine. C C PARSE_TYPE (Character) C C Specifies the type of parse to perform. There are C two options: C C "NO_CONCEAL" reveals concealed logical names. C C "SYNTAX_ONLY" does not verify that the device C and directory in the fully-expanded file C specification actually exist. C C RESULT (Character) C C Returns the parsed file specification. If a desired C field is not specified (null FIELD argument), the C entire, fully-expanded file specification is returned C to the calling routine. Otherwise, only the specified C field is returned to the calling routine: C C "NODE" returns "node::" (usually null) C "DEVICE" returns "disk:" C "DIRECTORY" returns "[directory]" C "NAME" returns "file name" C "TYPE" returns ".extension" C "VERSION" returns ";number" (";" if no C version number) C F$PARSE (Function Value) C C Returns the VAX/VMS status code returned by SYS$PARSE. C There are actually two calls to SYS$PARSE, one to C parse the related file specification and a second to C parse the primary file specification. An error in C either parse operation is immediately returned to C the calling routine. A disk or directory not found C error could be returned if you didn't specify C "SYNTAX_ONLY". RMS$_NORMAL is returned if there C are no errors. C C**************************************************************************** IMPLICIT NONE C... Parameters and external definitions. INCLUDE '($DSCDEF)' ! Descriptor type definitions. INCLUDE '($FABDEF)' ! RMS File Access Block (FAB) and INCLUDE '($NAMDEF)' ! name block field definitions. INCLUDE '($SYSSRVNAM)' ! VMS system service entry points. C... Subroutine arguments. RECORD /DSCDEF1/ file_spec ! Character string. RECORD /DSCDEF1/ default_spec ! Character string. RECORD /DSCDEF1/ related_spec ! Character string. RECORD /DSCDEF1/ field ! Character string. RECORD /DSCDEF1/ parse_type ! Character string. CHARACTER*(*) result C... Local variables. CHARACTER*16 field_name, type_of_parse CHARACTER*(NAM$C_MAXRSS) expanded_string, rlf_expanded_string INTEGER*4 length, nop, status RECORD /FABDEF/ fab ! RMS file access block. RECORD /NAMDEF/ nam ! RMS name block. RECORD /NAMDEF/ rlf ! RMS related file name block. result = ' ' ! Return blank result in case of error. C... Copy the field name and parse type strings into local C character strings. IF (%LOC (field) .EQ. 0) THEN field_name = ' ' ELSE CALL LIB$MOVC5 (field.DSC$W_MAXSTRLEN, + %VAL(field.DSC$A_POINTER), %REF(' '), + LEN(field_name), %REF(field_name)) ENDIF IF (%LOC (parse_type) .EQ. 0) THEN type_of_parse = ' ' ELSE CALL LIB$MOVC5 (parse_type.DSC$W_MAXSTRLEN, + %VAL(parse_type.DSC$A_POINTER), %REF(' '), + LEN(type_of_parse), %REF(type_of_parse)) ENDIF C... Parse the related file specification. Do not expand concealed C logical names and don't verify that its directory exists. IF (%LOC (related_spec) .NE. 0) THEN CALL LIB$MOVC5 (0, 0, 0, FAB$C_BLN, fab) ! Zero all fields. fab.FAB$B_BID = FAB$C_BID ! Block identifier. fab.FAB$B_BLN = FAB$C_BLN ! Block length. fab.FAB$L_FOP = FAB$M_NAM ! File-proc. options. fab.FAB$L_NAM = %LOC (rlf) ! Name block address. fab.FAB$L_FNA = related_spec.DSC$A_POINTER ! File name. CALL LIB$MOVC3 (1, related_spec.DSC$W_MAXSTRLEN, + fab.FAB$B_FNS) CALL LIB$MOVC5 (0, 0, 0, NAM$C_BLN, rlf) ! Zero all fields. rlf.NAM$B_BID = NAM$C_BID ! Block identifier. rlf.NAM$B_BLN = NAM$C_BLN ! Block length. rlf.NAM$B_NOP = NAM$M_SYNCHK ! Name block options. rlf.NAM$L_ESA = %LOC (rlf_expanded_string) ! Expanded string. rlf.NAM$B_ESS = NAM$C_MAXRSS status = SYS$PARSE (fab) IF (.NOT. status) THEN f$parse = status RETURN ENDIF rlf.NAM$L_RSA = rlf.NAM$L_ESA ! Resultant string. rlf.NAM$B_RSL = rlf.NAM$B_ESL ENDIF C... Parse the main file specification, applying the default and C related file specifications. CALL LIB$MOVC5 (0, 0, 0, FAB$C_BLN, fab) ! Zero all fields. fab.FAB$B_BID = FAB$C_BID ! Block identifier. fab.FAB$B_BLN = FAB$C_BLN ! Block length. fab.FAB$L_FOP = FAB$M_NAM ! File-proc. options. fab.FAB$L_NAM = %LOC (nam) ! Name block address. IF (%LOC (file_spec) .NE. 0) THEN fab.FAB$L_FNA = file_spec.DSC$A_POINTER ! File name. CALL LIB$MOVC3 (1, file_spec.DSC$W_MAXSTRLEN, fab.FAB$B_FNS) ENDIF IF (%LOC (default_spec) .NE. 0) THEN fab.FAB$L_DNA = default_spec.DSC$A_POINTER ! Default file spec. CALL LIB$MOVC3 (1, default_spec.DSC$W_MAXSTRLEN, + fab.FAB$B_DNS) ENDIF CALL LIB$MOVC5 (0, 0, 0, NAM$C_BLN, nam) ! Zero all fields. nam.NAM$B_BID = NAM$C_BID ! Block identifier. nam.NAM$B_BLN = NAM$C_BLN ! Block length. IF (type_of_parse .EQ. 'NO_CONCEAL') THEN ! Expand concealed nam.NAM$B_NOP = NAM$M_NOCONCEAL ! logical names. ELSEIF (type_of_parse .EQ. 'SYNTAX_ONLY') THEN ! Don't verify that nam.NAM$B_NOP = NAM$M_SYNCHK ! directory exists. ENDIF IF (%LOC (related_spec) .NE. 0) ! Related file name + nam.NAM$L_RLF = %LOC (rlf) ! block address. nam.NAM$L_ESA = %LOC (expanded_string) ! Expanded string. nam.NAM$B_ESS = NAM$C_MAXRSS status = SYS$PARSE (fab) IF (.NOT. status) THEN f$parse = status RETURN ENDIF C... Return the desired field. The completely expanded file C specification is stored by RMS in EXPANDED_STRING; the lengths C and addresses of the individual fields are available in the name C block. IF (field_name .EQ. 'NODE') THEN length = ZEXT (nam.NAM$B_NODE) CALL LIB$MOVC5 (length, %VAL(nam.NAM$L_NODE), %REF(' '), + LEN(result), %REF(result)) ELSEIF (field_name .EQ. 'DEVICE') THEN length = ZEXT (nam.NAM$B_DEV) CALL LIB$MOVC5 (length, %VAL(nam.NAM$L_DEV), %REF(' '), + LEN(result), %REF(result)) ELSEIF (field_name .EQ. 'DIRECTORY') THEN length = ZEXT (nam.NAM$B_DIR) CALL LIB$MOVC5 (length, %VAL(nam.NAM$L_DIR), %REF(' '), + LEN(result), %REF(result)) ELSEIF (field_name .EQ. 'NAME') THEN length = ZEXT (nam.NAM$B_NAME) CALL LIB$MOVC5 (length, %VAL(nam.NAM$L_NAME), %REF(' '), + LEN(result), %REF(result)) ELSEIF (field_name .EQ. 'TYPE') THEN length = ZEXT (nam.NAM$B_TYPE) CALL LIB$MOVC5 (length, %VAL(nam.NAM$L_TYPE), %REF(' '), + LEN(result), %REF(result)) ELSEIF (field_name .EQ. 'VERSION') THEN length = ZEXT (nam.NAM$B_VER) CALL LIB$MOVC5 (length, %VAL(nam.NAM$L_VER), %REF(' '), + LEN(result), %REF(result)) ELSE ! Return the complete file specification. length = ZEXT (nam.NAM$B_ESL) result = EXPANDED_STRING(1:length) ENDIF f$parse = status RETURN END
INCLUDE '($SSDEF)' ! System status codes. INTEGER*4 LIB$FIND_FILE ! External function. PARAMETER P_MULTIPLE = '00000002'X ! Flag causing LIB$FILE_FIND ! to use previous context for ! processing multiple ! wildcard specifications. INTEGER*4 num_file_names ! Files names from wildcard ! searches. CHARACTER*128 file_name(P_MAX_WORK_ORDERS) CHARACTER*(*) delimiters*4, command_line*255 CHARACTER*(*) file_spec*128, wildcard_spec*128 INTEGER*4 context, first, last, length, start, status C... Get the wildcard file specifications from the command line C and build a list of matching file names. CALL LIB$GET_FOREIGN (command_line, 'fileSpec ...? ') context = 0 delimiters = ' ,' // CHAR (9) num_file_names = 0 start = 1 CALL GETWORD (command_line, start, delimiters, + first, last, length) DO WHILE (length .GT. 0) ! For each wildcard spec ... wildcard_spec = command_line(first:last) status = SS$_NORMAL DO WHILE (status) ! For each matching file ... status = LIB$FIND_FILE (wildcard_spec, file_spec, + context, , , , P_MULTIPLE) IF (status) THEN num_file_names = num_file_names + 1 file_name(num_file_names) = file_spec ENDIF ENDDO start = last + 1 CALL GETWORD (command_line, start, delimiters, + first, last, length) ENDDO CALL LIB$FIND_FILE_END (context)
C**************************************************************************** SUBROUTINE GETWORD (string, start, delimiters, + first, last, length) C**************************************************************************** C C Subroutine GETWORD gets the next "word" from a string of text. C A "word" is a string of characters delimited on each side by any C character which is in a set of specified delimiters or by the end C of the string. C C A "word" has the following syntax (< > means optional): C C <delimiters> non-delimiters delimiters|end-of-string C C GETWORD returns the length and location of the next "word" to the C calling routine. If a "word" is not found or an error condition C is detected, GETWORD returns a null string (length = 0) located C one character position past the end of the input string. C C C Arguments: C C STRING (Character) C The character string to be scanned. C C START (Integer) C The position (1..n) within the string from which the C scan is to start. This argument is checked for C validity (including an implied check that the length C of the input string is greater than zero). C C DELIMITERS (Character) C A character string, each character of which is to be C considered a word delimiter; the beginning and the C end of the input string are implied delimiters. C C FIRST (Integer) C Returns the position (1..n) within the string at C which the next "word" begins; equals the length of C STRING plus one if no next "word" is found. C C LAST (Integer) C Returns the position (1..n) within the string at C which the next "word" ends; equals FIRST - 1 if no C next "word" is found. C C LENGTH (Integer) C The length in characters of the next "word"; equals C zero if no next "word" is found or START is invalid. C C**************************************************************************** IMPLICIT NONE C... Parameters and external definitions. INTEGER*4 STR$FIND_FIRST_IN_SET ! External routines. INTEGER*4 STR$FIND_FIRST_NOT_IN_SET C... Subroutine arguments. CHARACTER*(*) string, delimiters INTEGER*4 start, first, last, length C... Local variables. INTEGER*4 i C... Get the length of the input string and set up the first and C last pointers as if the next word were NOT found. length = LEN (string) ! Length of input string. first = length + 1 last = first - 1 C... If the start position is invalid, then return "Not Found". IF ((start .LT. 1) .OR. (length .LT. start)) THEN length = 0 RETURN ENDIF C... Skip delimiters to reach the start of the next "word". i = STR$FIND_FIRST_NOT_IN_SET (string(start:), delimiters) IF (i .GT. 0) THEN first = start + I - 1 ! Start of word found. ELSE length = 0 ! End of string. RETURN ENDIF C... Skip non-delimiters to reach the end of the "word". i = STR$FIND_FIRST_IN_SET (string(first:), delimiters) IF (i .GT. 0) THEN last = first + i - 2 ! End of word found. ELSE last = LEN (string) ! End of string. ENDIF RETURN END
!**************************************************************************** ! ! OMNIEDIT.COM - Omniscient Editor ! ! Arguments: ! P1 = Editor: EDT, TPU (EVE) ! P2 = File type: INI (regular), FOR (Fortran), ! MAC (Macro), WID (132 columns) ! P3 = Edit type: WRITE, READ (read-only) ! P4 = File name ! P5 = Additional command qualifiers ! !**************************************************************************** $ $ $ IF P1 .EQS. "EDT" THEN EDIT_COMMAND = "EDIT/EDT/COMMAND=ALEX:EDT''P2'.EDT" $ IF P1 .EQS. "TPU" THEN EDIT_COMMAND = "EDIT/TPU/SECTION=ALEX:EVE''P2'.EVE" $ $ IF P3 .EQS. "READ" THEN EDIT_COMMAND = EDIT_COMMAND + "/READONLY" $ $ EDIT_COMMAND = EDIT_COMMAND + P5 ! Additional command qualifiers. $ $ FILE_NAME = F$SEARCH (P4) ! Parse wildcard specification. $ IF FILE_NAME .EQS. "" THEN FILE_NAME = P4 $ $ WRITE SYS$OUTPUT "Editing ''FILE_NAME'" $ $ DEFINE/USER_MODE SYS$INPUT: SYS$COMMAND: $ 'EDIT_COMMAND' 'FILE_NAME'
C... Local variables. CHARACTER*128 command, input_file, output_file INTEGER*4 alternate, context, first, ierr, last, length, start C... Get the file names and alternate telemetry word from C the command line. CALL LIB$GET_FOREIGN (command, + 'file_name <telemetry_word> <dump_file>? ', ) start = 1 CALL getword (command, start, ' ', first, last, length) context = 0 CALL LIB$FIND_FILE (command(first:last), input_file, context) start = last + 1 CALL getword (command, start, ' ', first, last, length) IF (length .GT. 0) THEN READ (command(first:last), *, IOSTAT=ierr) alternate ELSE alternate = 72 ENDIF start = last + 1 CALL getword (command, start, ' ', first, last, length) IF (length .GT. 0) THEN output_file = command(first:last) ELSE output_file = 'SYS$OUTPUT' ENDIF