↑ Writing ↑

GEONius.com
28-May-2003
 E-mail 

F$USEFUL

A Look at Some Mundane VMS Utilities

Published in VAX Professional, April 1988



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.

 

F$PARSE

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
    CALL F$PARSE (new_file_name, old_file_name, , , , base_file_name)
    new_file_name = base_file_name
    context = 0
    CALL LIB$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.

 

F$SEARCH

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.)

 

F$PRICELESS

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.

 

$ ON END THEN EXIT

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.


 

Program 1: Program-Callable F$PARSE

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

 

Program 2: Work Order Selection Program (Wildcard Processing)

      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)

 

Program 3: Subroutine GETWORD

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

 

Program 4: OMNIEDIT.COM (Editor Shell)

!****************************************************************************
!
!	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'

 

Program 5: DUMPTLM Program (Parsing of Command Line)


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

©1987  /  Charles A. Measday  /  E-mail