Some Notes on Programming in DCL

      1. General Notes
      2. Input Parameters
      3. Passing Arguments to Programs
      4. Asking the User for Input
      5. Program Flow
      6. System "Files" and Their Redirection
      7. File I/O
      8. Symbols
      9. Error Handling
      10. Subroutines - GOSUB
      11. Subroutines - The CALL Command
      12. Date/Time Formats
      13. Faking a Case Statement
      14. Faking an Array
      15. A Few of the Lexical Functions

      1. General Notes

      First of all, you have to be aware that DCL lacks quite a bit, and for a lot of things you either have to find some goofy workaround or else switch to a real programming language. For example, it can only deal with integers - floating point numbers are not allowed; you cannot create functions (the only functions are the closed set of lexicals); you cannot use the output from one command as input for another command (you have to use a file as intermediary); you cannot always catch command output (for instance, the statistics produced by SEARCH/STATISTICS), and who knows what else.

      Integers are four bytes in length, but the top bit is used for signing, so the range of numbers available is runs from positive to negative 231 - 1.

      DCL script files have the .COM extension and are launched by prepending @. For example, CLEANUP.COM is run by typing @CLEANUP. These files are called "command procedures". All lines begin with a dollar sign; any text following an exclamation point is taken as a comment.

      Debug by typing SET VERIFY. This echos all commands to the screen so you can see where it's going or where it's hung up.

      You can halt a program by typing Ctrl/Y. At that point you can enter commands like SET [NO]VERIFY, STOP, EXIT, CONTINUE.

      You can enable/disable Ctrl/Y with the SET [NO]CONTROL=Y command. The scope of this command is global for the user. You can also program what happens at Ctrl/Y with ON CONTROL_Y THEN command. The scope of that command is the current command level.

      The difference between STOP and EXIT is that STOP terminates the command procedure completely, while EXIT terminates the current command level and returns to the level above. Also, EXIT can return a status code.

      You can nest up to 32 command levels. There are two ways to create a new command level: one is to run a command procedure (with the @ symbol and a filename); the other is by using the CALL command to execute a subroutine within a command procedure.

      To write a command on multiple lines, end each line (except the last) with a hyphen.

      Wild-card characters:

      * matches any number of characters, including none
      % matches exactly one character
      ... can be used in directory spec; matches the directory subtree

      Address format:

      node::device:[directory.subdirectory...]name.type;version
      
      2. Input Parameters

      If a parameter contains spaces, commas, slashes, or lowercase letters, it must be enclosed in double quotes. Null values are represented by an empty pair of double quotes ("").

      Parameters are separated by spaces or tabs. Lists of items are separated by commas.

      You can pass up to eight parameters to a command procedure. They are assigned to the symbols P1 to P8. Each new command level has its own set of P1-P8 symbols. If you do not assign a value, they are given null values. In the following example, P8 will be set to null:

      @sum 34 52 664 89 2 72 87

      To pass the value of a symbol, enclose it in single quotes ('P1'). To preserve spaces, tabs, and lowercase characters in the symbol's value, enclose it in THREE sets of quotation marks.

      You also need three sets of quotation marks to include a quotation mark as part of a string.

      Some examples:

          $ NAME = "Paul Cramer"
      
          $ @DATA NAME			! P1 = NAME
          $ @DATA "NAME"			! P1 = NAME
          $ @DATA ""NAME""			! P1 = NAME
          $ @DATA """NAME"""			! P1 = "NAME"
          $ @DATA ''NAME'			! P1 = NAME
      
          $ @DATA 'NAME'			! P1 = PAUL; P2 = CRAMER
      
          $ @DATA "''NAME'"		        ! P1 = Paul Cramer	
      
      
      or
          $ NAME = """Paul Cramer"""
      
          $ @DATA  'NAME'			! P1 = Paul Cramer
          
      
      3. Passing Arguments to Programs

      (example: passing arguments to CENSUS.EXE)
          $ RUN CENSUS
          1992
          1993
          1994
          $ EXIT
      
      You can't pass symbols, logical names or arithmetic expressions.

      4. Asking the User for Input

      This command shows the user the prompt string (with a colon tacked on the end, and puts the user's input into the symbol.

      INQUIRE symbol "prompt"

      example: INQUIRE input_filename "Filename"

      In a batch job, you can pass these inquired-for values on the line following the inquire command. If you don't do that, it gets a null value.

      INQUIRE will do symbol substitution if the user precedes the symbol with an apostrophe. Leading and trailing whitespace is removed, and embedded whitespace is collapsed to a single space. Input is also converted to uppercase letters. It will not do the last two actions if the string is enclosed in double quotes.

      This command, on the other hand, just takes what the user types:

      READ SYS$COMMAND /PROMPT="prompt string" symbol

      For both of these commands, if the user just hits the <ENTER> key, the symbol is assigned the null value.

      5. Program Flow

      Plain old goto:
          $ GOTO label_name
      
          $ label_name:
      
      Conditional execution:
          IF expression THEN command
          
          IF expression THEN GOTO label
          
          IF expression THEN @some_other_procedure
          
          IF expression
          THEN command
          ELSE command
          ENDIF
          
          IF expression
          THEN
              command
              command
          ELSE
              command
              command
          ENDIF
      
      IF blocks can be nested, but cannot exceed 15 levels.

      You cannot label a THEN or ELSE statement, but you can label an ENDIF statement.

      Symbols are automatically substituted in IF expressions.

      TRUE: expressions that evaluate to TRUE:

      odd integers
      character representations of odd integers (e.g., "27")
      character strings beginning with Y, y, T, t

      FALSE: expression that evaluate to FALSE:

      even integers
      character representations of even integers
      character strings that do not begin with Y, y, T, t

      String comparison operators (case sensitive):

      .EQS., .NES., .LTS., .GTS., .GES., .LES.

      Numerical comparison operators:

      .EQ., .NE., .LT., .GT., .GE., .LE.

      And remember:

      .AND., .OR., .NOT.

      Example:
          $ INQUIRE CONT "Continue? [Y/N]"
          $ IF .NOT. CONT THEN EXIT
      
      6. System "Files" and Their Redirection

      SYS$COMMAND - usually the terminal
      SYS$INPUT - default input
      SYS$OUTPUT - default output
      SYS$ERROR - default error stream

      In ordinary use, these four are usually defined to be the terminal. In a command file, sys$input is the command procedure file.

      A note about "define/user_mode": user-mode definitions are deleted from the process table when any image executing in the process exits. In other words, if you redefine sys$input before running a command procedure, it gets reset when the procedure exits.

      To redefine sys$input to the terminal:

          define/user_mode sys$input sys$command
      
      In this way, you will be able to have interactive input from the user, for instance, if you want to start an edit session, or you want to run a program that asks for user input. (Again, once that program or command file exits, the sys$input redefinition is dropped.)

      You can also define sys$input as a separate file. It would be the same as the above example; while the next program runs, it pulls the text from the file, just as if it were typed on lines following the command, as in section 3. The redefinition is dropped automatically once the program or command file exits. So, assuming there is a file INPUT.DAT that contains three lines (1995, 1996, 1997), these two blocks of commands are equivalent (and you could substitute sys$command for the INPUT.DAT filename to get interactive input from the user):

          $ define/user_mode sys$input input.dat
          $ run census
      
          $ run census
          1995
          1996
          1997
      
      You can include a file in your command procedure. You do this by giving "sys$input" where you would normally give the file name; all lines up to the next line beginning with a dollar sign are taken as the file. Example:
          $ FORTRAN/OBJECT=TESTER/LIST=TESTER SYS$INPUT
          C THIS IS A TEST PROGRAM
            A = 1
            B = 2
            STOP
            END
          $ PRINT TESTER.LIS
          $ EXIT
      
      Rather than look for an external file, the fortran compiler takes the five lines as the file to compile.

      You can output data in a similar way:

          $ TYPE SYS$INPUT
              This is stuff I want the user to see on the screen.
              It can't contain symbols or functions; just straight text.
              It ends at the next line that begins with a dollar sign.
          $!
      
      Trick for writing to a file:
          $ TYPE /OUTPUT=file.name SYS$INPUT
          This is the stuff I want in the file.
          Otherwise I'd have to type it all
          $! input ends here
      
      If you need to output symbols or functions, use WRITE SYS$OUTPUT whatever. Enclose text strings in quotation marks; use doubled quotation marks to make quotation marks print. Symbols are substituted automatically; if you put a symbol in a string, you must enclose it in single quotes. Examples:
          $ WRITE SYS$OUTPUT "This is just text"
          $ WRITE SYS$OUTPUT "I want ""quotes"" around that word."
          $ WRITE SYS$OUTPUT filename
          $ WRITE SYS$OUTPUT "I deleted ''filename' today."
      
      You can redirect sys$output in a number of ways:

      - use the /OUTPUT qualifier on individual commands
      - suppress command output by setting sys$output to the null device (nl:)
      - you can set sys$output to a file

      Examples:
          $ DEFINE/USER_MODE SYS$OUTPUT SHOW_USER.DAT
          $ SHOW USERS
          
          $ DEFINE/USER_MODE SYS$OUTPUT NL:
      
      To reset sys$input, sys$output, etc. to their defaults, use the DEASSIGN command:
          $ DEASSIGN SYS$OUTPUT
      
      If you want to suppress the appearance of error messages, you have to redirect BOTH sys$output and sys$error.

      You can also suppress error messages with the command

          SET MESSAGE /[NO]FACILITY /[NO]IDENTIFICATION /[NO]SEVERITY /[NO]TEXT
      
      7. File I/O

      This deals with the commands open, close, write, and read.

      Note that each of these commands has the option /ERROR=label which causes the command procedure to go to the specified label if there is an error.

      When you open a file, you must assign a unique logical name to that file i/o stream, and this logical name is used for read/write/close commands.

      OPEN /option(s) logical_name filespec

      Example:
          OPEN/READ INFILE DISK4:[MURPHY]STATS.DAT
      
      The options available are:

      /READ
      open the file for readonly access and position the pointer at the beginning of the file

      /APPEND
      open the file for write-only access; all new records are added to the end of the file

      /WRITE
      create a new file for write-only access. If a file with the given filespec already exists, a new file is created with the next-higher version number.

      /READ/WRITE
      open an existing file for read/write access. Cannot be used to open a new file.

      /SHARE[=option]
      if option is READ, other users have read-only access; if there is no option specified, or the option is WRITE, other users have read/write access

      The CLOSE command only needs the logical name of the file:

      CLOSE logical_name

      The WRITE command works as explained above:

      WRITE logical_name whatever

      Symbol substitution is automatic; enclose text strings in quotation marks. Symbols in strings must be surrounded by single quotes. You can also use a comma to concatenate strings and symbols:
          WRITE OUTFILE "Count is ", COUNT, "."
      
      And look at this:
          $ COUNT = 4
          $ WRITE OUTFILE P'COUNT'
      
      It writes the contents of the variable P4.

      The write command has the options:

      /SYMBOL
      which is used if the record is longer than 1024 bytes or an expression in the write command is longer than 255 bytes

      /UPDATE
      which must be used to change an existing record in a file. The file must be opened with /read/write options, and the changed record must be the same length as the old record.

      The READ command reads a record and assigns its contents to a symbol:

      READ logical_name symbol

      as in:
          READ INFILE RECORD
      
      It has a limit of 1024 bytes.

      Some options for the READ command are listed below. You should check the online help for more information.

      /DELETE
      deletes the record after reading it. The file must have been opened with /READ/WRITE option.

      /END_OF_FILE=label
      goes to the specified label when the end of file is reached. If this option isn't used, the program goes to the label specified in the /ERROR option. If neither of these are specified, the current ON condition action is taken.

      8. Symbols

      Symbols come in three datatypes: string, integer, and boolean. Their type depends on what value is assigned.

      Symbols also have either local or global scope. Local symbols are assigned with a single equal sign; global symbols are assigned with a double equal sign.

          count = 52				! local
          dump  == "purge/nolog/noconfirm"	! global
      
      If the assigned value begins with a dollar sign not followed by a space, it means that it is an image that will be run when the symbol is typed.

      Local symbols belong to the command level on which they are created, and they are deleted when that command level terminates. Global symbols can be defined at any command level.

      When a symbol is referenced, its value is sought first on the current command level. If the symbol was not defined on this level, it searches the next higher level. It then searches the DCL prompt level, and finally looks among the global symbols. However, if the symbol was defined on a higher command level, its value cannot be changed on a lower command level, unless it is a global symbol.

      Different symbols with the same name can exist on different command levels. A good example is the group of symbols P1-P8.

      Symbols are deleted with the command DELETE/SYMBOL [/GLOBAL] symbol_name.

      Symbol substitution:

      Enclosing a symbol or lexical function in quotes causes it to be substituted:

          type 'file_spec'
          type 'f$environment("PROCEDURE")'
      
      When enclosed in string literals, they must be preceded by another quote:
          "The file ''file_spec' was typed."
      
      In fact, the trailing apostrophe can be omitted except:

      • when the character following could be taken as part of the symbol for example, an alphanumeric, a dollar sign...
      • when another apostrophe substitution immediately follows
      • when the substitution is the last thing in a string literal
      • when the character following is one of the following: '~%&{}\|
      • when the next character is a space followed by one of the following: #^)+=],?/>

      There is also ampersand substitution, but it is complicated and should be avoided. It works in a situation like WRITE SYS$OUTPUT &P'I' so that first the value of I is substituted (let's say it's 3) and then the value of P3 is substituted. If the ampersand wasn't there, the command would write the two letters "P3" to the screen.

      Substring assignment:

          string_variable[index,length] := "whatever"		! local
          string_variable[index,length] :== "whatever"	! global
      
      "index" begins at zero. If the given index is greater than the length of the string, the gap is filled with spaces. if substring is shorter than the length, it is padded on the right; if substring is longer than the length, it is truncated.

      Substring subtract:

      While the plus sign concatenates, the minus sign removes the leftmost occurrence of a string:

          blob = "why-oh-why-oh"
          blob = blob - "-oh"
          ! blob now equals "why-why-oh"
      
      Bit-field assignment:

      First bit is zero (duh!). hex is represented by %X0A1D, for instance.

          symbol[position,size] = expression		! local
          symbol[position,size] == expression		! global
      
      9. Error Handling

      $STATUS contains value returned by last procedure executed. $SEVERITY shows how bad the error (if any) was.

      The EXIT and RETURN command can both be used to set $STATUS and $SEVERITY.

      To check the value of $STATUS or $SEVERITY, you can do stuff like:

          IF $STATUS THEN command
          IF .NOT. $STATUS THEN command
      
      and the command will be executed if $STATUS is true (odd) in the first case, and if $STATUS is false (even) in the second.

      $STATUS:

          bits 0-2  - severity level of message (this makes it odd or even)
               3-15 - message number
              16-27 - facility number that generated the message
              28-31 - internal control flags
      
      This will show you the associated message:
          write sys$output f$message($status)
      
      $SEVERITY:
          bits 0-3 : condition code
      
      values: 0 = warning, 1 = success, 2 = error, 3 = info, 4 = fatal error (note that good values are odd, which means true).

      The ON command checks $STATUS and $SEVERITY. It is executed only once; its scope is within a command level. That is, a nested procedure may have a different ON command than the level above it.

      ON condition THEN [$] command

      condition:

      WARNING
      command is executed for warning, error, or severe_error
      ERROR
      command executed for for error or severe_error; continues in case of warning
      SEVERE_ERROR
      command executed for severe (fatal) error; continues in case of warning or error

      examples:
          ON WARNING THEN EXIT
          ON ERROR THEN GOTO ERR1
          ON WARNING THEN CONTINUE
      
      SET NOON is a command that stops error checking. The command interpreter continues to put values in $STATUS and $SEVERITY, but doesn't check them. You can explicitly check if you like.

      SET ON turns error checking back on.

      SET [NO]ON applies only at current command level.

      You can also suppress error messages with the command

          SET MESSAGE /[NO]FACILITY /[NO]IDENTIFICATION /[NO]SEVERITY /[NO]TEXT
      
      10. Subroutines - GOSUB

      Subroutines start with an ordinary label

          $ SUBROUTINE_ONE:
      
      they end with a RETURN command, and they are called via the GOSUB command. No parameters can be passed.

      EXAMPLE:

          $ SHOW TIME
          $ GOSUB TEST1
          $ WRITE SYS$OUTPUT "Success!"
          $ SHOW TIME
          $ EXIT
          $!
          $!
          $ TEST1:
          $   WRITE SYS$OUTPUT "Down in the subroutine"
          $   RETURN %X1
      
      Note that there is an EXIT command before the subroutine begins; otherwise you end up executing it again before leaving the procedure.

      Also, RETURN without a parameter returns success. RETURN writes values in $STATUS and $SEVERITY.

      This type of subroutine is on the same program level as the rest of the command procedure, so the subroutine has access to all the symbols and settings made up to the point that it's called.

      11. Subroutines - The CALL Command

      The CALL command creates a new command level. It transfers control to a subroutine in the same file from which it is called.

      You can pass up to eight parameters (p1-p8), just as described in section 2.

      The CALL command has an optional /OUTPUT=filename qualifier that lets you redirect sys$output to a file. It uses .LIS as the default filetype for these output files.

          CALL [/OUTPUT=output_file] subroutine_label [parameters]
      

      The SUBROUTINE and ENDSUBROUTINE commands begin and end a CALL subroutine. You can put an EXIT command before the ENDSUBROUTINE command, but it is not needed. ENDSUBROUTINE terminates the subroutine. Command lines within a subroutine are executed only when called by a CALL command. Every subroutine must end in an ENDSUBROUTINE command.

      Example:

          $ FILE_ROLLER:  SUBROUTINE
          $ command
          $ command
          $ ENDSUBROUTINE
      
      Subroutine entry points (labels) that are defined within another subroutine are local to that subroutine. They cannot be called from outside the subroutine. In other words, if you define subroutine B within subroutine A, you can only call subroutine B from inside subroutine A; nowhere else.

      Likewise, labels defined in subroutines are only available within that subroutine.

      If a subroutine is defined within an IF block, you cannot call it from outside that IF block. (why would you do it, anyway?)

      12. Date/Time Formats

      There are four named date/time formats.

      Absolute timedd-mon-yyyy hh:mm:ss.cc Unambiguous expression of date/time.

      There are three special absolute times: TODAY, TOMORROW, YESTERDAY (times for all three are midnight).

      Comparison timeyyyy-mm-dd hh:mm:ss.cc Allows easy comparison of date/times.
      Delta time +[dddd-][hh:mm:ss.cc]
      -[dddd-][hh:mm:ss.cc]
      Expresses the difference between two absolute times.

      You can truncate delta time on the right.

      If you specify days, you must include the hyphen.

      You can omit values for fields as long as you include the punctuation. Default for omitted fields is zero.

      Combination timeabsolute+dddd-hh:mm:ss.cc
      absolute-dddd-hh:mm:ss.cc
      An absolute and delta time used together. Delta is added or subtracted from absolute. Usually occurs within double quotes: "TOMORROW+30-" means 30 days after tomorrow.

      13. Faking a case statement

          $ CMD_LIST = "/PURGE/DELETE/EXIT/"
          $ INQUIRE COMMAND "Command (PURGE, DELETE, EXIT)"
          $ IF F$LOCATE ("/"+COMMAND+"/", CMD_LIST) .EQ. F$LENGTH(CMD_LIST) -
            THEN GOTO INVALID_INPUT
          $ GOTO 'COMMAND'
          $ etc
          $ PURGE:
          $	whatever
          $	GOTO END_LABEL
          $ DELETE:
          $ etc
          $!
          $ INVALID_INPUT:
          $ etc
      
      If you took out the slashes from the F$LOCATE command, it would take partial spellings. If the command is not in the command list, f$locate will return a value one higher than the length of the list.

      14. Faking an Array

      Use a root symbol name and an index variable. In this example, "name" is the root symbol name and "idx" is the index variable. So, the array is composed of name1, name2, name3, ...

          idx = 1
          another:
              write sys$output name'idx'
              idx = idx + 1
              if idx .LE. max_element then goto another
      
      15. A Few of the Lexical Functions

      f$cvtime() - does time conversions - look in the online help for more info.

      f$directory() - returns the current directory

      f$edit(original_string,"edits") - returns the original_string, edited according to the edits given in the "edits" string. the possible edits are:

      COLLAPSE - remove all spaces or tabs
      COMPRESS - replace multiple spaces or tabs with a single space
      TRIM - remove leading and trailing spaces or tabs
      UNCOMMENT - removes everything following an exclamation mark
      LOWERCASE
      UPCASE
      Example: new_string = f$edit(old_string,"TRIM,COMPRESS")

      f$element(element_number,delimiter,string) - returns the nth element from the string. The first element is zero. If not found, returns the delimiter.

      f$environment(item) - returns information about current DCL environment, such as DEFAULT, DEPTH, ON status, VERIFY status...

      f$extract(start,length,string) - returns a string of specified length from the original string, starting at offset "start" (first character has offset zero). Like a substr function. Returns a null string for invalid offsets; does not pad if specified length runs beyond the end of the original string

      f$fao - does all kinds of wild conversions, like printf does in C. Use this if you need special characters, as well. See online help.
      f$file_attributes ("filename.ext","opt") - returns file attributes; two of the attributes ("opt") that you might use are

      CDT - creation date and time
      EOF - file size in blocks

      f$integer(string) - converts string to integer

      f$length(string) - returns the length of the string

      f$locate(thing,string) - returns the offset if the thing is found in the string; the first character is offset 0. If not found, returns a value one higher than the length (as returned by f$length, which starts at one, not zero).

      f$message($status) - returns the message associated with a status code (you could put the code in the place of the status, too)

      f$parse ("filename",,,"field") - returns a part of the file's attributes: "field" can be NODE, DEVICE, DIRECTORY, NAME, TYPE, VERSION. If the filename is RADIO.DAT, then "RADIO" is the NAME, and ".DAT" is the TYPE. They fit together in this way:

          NODE::DEVICE:[DIRECTORY.SUBDIRECTORY]NAME.TYPE;VERSION
      
      There are other arguments for this function; see online help.

      f$search("filename") - searches the current directory for a file. Returns a null string if not found; returns the full address of the file (including device, directory, and version number) if found.

      f$string(integer) - converts integer to string

      f$time() - returns the current time

      f$trnlnm() - translates logical names into their values

      f$type() - determines the data type of a symbol. Returns null string if symbol is undefined. Note that F$TYPE(symbol) .EQS. "INTEGER" if the symbol is a string representation of a number, i.e.,

          year_string = "1998"
          if f$type(year_string) .eqs. "INTEGER" -
          then year = f$integer(year_string)
          endif
      

      Software References Kevinations home page