OpenVMS 用户手册
14.16.5 使用 GOTO 命令
GOTO 命令把控制传递到在命令过程中的标号行。(有关标号使用的详情,请参阅第 13 章。)
GOTO 命令在 THEN
子句中特别有用,它使过程分支向前或向后。例如,当使用命令过程的参数时,可以在过程开始测试参数,并转移到适当的标号。
GOTO 或 GOSUB 命令的目标标号不能在 IF-THEN-ELSE 结构或单独的子例行程序之内。
在以下例子中,IF 命令检查 P1 不是空串:
$ IF P1 .NES. "" THEN GOTO OKAY
$ INQUIRE P1 "Enter file spec"
$ OKAY:
$ PRINT/COPIES=10 'P1'
.
.
.
|
如果 P1 是空串,就不执行 GOTO 命令,而 INQUIRE 命令提示输入参数值。否则,GOTO
命令使控制流绕过 INQUIRE
命令。在任何一种情况下,该过程执行跟随行标号 OKAY 的 PRINT 命令。
在以下例子中 GOTO 命令返回出错消息,因为它的目标 (TEST_1) 在 IF-THEN 结构内:
$ GOTO TEST_1
$ EXIT
$ IF 1.EQ.1
$ THEN WRITE SYS$OUTPUT What are we doing here?"
$ TEST_1:
$ WRITE SYS$OUTPUT "Got to the label"
$ ENDIF
$ EXIT
|
14.16.5.1 避免重新执行
您也可以使用 GOTO
命令避免重新执行已成功完成的部分作业。要做到这一点,执行以下步骤:
步骤 |
动作 |
1
|
在过程中从一个标号开始每个可能的开始点。
|
2
|
在这个标号之后,使用 SET RESTART_VALUE = label-name 命令设置到这个标号的重新启动点。 在执行 SET RESTART_VALUE =
label-name 命令之后,如果批量作业被中断,那么当重新启动这个批量作业时,系统把适当的标号名赋值给全局符号 BATCH$RESTART。
|
3
|
在这个过程开始,测试符号
$RESTART 的值。
如果 $RESTART 为真,执行一个 GOTO 语句并且使用符号
BATCH$RESTART 作为转移标号。 |
$RESTART 全局符号
$RESTART
是系统为您维护的保留全局符号。如果一个批量作业中断后重新启动,那么 $RESTART 为真。否则,$RESTART 为假。不能删除保留的全局符号 $RESTART。
如果命令过程有 SET RESTART_VALUE
命令,而您想要全部重新执行这个作业,可输入 SET
ENTRY/NOCHECKPOINT 命令删除这个全局符号 BATCH$RESTART。如果重新启动一个中断的作业,那么这个作业从它中断的位置开始执行。
这个命令过程展示如何使用在批量作业中的重新启动值:
$ ! Set default to the directory containing
$ ! the file to be updated and sorted
$ SET DEFAULT DISK1:[ACCOUNTS.DATA84]
$
$ ! Check for restarting
$ IF $RESTART THEN GOTO 'BATCH$RESTART'
$
$ UPDATE_FILE:
$ SET RESTART_VALUE = UPDATE_FILE
.
.
.
$ SORT_FILE:
$ SET RESTART_VALUE = SORT_FILE
.
.
.
EXIT
|
要把这个命令过程提交为一个可以重新启动的批量作业,那么当提交这个作业时,为 SUBMIT 命令使用 /RESTART 限定词。因为中断的作业从中断的位置开始执行,因此在 SORT_FILE
例行程序期间如果这个作业中断,当重新启动时它就从标号 SORT_FILE 开始执行。 系统失败时,不会维护大部分进程环境。经系统失败后被维护的符号只有 $RESTART
和 BATCH$RESTART。因此您应该在每个 SET
RESTART_VALUE 命令之后或在如果 $RESTART 为真就会执行的 THEN
命令块中,重新定义用于您的命令过程的任何符号或进程逻辑名。如果您在 THEN 命令块中定义符号和逻辑名,那么命令
GOTO 'BATCH$RESTART' 应该是 THEN 命令块中的最后一个命令。14.16.6 使用 GOSUB 和 RETURN 命令
在命令过程中 GOSUB
命令把控制传递到标号的子例行程序。如果这个标号在命令过程中不存在,这个过程不能继续执行,并且被强制退出。(有关标号的完整信息,请参阅第 13 章。)
每个过程级可以嵌套 GOSUB 命令最多 16 次。
GOSUB 命令是局部子例行程序调用;它不建立新的过程级。因此,在当前命令级中定义的所有标号和局部符号可用于 GOSUB 调用的子例行程序。
RETURN 命令终止子例行程序,并把控制返回到在 GOSUB
命令之后的命令。您可以指定 RETURN 命令的 $STATUS 值,取代 DCL
在子例行程序结束时分配给 $STATUS 的值。这个值必须是一个 0 至 4 之间的整数或者一个等价表达式。如果为 $STATUS
指定一个值,DCL 把这个值解释为一个条件代码。如果不为 $STATUS
指定一个值,则就保存
$STATUS 的当前值。
以下例子展示如何使用 GOSUB 命令把控制传递到子例行程序:
$!
$! GOSUB.COM
$!
$ SHOW TIME
$ GOSUB TEST1 (1)
$ WRITE SYS$OUTPUT "GOSUB level 1 has completed successfully."
$ SHOW TIME
$ EXIT
$!
$! TEST1 GOSUB definition
$!
$ TEST1:
$ WRITE SYS$OUTPUT "This is GOSUB level 1."
$ GOSUB TEST2 (2)
$ RETURN %X1 (3)
$!
$! TEST2 GOSUB definition
$!
$ TEST2:
$ WRITE SYS$OUTPUT "This is GOSUB level 2."
$ WAIT 00:00:02
$ RETURN (4)
|
当您检查这个例子时,注意以下几点:
- 第一个 GOSUB 命令把控制传递到标号为 TEST1 的子例行程序。
- 这个过程执行在子例行程序 TEST1
中的命令,转移到标号为 TEST2 的子例行程序。
- 在子例行程序 TEST1 中的 RETURN 命令把控制返回到主命令过程,并且把值 1 传递给 $STATUS,指出成功完成。
-
在子例行程序 TEST2 中的 RETURN 命令把控制返回到子例行程序 TEST1。注意,这个命令在命令 3 之前执行。
14.17 建立新的命令级
有两种方法可建立新的命令级:
- 在一个命令过程中使用执行过程 (@)
命令来嵌套命令过程,以调用另一个命令过程 (如第 13 章所述)。
- 使用 CALL 命令调用存在于命令过程中的子例行程序。
14.17.1 使用 CALL 命令
在命令过程中,CALL 命令把控制传递到标号的子例行程序,并建立新的过程级。CALL
命令允许您在单个文件中保持一个以上的相关命令过程,从而使过程更易于管理。子例行程序标号必须是唯一的,在命令过程中可以放在 CALL 命令之前或之后。第 13 章包含输入子例行程序标号的规则。
除标号之外,您可以传递多达八个可选参数给子例行程序。有关参数的完整信息,请参阅
14.2 节。
以下是使用 CALL 命令的规则:
- 把输出发送给 SYS$OUTPUT
- 有一个可选的 /OUTPUT 限定词,允许您把输出从子例行程序指引到一个文件
- 对输出文件使用默认文件类型 .LIS
- 输出文件说明中不接受通配符
14.17.1.1 CALL 命令的默认值
以下是与使用 CALL 命令相关的附加默认:
- 可以嵌套 CALL 命令调用的子例行程序和执行过程 (@) 命令调用的过程,最多可达 32 个命令级。
- 除非使用 SET SYMBOL
命令屏蔽,在外部级定义的局部符号可用于任何内部的过程或子例行程序级。任何命令级都有全局符号。
- 标号只在定义它们的命令级才有效。
14.17.1.2 开始和结束子例行程序
SUBROUTINE 和 ENDSUBROUTINE 命令定义 CALL
子例行程序的开始和结束。这个标号在 SUBROUTINE
命令之前定义子例行程序的入口点。您可以把 EXIT 命令放置在 ENDSUBROUTINE 命令之前,但是对于终止子例行程序不是必需的。ENDSUBROUTINE
命令终止子例行程序,并把控制传递到在 CALL 命令之后的命令行。
只当子例行程序被 CALL 命令调用时,才执行这个子例行程序中的命令行。在命令过程的逐行执行期间,命令语言解释程序跳过在 SUBROUTINE 和 ENDSUBROUTINE 命令之间的所有命令。 以下限制应用于定义子例行程序入口点的作用域和标号引用的使用:
- 在另一个子例行程序中定义的子例行程序入口点局部于那个子例行程序。如果一个子例行程序入口点是在一个单独的子例行程序块中,那么就不能调用这个子例行程序。
- 如果一个子例行程序入口点定位在 IF-THEN-ELSE
块,就不能从 IF-THEN-ELSE
块的外部调用这个子例行程序。
- 每个 SUBROUTINE 命令必须有匹配的 ENDSUBROUTINE 命令结束子例行程序。
在以下例子中,因为 CALL BAR
命令在 MAIN 子例行程序之外,因此这个调用无效:
$ CALL BAR
$
$ MAIN: SUBROUTINE
$
$ BAR: SUBROUTINE
$ ENDSUBROUTINE
$
$ ENDSUBROUTINE
|
要使这个 CALL 命令起作用,必须把它放在 SUBROUTINE
和 ENDSUBROUTINE 点之间。
在这个例子中展示的调用不允许,因为它是在
IF-THEN-ELSE 块之内:
$ IF 1
$ THEN
$ BOB:SUBROUTINE
$ ENDSUBROUTINE
$ ENDIF
$ CALL BOB
|
以下例子包括两个子例行程序,称为 SUB1 和 SUB2。直到使用 CALL 命令调用它们,才执行这些子例行程序。
$
$! CALL.COM
$
$! Define subroutine SUB1.
$!
$ SUB1: SUBROUTINE
.
.
.
$ CALL SUB2 !Invoke SUB2 from within SUB1.
.
.
.
$ @FILE !Invoke another command procedure file.
.
.
.
$ EXIT
$ ENDSUBROUTINE !End of SUB1 definition.
$!
$! Define subroutine SUB2.
$!
$ SUB2: SUBROUTINE
$ EXIT
$ ENDSUBROUTINE !End of SUB2 definition.
$!
$! Start of main routine. At this point, both SUB1 and SUB2
$! have been defined but none of the previous commands have
$! been executed.
$!
$ START:
$ CALL/OUTPUT=NAMES.LOG SUB1 "THIS IS P1"
.
.
.
$ CALL SUB2 "THIS IS P1" "THIS IS P2"
.
.
.
$ EXIT !Exit this command procedure file.
|
CALL 命令调用子例行程序 SUB1,并且把输出指引到文件 NAMES.LOG。子例行程序 SUB1 调用子例行程序 SUB2。这个过程执行 SUB2,使用执行过程 (@) 命令调用命令过程 FILE.COM。当 SUB1 中的所有命令执行后,在主过程中的 CALL 命令第二次调用 SUB2。SUB2
完成执行时,这个过程就退出。14.18 编写 Case 语句
Case
语句是特殊的格式的条件代码,从一组命令块中执行其中一个,这取决于变量或表达式的值,。通常,Case
语句的有效值是每个命令块开始的标号。使用指定的值作为 GOTO 语句的目标标号,Case
语句把控制传递到适当的代码块。
要编写 Case 语句,必须:
- 列出标号。
- 编写 Case 语句。
- 编写命令块。
14.18.1 列出标号
要列出标号,就使符号等同于一个包含一列用斜杠 (或您挑选用作定界符的任何字符)
定界的标号的字符串。这个符号定义应该在命令块之前。
以下例子使符号 COMMAND_LIST 等同于标号
PURGE、DELETE 和 EXIT:
$ COMMAND_LIST = "/PURGE/DELETE/EXIT/"
|
14.18.2 编写 Case 语句
要编写 Case 语句,遵循这个过程:
步骤 |
动作 |
1
|
使用 INQUIRE 命令获得 Case 变量的值。 |
2
|
使用 IF 命令与 F$LOCATE 和 F$LENGTH 确定 Case 变量的值是否有效。 |
3
|
如果 Case 变量有效,那么执行这个 Case 语句 (与 GOTO
命令) 把控制传递到适当的代码块。 否则,显示一条消息并退出,或者请求不同的 Case 值
。 |
在以下例子中,标号等于完整的命令名。因此,F$LOCATE
把定界符包括在它的命令名搜索中,以确保这个命令不是缩写的。
$GET_COMMAND:
$ INQUIRE COMMAND -
"Command (EXIT,PURGE,DELETE)"
$ IF F$LOCATE ("/"+COMMAND+"/",COMMAND_LIST) .EQ. -
F$LENGTH (COMMAND_LIST) THEN GOTO ERROR_1
$ GOTO 'COMMAND'
.
.
.
$ERROR_1:
$ WRITE SYS$OUTPUT "No such command as ''COMMAND'."
$ GOTO GET_COMMAND
|
14.18.3 编写命令块
每块命令可能包含一个或多个命令。用唯一标号开始每个命令块。把控制传递到在这列命令块之外的标号而结束每个命令块。
在以下例子中,每块命令开始于唯一标号 (PURGE:、DELETE:),并且把控制传递到一个在当前命令块之外的标号 (GOTO GET_COMMAND)
而结束:
$GET_COMMAND:
.
.
.
$PURGE:
$ INQUIRE FILE
$ PURGE 'FILE'
$ GOTO GET_COMMAND
$ !
$DELETE:
$ INQUIRE FILE
$ DELETE 'FILE'
$ GOTO GET_COMMAND
$ !
$EXIT:
|
14.19 编写循环
您可以编写在这个命令块的开始测试变量的循环 (如第 13 章所述)。然而,您也可以编写在循环的末端测试终止变量的循环,通过以下这个过程:
步骤 |
动作 |
1
|
开始循环。
|
2
|
执行循环体的命令。
|
3
|
更改终止变量。
|
4
|
测试终止变量。
如果条件不满足,则前去循环的开始。
|
5
|
结束循环。
|
注意,在循环末端测试终止变量时,在循环体中的命令至少执行一次,而不管终止变量的值。
在这个例子中展示的两个命令块执行一个当 COMMAND 等于 "EX" (EXIT) 时就终止的循环。F$EXTRACT 把 COMMAND
截短为它的前两个字符。在第一个例子中,在循环的开始测试终止变量 COMMAND;在第二个例子中,在循环末端测试。
$ ! EXAMPLE 1
$ !
$GET_COMMAND:
$ INQUIRE COMMAND-
"Command (EXIT,DIRECTORY,TYPE,PURGE,DELETE,COPY)"
$ COMMAND = F$EXTRACT(0,2,COMMAND)
$ IF COMMAND .EQS. "EX" THEN GOTO END_LOOP
.
.
.
$ GOTO GET_COMMAND
$END_LOOP:
|
$ ! EXAMPLE 2
$ !
$GET_COMMAND:
$ INQUIRE COMMAND-
"Command (EXIT,DIRECTORY,TYPE,PURGE,DELETE,COPY)"
$ COMMAND = F$EXTRACT(0,2,COMMAND)
.
.
.
$ IF COMMAND .NES. "EX" THEN GOTO GET_COMMAND
$ ! End of loop
|
要执行特定次数的循环,使用一个计数器作为终止变量。在以下例子中,用户输入 10 个文件名并放入局部符号 FIL1、FIL2、...、FIL10:
$ NUM = 1 ! Set counter
$LOOP: ! Begin loop
$ INQUIRE FIL'NUM' "File" ! Get file name
$ NUM = NUM + 1 ! Update counter
$ IF NUM .LT. 11 THEN GOTO LOOP ! Test for termination
$END_LOOP: ! End loop
.
.
.
|
以下例子使用一个计数器控制循环执行的次数。这个循环执行 10 次;在循环末端测试终止变量:
$! Obtain 10 file names and store them in the
$! symbols FILE_1 to FILE_10
$!
$ COUNT = 0
$ LOOP:
$ COUNT = COUNT + 1
$ INQUIRE FILE_'COUNT' "File"
$ IF COUNT .LT. 10 THEN GOTO LOOP
$!
$ PROCESS_FILES:
.
.
.
|
符号 COUNT 用来记录在循环内命令的执行次数。COUNT 也用来建立符号名 FILE_1、FILE_2,以此类推至 FILE_10。注意,COUNT
的值在循环开始递增,而在循环末端测试。因此,当 COUNT 从 9 增至 10 时,在 IF
语句发现假值之前执行最后一次循环 (获得 FILE_10 的值)。
要为已知顺序的值执行一个循环,使用 F$ELEMENT
词法函数。F$ELEMENT
词法函数从一列用定界符分隔的项目中获得项目。您必须提供项目数、项目定界符和这个列表作为 F$ELEMENT 的变元。
有关如何使用 F$ELEMENT 词法函数的详情,请参阅 OpenVMS DCL Dictionary。
在以下例子中,文件 CHAP1、CHAP2、CHAP3、CHAPA、CHAPB
和 CHAPC 被依次处理:
$ FILE_LIST = "1,2,3,A,B,C"
$ INDEX = 0
$PROCESS:
$ NUM = F$ELEMENT(INDEX,",",FILE_LIST)
$ IF NUM .EQS. "," THEN GOTO END_LOOP
$ FILE = "CHAP''NUM'"
$ ! process file named by FILE
.
.
.
$ INDEX = INDEX + 1
$ GOTO PROCESS
$END_LOOP:
$ EXIT
|
在以下例子中,命令过程使用一个循环把在符号 FILE_LIST 中列出的文件复制到另一个节点的目录中:
$ FILE_LIST = "CHAP1/CHAP2/CHAP3/CHAP4/CHAP5"
$ NUM = 0
$!
$! Process each file listed in FILE_LIST
$ PROCESS_LOOP:
$ FILE = F$ELEMENT(NUM,"/",FILE_LIST)
$ IF FILE .EQS. "/" THEN GOTO DONE
$ COPY 'FILE'.MEM MORRIS::DISK3:[DOCSET]*.*
$ NUM = NUM + 1
$ GOTO PROCESS_LOOP
$!
$ DONE:
$ WRITE SYS$OUTPUT "Finished copying files."
$ EXIT
|
F$ELEMENT 词法函数返回的第一个文件是 CHAP1,下一个文件是 CHAP2,以此类推。每循环一次,NUM 的值就加 1,以便获得下一个文件名。当
F$ELEMENT 返回一个斜杠时,就处理完 FILE_LIST 的所有项目,并且终止循环。14.20 使用 PIPE 命令
PIPE 命令在同一命令行中执行一个或多个 DCL 命令字符串。允许执行 UNIX
式样的命令处理,例如命令流水线、输入/输出重定向、条件和后台执行。
这种式样的命令处理支持互连网软件的开发和使用,它经常期望有些格式的流水线命令分析出现在主机和目标系统上。
以下几节描述使用 PIPE 命令的不同方法来执行 DCL
命令,如何中断 PIPE
命令和如何改进子进程性能。附有例子说明。
有关 PIPE 命令的完整信息,请参阅
OpenVMS DCL Dictionary: N--Z。
您可以在单个 PIPE 命令中指定多个 DCL
命令,然后顺序执行这些 DCL 命令。使用格式以下:
PIPE command-sequence ;
command-sequence [; command-sequences]...
|
14.20.1 使用 PIPE 命令执行条件命令
一个命令序列的执行有条件地依赖于上一个命令序列的执行结果。使用格式:
PIPE command-sequence1 && command-sequence2
|
注意,只有
command-sequence1 成功,才执行 command-sequence2。如果使用以下格式,则只有
command-sequence1 失败,才执行
command-sequence2。
PIPE command-sequence1 || command-sequence2
|
|