DIGITAL Command Language
The DIGITAL Command Language (DCL) is a high-level, English-like command-line interpreter and scripting language designed as the primary interface for interacting with the VMS (later OpenVMS) operating system.[1] Developed by Digital Equipment Corporation (DEC) in the late 1970s alongside the VAX/VMS system, DCL enables users to execute operations such as file management, process control, resource allocation, and system monitoring through structured commands, qualifiers, and parameters.[2] It supports both interactive sessions at the DCL prompt ($) and non-interactive modes like batch jobs, making it essential for system administration and automation in enterprise environments.[3]
DCL's structure revolves around command lines composed of a verb (e.g., COPY, [DIRECTORY](/page/Directory), SET), optional qualifiers (e.g., /[LOG](/page/Log) for output logging or /CONFIRM for user prompts), parameters (e.g., file specifications), and values, allowing precise control over system actions.[4] Key features include built-in commands for core tasks like creating directories (CREATE/[DIRECTORY](/page/Directory)), printing files (PRINT), and displaying system status (SHOW [PROCESS](/page/Process)); support for symbols and logical names as variables (e.g., SYS$DISK for default disk reference); and lexical functions (e.g., F$GETDVI for device information) that enhance scripting capabilities in command procedures (.COM files).[2] It also incorporates error handling via mechanisms like the ON command for condition monitoring (e.g., ON ERROR THEN [GOTO](/page/Goto) [label](/page/Label)) and wildcards (*, %) for flexible file operations, with parsing modes such as TRADITIONAL and EXTENDED introduced in later versions for improved syntax flexibility.[4]
Historically, DCL evolved from its origins in VMS Version 1.0 (1978) for DEC's VAX minicomputers, renamed OpenVMS in 1992 with the Alpha port, and continued through DEC's acquisitions by Compaq in 1998 and Hewlett-Packard (HP) in 2002, with maintenance since 2014 by VMS Software, Inc.[2][5] Notable enhancements include larger disk handling (up to 1.98 TB) in OpenVMS Version 8.4 (2010), alongside cluster-aware commands for distributed systems and integration with network protocols like TCP/IP.[4] As of November 2025, DCL remains a robust tool for legacy and modern OpenVMS deployments on Alpha, Itanium (Integrity), and x86-64 architectures, prized for its reliability in high-availability environments such as scientific computing and finance.[3]
History and Overview
Historical Development
The DIGITAL Command Language (DCL) was introduced in October 1978 as part of VAX/VMS version 1.0, developed by Digital Equipment Corporation (DEC) as the primary command-line interpreter for the VAX-11/780 minicomputer architecture.[6] Designed to provide a structured interface for managing the Virtual Memory System (VMS) operating environment, DCL emphasized ease of use and integration with VMS's multi-user, multiprocessing capabilities from its inception.[7]
The development of DCL was led by engineer Dave Cutler, who headed the core VMS team at DEC, drawing on prior experience with real-time systems like RSX-11 to prioritize reliability and robustness for enterprise computing applications.[8] DEC's engineering focus at the time centered on creating dependable systems for scientific, financial, and industrial users, influencing DCL's design as a high-level scripting tool that abstracted low-level hardware interactions.[9]
Major enhancements to DCL evolved alongside VMS releases, such as in version 5.0 (May 1988), which introduced improved support for VAXcluster distributed computing environments.[6] In the 1990s, VMS (renamed OpenVMS with version 6.0 in 1992) was ported to the Alpha architecture, extending DCL's compatibility to 64-bit RISC processors while maintaining backward compatibility with VAX applications.[10]
Corporate transitions shaped DCL's trajectory: DEC was acquired by Compaq Computer Corporation in 1998, which integrated it into broader server offerings; Compaq merged with Hewlett-Packard in 2002, rebranding VMS as OpenVMS under HP. HP licensed exclusive development rights to VMS Software Inc. (VSI) in 2014. Following the 2015 split into HP Inc. and Hewlett Packard Enterprise (HPE), HPE managed sales and support until June 2019, after which VSI became the sole provider.[11] In the 2020s, VSI announced and began delivering OpenVMS support for x86-64 architectures, with initial production releases like version 9.2 in July 2022, enabling DCL execution on modern commodity hardware and hypervisors. Subsequent updates include OpenVMS v9.2-3 in November 2024 and v10.1-3 in September 2025, further enhancing x86-64 support.[12][13]
Core Features and Design Principles
DIGITAL Command Language (DCL) was designed with goals centered on usability and compatibility within the VMS operating system, emphasizing case-insensitivity to simplify user input by automatically converting all commands and symbols to uppercase during processing.[2] This feature, combined with a verb-noun command structure—where commands typically consist of an English-like verb followed by parameters—facilitates intuitive interaction for system tasks.[14] Furthermore, DCL integrates deeply with the VMS file system through logical names and device-independent specifications, enabling portability across diverse DEC hardware platforms by abstracting physical device details.[2]
At its core, DCL incorporates principles of flexibility through symbolic substitution, allowing users to define and reference symbols (variables) at various scopes—such as process, job, or system levels—to dynamically generate commands and adapt procedures without hardcoding values.[2] Built-in support for qualifiers, prefixed with a slash (e.g., /COPIES=10 for the PRINT command), provides a structured way to modify command behavior, enhancing modularity and reducing the need for separate commands for common variations.[14] These elements promote efficient scripting and automation, aligning with DCL's role as both an interactive interface and a procedure language.
DCL exhibits particular strengths in multi-user environments, leveraging VMS's user identification code (UIC)-based protection mechanisms and hierarchical logical name tables to manage access and resource sharing across processes and jobs.[2] Its design draws influences from earlier DEC systems, including RSX-11's command structures for real-time processing and job control language (JCL) elements from mainframe batch systems, adapting these for VMS's multi-user, time-sharing architecture.[14]
While DCL supports interactive use, it is primarily oriented toward batch processing and command procedures, presenting limitations as a shell compared to more dynamic, real-time alternatives in modern systems.[2] However, it offers advantages in structured error reporting through condition codes and status values, such as %SYSTEM-F-NOOP for no-operation failures, which provide detailed severity levels and facilitate robust error handling in procedures.[14]
Syntax and Parsing
Command Structure
The basic structure of a DIGITAL Command Language (DCL) command follows the format verb [noun] [qualifier=value] [parameter], where the verb initiates the action, the optional noun specifies the target object, qualifiers modify the command's behavior, and parameters provide specific values or arguments.[15] For example, the command $ DIR AVERAGE.* lists files matching the pattern AVERAGE.* using the verb DIR (for DIRECTORY) and a noun parameter as a filespec.[15] Commands are typically entered at the DCL prompt prefixed by $, though this prompt symbol is optional in scripts or command files.[15]
Qualifiers, prefixed with a forward slash /, function as switches to alter default behavior and are often specified as /qualifier=value, such as /OUTPUT=filespec to redirect output to a file or /LOG to enable logging.[15] Parameters serve as positional or named arguments, accepting values like filespecs (e.g., *.COM), device names, or numbers; for instance, in $ COPY input-filespec output-filespec, the two filespecs are positional parameters defining source and destination.[15] Multiple qualifiers can be combined, as in $ MOUNT /BIND=LIBRARY DMA0: BOOK1, where /BIND=LIBRARY is a qualifier specifying a bind operation for the volume.[15]
DCL commands handle strings by enclosing them in double quotes to preserve spaces and case, such as "Enter Y[ES]" for a prompted message, while numbers are interpreted as integers or hexadecimal values (e.g., %X1C for hexadecimal 1C).[15] Logical names, which act as symbolic aliases for devices, files, or system elements (e.g., SYS$OUTPUT for the default output device), can be used directly in commands and are translated during execution via prior definitions like $ DEFINE TEMP: DKA1:[MYDIR].[15]
Punctuation rules include colons (:) to terminate device or logical name specifications, as in DKA0: for a disk device, and square brackets ([]) for directory paths in VMS filespec notation, such as [GROUP6,JENNIFER] to denote a user identification code or directory.[15] Wildcards like * and % are supported in filespecs for pattern matching, e.g., DIR/FILESPEC=*.COM to list only command files.[15]
DCL is case-insensitive, treating [DIR](/page/Dir) and [dir](/page/Dir) as equivalent, and supports abbreviations by allowing truncation to the shortest unique prefix, such as [MOU](/page/Mount) for MOUNT or /L for /[LOG](/page/Log), provided the abbreviation unambiguously identifies the element.[15] The DCL parser interprets this structured input to validate and execute commands accordingly.[15]
Parser Mechanics
The DIGITAL Command Language (DCL) parser processes user input through a structured sequence of phases to ensure commands are valid and executable. The process begins with lexical scanning, where the input string is tokenized by separating elements based on delimiters such as spaces, while ignoring comments introduced by an exclamation mark (!) and handling line continuations indicated by a trailing hyphen (-). This phase identifies basic components like commands and operands, with a maximum command length of 1024 characters across continued lines, each segment up to 255 characters.[16][17]
Following lexical scanning, syntactic analysis occurs using compiled command tables generated by the Command Definition Utility (CDU), which validate the structure against predefined grammar rules for command validity. Token types recognized include verbs (the primary command actions, such as COPY or SHOW), nouns (parameters or objects like file specifications), operators (such as logical connectors), and literals (constant values). Qualifiers, which modify command behavior and begin with a slash (/), are also parsed here, along with their values. To handle embedded spaces within tokens, double quotes enclose literals, preserving them as single units during tokenization—for example, "file with spaces.txt" is treated as one noun. Semantic checks then verify the appropriateness of qualifiers and values, ensuring they align with the verb's requirements, such as type compatibility for parameters.[18][19]
Error detection integrates throughout these phases, with the parser issuing messages for issues like invalid verbs (%DCL-W-IVVERB, unrecognized command verb—check validity and spelling) or unrecognized qualifiers (%DCL-W-IVQUAL). Recovery mechanisms include defaulting optional nouns to system or command-specific values and, in interactive mode, prompting users for missing required parameters via routines like LIB$GET_INPUT. If parsing fails severely, such as with null input (%CLI-E-NOCOMD), the command is rejected without execution.[1][16]
DCL supports command recall and line editing to facilitate input correction, particularly in interactive sessions. Users can recall up to 254 previous commands using the RECALL command or keyboard sequences like Ctrl/B, with editing enabled via SET TERMINAL/LINE_EDIT for insert, delete, and cursor movements; early VMS versions relied on TPEDIT mode for similar functionality. In batch mode, parsing differs by prohibiting interactive prompts—full commands must be provided in scripts, though line continuations with hyphens remain supported for multi-line constructs, ensuring non-interactive execution without user intervention.[1][20][17]
Symbols and Variables
Symbol Declaration and Usage
In DIGITAL Command Language (DCL), symbols serve as variables to store and manipulate data within command procedures and interactive sessions on OpenVMS systems. Symbols are declared using assignment statements, which define their name and value. The basic syntax for creating a local symbol, which is private to the current process and accessible only within the current command procedure level, is symbol = expression for numeric or mixed values, or symbol := string for string values. For example, $ COUNTER = 0 creates a local integer symbol, while $ MESSAGE := "Hello World" defines a local string symbol, where the colon in string assignment normalizes the value by converting it to uppercase and compressing multiple spaces to single spaces unless quoted.[21][19]
Global symbols, in contrast, extend visibility across all command procedure levels within the same process, allowing data sharing among nested or called procedures without redefinition. They are declared using double equals for numeric assignments (symbol == expression) or double colon-equals for strings (symbol :== string). For instance, $ SHARED_VAR == 42 establishes a global integer symbol accessible throughout the process. The distinction between local and global ensures controlled data access: local symbols are deallocated upon procedure exit to prevent unintended persistence, while global symbols remain available until explicitly removed or the process ends. Symbol names can be 1 to 255 characters long, starting with an alphabetic character, underscore, or dollar sign ($), and are case-insensitive in TRADITIONAL parsing; in EXTENDED parsing, they are case-sensitive.[21][22]
Scope for DCL symbols is inherently tied to the process level, with no direct support for job-wide, group-wide, or system-wide sharing via symbol tables alone; broader scopes are achieved through logical names stored in dedicated tables (LNMPROCESS, LNMJOB, LNMGROUP, LNMSYSTEM). However, within a process, the SET SYMBOL command with the /SCOPE qualifier manages visibility: for example, SET SYMBOL/SCOPE=NOLOCAL restricts access to global symbols only in procedures, while SET SYMBOL/SCOPE=NOGLOBAL limits to local symbols. This scoping prevents conflicts in complex scripts but applies only to the current process, as symbols do not propagate across job controllers or batch subprocesses without explicit mechanisms like LIB$SPAWN copying.[23][24]
Symbols are used in DCL commands through substitution, where their values replace the symbol name during parsing. Standard substitution occurs automatically in unquoted positions, but to force it in quoted strings or unusual contexts, enclose the symbol in single quotes ('symbol') for both symbol and lexical function replacement. For symbol-only substitution without lexical functions, use the ampersand (&symbol); the caret (^symbol) forces substitution even in places DCL might otherwise ignore it, such as within certain qualifiers. An example is $ WRITE SYS$OUTPUT "The value is 'COUNTER'.", which outputs the current value of COUNTER. These mechanisms enable dynamic command construction, such as $ @'FILENAME' to execute a procedure named by a symbol. Detailed indirection layering builds on this foundation.[21]
DCL supports two primary data types for symbols: strings (up to 255 characters on VAX or 8192 on Alpha/Itanium) and 32-bit signed integers, with automatic type coercion during operations—for instance, a string like "123" coerces to integer 123 in arithmetic expressions. Dates are not a native type but are handled as strings via lexical functions (e.g., F$TIME returns a string timestamp) or as binary integers for computations, with coercion ensuring compatibility in mixed expressions like comparisons. Boolean logic uses even integers as FALSE (0) and odd as TRUE. Symbols cannot hold floating-point or larger numeric types directly; such operations require lexical functions.[21][25][26]
To deassign a symbol, use the DELETE/SYMBOL command, which removes it from the local table by default (DELETE/SYMBOL symbol) or the global table with the /GLOBAL qualifier (DELETE/SYMBOL/GLOBAL symbol). This frees resources and prevents lingering values. For persistence across command procedure calls or sessions, symbols are not inherently saved; however, the SET SYMBOL command with /GENERAL or /VERB qualifiers can influence how symbols are interpreted (as commands or data), and explicit redefinition in login procedures (e.g., LOGIN.COM) or use of global symbols ensures availability within a session. Logical names, assigned via the ASSIGN command, offer true cross-session persistence when placed in job or system tables.[21][23]
Indirect Referencing
Indirect referencing in DIGITAL Command Language (DCL) allows symbols to dynamically resolve to the values of other symbols, enabling flexible runtime evaluation in command procedures. The syntax employs the ampersand (&) prefix followed by a symbol name, such as &SYMBOL, which substitutes the value of SYMBOL into the command line. For instance, if A := "B" and B := "value", then executing WRITE SYS$OUTPUT &A outputs "value", as &A resolves to "B" and DCL interprets that as the symbol B whose value is then used.[20]
This mechanism supports multi-level indirection through repeated ampersands, permitting nested resolution up to 31 levels deep. An example of two-level indirection is C := "A", where &C resolves to "A", and &&C further resolves to the value of A (e.g., "B"), potentially chaining to deeper levels for runtime evaluation. Such nesting facilitates complex dynamic constructions without explicit conditional logic.[20]
Common use cases include building dynamic commands in parameterized scripts, such as generating file names or qualifiers based on runtime conditions, and implementing menu systems where user selections indirectly reference predefined actions or parameters. For example, in a script processing multiple files, a loop might use &[FILENAME](/page/Filename) to append varying file specifications dynamically.[19]
DCL imposes limitations on indirect referencing, including no built-in recursion detection, which can lead to infinite loops if circular references are created (e.g., X := "Y" and Y := "X" with &X triggering endless resolution). Additionally, the total substitution buffer is capped at 1024 characters, potentially truncating deeply nested or lengthy indirect expansions.[20]
In comparison to direct substitution—where a symbol like SYMBOL or 'SYMBOL' (quoted to force evaluation) inserts its literal value without further resolution—indirect referencing with & evaluates the substituted content as a potential symbol name, providing greater dynamism but requiring careful management to avoid errors.[19]
Commands
Command Categories
DCL commands are organized into functional categories that reflect their primary roles in interacting with the OpenVMS operating system. These categories encompass built-in imperatives for managing resources, processes, and data, enabling users to perform system-level tasks efficiently.[4][2]
File management commands handle operations on files and directories, such as listing contents, copying, deleting, renaming, and purging versions to maintain organized storage. Key examples include DIR (or DIRECTORY) for displaying file information, COPY for duplicating files, DELETE for removing them, RENAME for altering names, PURGE for selective version deletion, and TYPE for viewing contents. These commands support wildcard specifications and logical names for batch operations on multiple files.[4][15]
Process control commands facilitate job submission, multitasking, and execution management within the OpenVMS environment. Prominent examples are RUN for directly executing images or programs, SUBMIT for queuing batch jobs, and SPAWN for creating subprocesses to run commands concurrently without interrupting the parent process. Additional related commands include ATTACH for connecting to existing subprocesses and CANCEL for interrupting pending requests. These enable efficient handling of computational tasks and resource allocation.[4][2]
System utilities provide tools for configuration, status inquiry, and performance monitoring of the operating system. The SHOW command displays a wide range of information, such as process status, system parameters, and resource usage; SET modifies configurations like defaults, protections, and privileges; and MONITOR collects performance metrics for CPU, memory, and I/O activities. Other utilities include INITIALIZE for setting up queues or databases and DUMP for error analysis. These commands are essential for administrative oversight and troubleshooting.[4][15]
Network and device commands support connectivity, maintenance, and hardware interactions. SET HOST establishes remote terminal sessions for accessing other nodes, while MCR invokes the Maintenance Command Recognizer for low-level device control, particularly in diagnostic contexts. Device-related examples include MOUNT and DISMOUNT for volumes, ALLOCATE and DEALLOCATE for assigning hardware units, and SET DEVICE for configuring attributes like access modes. Network utilities such as SET NETWORK and SHOW NETWORK manage connectivity parameters. These facilitate distributed operations and hardware management in clustered environments.[4][2]
Data manipulation commands process text files for searching, sorting, and output, aiding in information retrieval and analysis. SEARCH scans files for specified strings or patterns, SORT orders records based on keys, and TYPE outputs file contents to the terminal or another file. These are particularly useful for handling sequential or indexed files in Record Management Services (RMS).[4]
Certain historical DCL commands, deprecated in modern OpenVMS versions, were tailored to VAX-specific hardware or early system architectures and have been replaced by more generalized equivalents. Examples include SHOW MAGTAPE for displaying magnetic tape device details (superseded by SHOW DEVICE/FULL in VMS 5.4) and SET PROCESS/CPU=[NO]ATTACHED for asymmetric multiprocessing control (removed due to lack of support in symmetric multiprocessing). Other obsolete qualifiers, such as those in SET DEVICE/ACL or INITIALIZE/QUEUE/PRIORITY, were phased out starting from VMS 4.0 for streamlined access control and queue management. These changes reflect OpenVMS evolution from VAX to Alpha and Itanium platforms, prioritizing compatibility and efficiency.[27][4]
Command Execution
The execution of a DCL command in OpenVMS follows a structured runtime model that begins with parsing the command line, followed by image activation for executable commands, processing of qualifiers to modify behavior, and handling of input/output streams. For built-in DCL commands, execution occurs directly within the DCL interpreter without invoking external images, while commands that invoke utilities or user programs (e.g., via RUN) activate the corresponding executable image using OpenVMS Record Management Services (RMS) to locate and load the file based on its specification, applying defaults like .EXE for the file type if omitted. Qualifiers, such as /OUTPUT or /QUALIFIER-specific options, are processed during this phase to alter execution parameters, for instance directing output to a specified file instead of the default SYS$OUTPUT stream, which typically routes to the terminal in interactive sessions or a log file in batch mode. Output from the command is then directed to the appropriate device or file, with RMS managing record formats (e.g., sequential or relative) and any specified redirections.
Status reporting in DCL relies on the global symbol STATUS, which is automatically set after each command or [image](/page/Image) execution to the [condition](/page/Condition) value returned, allowing scripts or subsequent commands to check outcomes. The value of STATUS is a 32-bit integer where the low-order three bits determine the severity level: 0 or 1 for success (e.g., SS_NORMAL = 1), 2 for warning, 3 for [error](/page/Error), and 4 for severe (fatal) [error](/page/Error) (e.g., SS_ABORT).[28] These condition codes are defined in system service references and can trigger ON ERROR actions if severity exceeds a threshold. Users can examine STATUS explicitly, such as with IF FTYPE("$STATUS") EQ 1 THEN ... to branch based on success.
DCL supports both interactive and batch execution modes, with distinct behaviors in input handling, prompting, and logging. In interactive mode, commands are entered directly at the DCL prompt (), allowing real-time user input for interactive qualifiers (e.g., READ SYSCOMMAND prompts the terminal), and output appears immediately on the terminal unless redirected. Batch execution, invoked by submitting a .COM file via the SUBMIT command, runs the procedure in a separate detached process without user interaction, equating SYSINPUT to the null device, SYSOUTPUT and SYSERROR to log files (e.g., .LOG and .LIS by default), and suppressing prompts to enable unattended operation. Differences in logical name assignments, such as SYSCOMMAND being the terminal in interactive mode but null in batch, ensure compatibility while preventing hangs in non-interactive environments.
Although DCL provides the native PIPE command for UNIX-style pipelining since OpenVMS Version 7.1,[20] it has limitations in command compatibility and nesting, as not all DCL verbs support piping (e.g., some ignore SYSPIPE input), and nested PIPEs are restricted to one per command procedure level. Workarounds for unsupported scenarios often involve temporary files to stage intermediate output, such as writing results from one command to a temp file and reading it as input for the next, avoiding the constraints of direct piping. For instance, complex data flows may use DEFINE/USER SYSPIPE temp.dat before a command to simulate input redirection.
DCL commands execute within the security context of the invoking user's privileges and User Identification Code (UIC), which are inherited from the login process and determine access to system resources. The UIC, formatted as [group,member] and stored in the SYSUAF.DAT authorization database, identifies the owner for file creation and applies protection checks based on the process UIC unless overridden (e.g., via /OWNER_UIC qualifier requiring IMPERSONATE privilege). User privileges, such as SYSPRV for system-wide access or GRPPRV for group-level operations, must be held for privileged actions like modifying system files; without them, commands fail with status codes like SS$_NOPRIV, enforcing the principle of least privilege in the execution environment.
Scripting
Script Fundamentals
DCL scripts, also known as command procedures, are text files that contain sequences of DCL commands designed for automated execution on OpenVMS systems. These files typically use the .COM extension by default, allowing users to store reusable command sets for tasks such as system configuration or batch processing. To create a script, users edit a plain text file with a text editor like EDT or EVE, entering DCL commands line by line, and save it with the .COM suffix in a directory accessible to the DCL prompt.[29]
Scripts are invoked interactively by entering the at-sign (@) followed by the filename at the DCL prompt, such as @[TEST](/page/Filename).COM, which executes the commands sequentially without requiring the full file type if .COM is used. For non-interactive execution, the SUBMIT command queues the script as a batch job, for example, $ SUBMIT MYJOB.COM, directing all output—including command results, messages, and errors—to a default log file with a .LOG extension in the user's directory. Inline comments within scripts begin with ! after a command on the same line (e.g., $ command ! comment) or $! at the start of a line for full-line comments, enabling documentation without affecting execution; labels for basic navigation are defined as label_name:, facilitating simple jumps with the GOTO command.[29][30]
Positional parameters allow scripts to accept input dynamically, with up to eight arguments passed via the command line and referenced inside the script as P1, P2, and so on—for instance, @[SCRIPT](/page/Script) "input1" "input2" makes the values available as 'P1' and 'P2' for string handling or P1 and P2 without quotes for symbols. Basic error handling is achieved through the ON ERROR directive, which branches execution on failure, such as ON ERROR THEN GOTO error_label to redirect to a labeled section for cleanup or logging. Symbols, as defined in related sections, can enhance script flexibility by storing intermediate values during parameter processing or conditional checks.[29][30]
Control Structures
The IF-THEN-ELSE construct in DCL provides conditional execution within command procedures, allowing scripts to branch based on evaluated conditions. The syntax is $ IF condition THEN command [ELSE command], where the condition is a logical expression, and execution proceeds to the THEN clause if true or the optional ELSE clause if false, terminated by $ ENDIF. For instance, to check for the existence of a file, the condition might be written as $ IF F$SEARCH("example.dat") .NES. "" THEN WRITE SYS$OUTPUT "File found" ELSE WRITE SYS$OUTPUT "File not found" ENDIF.[19]
Conditions in DCL are evaluated as follows: for numeric results, true if the value is odd, false if even or zero; for string results, true if the first character (case insensitive) is T or Y, false otherwise (including the null string, "FALSE", or strings starting with other characters). Relational operators such as .EQ. for equality (numeric or string, depending on context) and .NES. for inequality are used, alongside string-specific operators like .EQS. for exact matches. Status symbols like $SEVERITY, a global symbol reflecting the severity level of the most recent condition code (e.g., 1 for success, 2 for error), enable checks such as $ IF $SEVERITY .EQ. 1 THEN ... to verify command outcomes.[19][19]
Logical operators .AND., .OR., and .NOT. combine multiple conditions, evaluated left-to-right with short-circuiting where applicable. An example is $ IF (F$SEARCH("file1.dat") .NES. "") .AND. (F$SEARCH("file2.dat") .NES. "") THEN WRITE SYS$OUTPUT "Both files exist" ENDIF, which executes only if both files are present.[19]
The GOTO and EXIT commands facilitate flow control, with GOTO transferring execution to a labeled statement via $ GOTO label_name, where labels are defined as label_name: and scoped locally to the current command procedure. EXIT terminates the procedure with $ EXIT [status], optionally setting a return status code, often used conditionally like $ IF error_condition THEN EXIT. Labels must be unique within the procedure and cannot be targeted if inside a separate IF-THEN-ELSE block, limiting unstructured branching.[31][31]
DCL lacks native FOR or WHILE loops, relying on GOTO-based workarounds for iteration. A simple counted loop might appear as:
$ COUNTER = 0
LOOP_START:
$ COUNTER = COUNTER + 1
$ IF COUNTER .LT. 5 THEN GOTO LOOP_START
$ WRITE SYS$OUTPUT "Loop completed"
$ COUNTER = 0
LOOP_START:
$ COUNTER = COUNTER + 1
$ IF COUNTER .LT. 5 THEN GOTO LOOP_START
$ WRITE SYS$OUTPUT "Loop completed"
For WHILE-like behavior, an IF-GOTO combination simulates the construct:
WHILE_LOOP:
$ IF condition .NES. "TRUE" THEN GOTO LOOP_END
$ ! Commands to execute while true
$ GOTO WHILE_LOOP
LOOP_END:
WHILE_LOOP:
$ IF condition .NES. "TRUE" THEN GOTO LOOP_END
$ ! Commands to execute while true
$ GOTO WHILE_LOOP
LOOP_END:
Such patterns use EXIT within the loop body to break early if needed. As of OpenVMS Version 9.2 (2023), DCL scripting fundamentals remain unchanged, supporting x86-64 architecture through emulation.[20]
IF-THEN-ELSE statements support nesting to any practical depth, provided each is properly closed with ENDIF, enabling hierarchical logic like nested file checks. However, GOTO cannot jump into or out of nested IF blocks, enforcing block integrity. Common pitfalls include infinite loops from GOTO without an exit condition, such as omitting the counter check above, which can hang the procedure until manual intervention; mismatched ENDIFs causing syntax errors; and referencing undefined labels, triggering warnings and procedure termination.[19][31]
Lexical Functions
Function Syntax
DCL lexical functions are invoked in the format F$function([arguments]), where function is the name of the specific lexical function (which may be abbreviated to its unique prefix) and the parentheses are required even if no arguments are provided.[32] These functions can be embedded directly within DCL commands, expressions, or assignment statements to symbols, such as $ value = F$GETSYI("HW_MODEL"), allowing runtime evaluation and substitution by the DCL interpreter before command execution.[26]
Arguments to lexical functions are passed positionally inside the parentheses and separated by commas if multiple are specified; they consist of string or integer expressions, with optional arguments omitted by retaining commas (e.g., F$FUNCTION(,optional_arg)) or using empty strings ("") as needed for specific functions.[33] String delimiters such as double quotes are used for literal arguments containing spaces or special characters.[32]
Lexical functions return values as either character strings or integers, depending on the function; in numeric contexts, DCL coerces string results to integers (e.g., treating "123" as 123, or non-numeric strings as 0), while integer results are converted to strings for string operations.[26]
These functions are available for use in both interactive DCL sessions and batch command procedures, operating within the current process context to provide system, file, or string-related information.[32] Certain functions, however, require specific user privileges (such as SYSPRV for process inquiries) to access restricted data.[26]
On failure, invalid input, or exhaustion of available data (e.g., in iterative functions), lexical functions typically return a null string (""), which can be detected in conditional expressions using the .EQS. "" operator, such as $ IF F$FUNCTION() .EQS. "" THEN ....[34]
Common Lexical Functions
DCL lexical functions provide essential utilities for file handling, system inquiries, string operations, and environmental assessments within command procedures and interactive sessions. These functions enable dynamic scripting by returning contextual data without invoking external commands, facilitating efficient automation on OpenVMS systems.[19]
The FSEARCH function searches directories for files matching a given specification, invoking the RMS SEARCH service to return the full file specification of the next matching file or an empty string if none exists.[31] It supports an optional index parameter for iterating through multiple matches, commonly used in loops for directory traversal, such as processing all command files in a directory. For example, the assignment $ dir = F$SEARCH("*.COM") retrieves the specification of the first matching .COM file, and subsequent calls with an incrementing index can enumerate others until an empty result signals completion.[31]
The FFILE_ATTRIBUTES function retrieves metadata about a specified file, such as size, creation date, revision number, or record format, returning the requested attribute as a string or an empty string for invalid attributes.[19] It requires a file specification and an attribute keyword (e.g., "SIZE" or "CREATED"), making it valuable for conditional logic based on file properties, like checking file size before processing. An example usage is ` size = F$FILE_ATTRIBUTES("EXAMPLE.DAT", "SIZE")`, which assigns the file's byte size to the symbol size.[19]
FGETSYI obtains [hardware](/page/Hardware) and software details about the local [system](/page/System) or a [cluster](/page/Cluster) [node](/page/Node) by invoking the GETSYI system service, returning values for items like memory size, node name, or hardware type as strings or integers.[19] It accepts an item code (e.g., "NODENAME" or "MEMORY_SIZE") and optionally a node name, useful for adaptive scripting that adjusts to system configuration. For instance, $ node = F$GETSYI("NODENAME") captures the current node's name for logging or conditional execution.[19]
The FGETJPI function queries [process](/page/Process) details via the GETJPI system service, providing information such as process ID, username, status, or priority for a specified process.[19] Parameters include a process identifier (e.g., 0 for the current process) and an item code (e.g., "USERNAME" or "PRCNAM"), returning the value as a string or integer. This is particularly helpful for monitoring or managing running processes in scripts. An example is $ user = F$GETJPI(0, "USERNAME"), which retrieves the username of the current process.[19]
String Manipulation Functions
FEXTRACT isolates a [substring](/page/Substring) from a source [string](/page/String), specified by starting position (0-based) and [length](/page/Length), returning the extracted portion or the full string if length exceeds bounds.[19] It is ideal for [parsing](/page/Parsing) file names or log entries, with parameters for start index, length, and the input string. For example, ` prefix = F$EXTRACT(0, 3, "TESTFILE.TXT")` yields "TES".[19]
The FLOCATE function searches for a [substring](/page/Substring) within a larger string, returning the 0-based offset of the first occurrence or -1 if not found, aiding in pattern-based [extraction](/page/Extraction) when combined with other functions.[19] It takes the substring to locate and the source string as arguments. A typical use is ` pos = F$LOCATE(".TXT", "EXAMPLE.TXT")`, which returns 8, allowing subsequent extraction of the extension.[19]
FCVTIME converts time values between formats, such as [absolute](/page/Absolute), relative, or [delta](/page/Delta) times, returning a standardized [string](/page/String) like "yyyy-mm-dd hh:mm:ss.cc" based on optional qualifiers.[19] It processes an input time [string](/page/STRING) and an option (e.g., "[ABSOLUTE](/page/Absolute)" or "SINCE"), supporting [date](/page/Date) arithmetic in scripts for [logging](/page/Logging) or scheduling. For example, ` now = FCVTIME(FTIME())` formats the current time from F$TIME for display.[19]
Environment Functions
FENVIRONMENT queries DCL execution context, returning details like terminal type, default protection, or symbol scope for items such as "TERM_TYPE" or "DEF_PROT".[19] It accepts an item code and returns a string, enabling scripts to adapt to interactive versus batch modes. An example is ` term = F$ENVIRONMENT("TERM_TYPE")`, which identifies the terminal for output formatting.[19]
The FPRIVILEGE function tests whether a specific [user](/page/User) [privilege](/page/Privilege) (e.g., "SYSPRV" or "GRP") is currently enabled, returning "TRUE" or "FALSE" to enforce [security](/page/Security) in procedures.[31] It requires the privilege name as input, preventing unauthorized operations. For instance, ` has_priv = F$PRIVILEGE("SYSPRV")` checks for system privilege before sensitive actions.[31]