COBOL考古(八)

文件处理

前一章和实验侧重于变量和将文字值移动到变量中,然后使用COBOL DISPLAY语句编写变量内容。本节介绍将记录从文件中读取到变量中,将变量移动到输出变量中,然后将输出变量写入不同的文件。一个简单的COBOL程序用于读取文件中的每个记录,并将每个记录写入不同的文件,以说明从输入外部数据源读取记录并将记录写入输出外部数据源所需的COBOL代码。

有经验的COBOL程序员可以回答这个问题:“一个企业COBOL程序如何从输入外部数据源读取数据并将数据写入输出外部数据源?”本章的目标是提供足够全面的信息,以便读者能够回答这个问题。

  • 用于顺序文件处理的COBOL代码

    • COBOL输入和输出

    • FILE-CONTROL段

    • COBOL外部数据源

    • 数据集、记录和字段

    • ASSIGN子句

  • PROCEDURE DIVISION 顺序文件处理

    • 打开输入和输出进行读取和写入

    • 关闭输入和输出

  • 用于顺序读写记录的COBOL编程技巧

    • READ-NEXT-RECORD 段的执行

    • READ-RECORD 段

    • WRITE-RECORD 段

    • 对 READ-NEXT-RECORD 段的迭代处理

  • 实验

用于顺序文件处理的COBOL代码

用于顺序文件处理的COBOL代码包括:

  • 环境部分(ENVIRONMENT DIVISION)。

    • SELECT子句(SELECT clauses)

    • ASSIGN子句(ASSIGN clauses)

  • 数据部分(DATA DIVISION)。

    • FD语句(FD statements)
  • 过程部分(PROCEDURE DIVISION)。

    • OPEN语句(OPEN statements)

    • CLOSE语句(CLOSE statements)

    • READ INTO语句(READ INTO statement)

    • WRITE FROM语句(WRITE FROM statement)

COBOL输入和输出

环境部分(ENVIRONMENT DIVISION)和数据部分(DATA DIVISION)描述了在PROCEDURE DIVISION程序逻辑中使用的输入和输出。之前的章节介绍了在数据部分中的变量描述,并将文字值移动到定义的变量中。环境部分,尤其是INPUT-OUTPUT SECTION和FILE-CONTROL段,介绍了访问外部数据源的方式,其中来自外部数据源的数据被移动到定义的变量中。

FILE-CONTROL段

FILE-CONTROL段将每个COBOL内部文件名与外部数据集名称关联起来。在FILE-CONTROL段内,SELECT子句创建了一个内部文件名,而ASSIGN子句创建了一个外部数据集名称。图1显示了PRINT-LINE内部文件名与PRTLINE外部数据集名称关联,以及ACCT-REC内部文件名与ACCTREC外部数据集名称关联。标题为Assign Clause的部分进一步解释了SELECT ASSIGN TO的关系。

FILE-CONTROL

图1. FILE-CONTROL

虽然SELECT为内部文件提供了一个名称,ASSIGN为外部数据集名称提供了一个描述,但COBOL程序需要有关两者的更多信息。在DATA DIVISION的FILE SECTION中,提供了有关两者的更多信息。

COBOL保留字’FD’用于在FILE-SECTION中为COBOL内部文件名提供更多信息。FD语句下的代码是记录布局。记录布局包括级别号、变量名称、数据类型和长度,如图2所示。

FILE-SECTION

图2. FILE-SECTION

COBOL外部数据源

Enterprise COBOL源代码编译并在IBM Z主机硬件上执行,其中z/OS是操作系统软件。z/OS将数据存储在数据集和Unix文件中。z/OS包括许多数据存储方法。本章将重点介绍z/OS顺序数据存储方法。顺序数据集是记录的集合。

数据集、记录和字段

数据集包含多个记录。记录是数据集中的单行,具有定义的长度。每个记录可以细分为字段,其中每个字段具有定义的长度。因此,所有字段长度的总和将等于记录的长度。请参阅图3。

程序读取的每个记录可能导致磁盘存储访问。程序通常按顺序一次读取1个记录,直到读取所有记录为止。当读取记录时,从磁盘检索的记录存储在内存中供程序访问。每次读取下一个记录时,都需要从磁盘检索记录,这会对系统性能产生负面影响。记录可以被分组为块,块是一组记录。结果是当读取第一个记录时,假设程序将读取第二个、第三个等记录,整个块记录将被读入内存,避免不必要的磁盘检索和负面的系统性能。保存记录或记录块的内存,以供程序读取,被称为缓冲区。COBOL BLOCK CONTAINS子句可用于指定缓冲区中块的大小。请参阅图3。

Records, fields, and blocks

图3. 记录、字段和块

ASSIGN子句

虽然SELECT子句名称是内部文件名,但ASSIGN子句名称描述了程序外部的数据源。z/OS使用作业控制语言(JCL)操作来告诉系统要加载和执行的程序,然后是程序所需的输入和输出名称。JCL输入和输出名称称为DDNAMEs。JCL DDNAME语句包括一个JCL DD操作,其中DD是Data Definition的缩写。在同一个DDNAME语句中是系统控制的数据集名称。

COBOL代码“SELECT ACCT-REC ASSIGN TO ACCTREC”需要一个JCL DDNAME ACCTREC,其中DD将ACCTREC重定向到z/OS控制的数据集名称MY.DATA。示例1中显示了COBOL程序。

将ACCT-REC通过ASSIGN TO重定向到JCL DDNAME ACCTREC的目的是为了灵活性。ACCT-REC在程序中使用,ACCTREC是与JCL的桥梁,如示例1所示,DD JCL语句将ACCTREC连接到实际数据集,如示例2所示。这种灵活性允许同一个COBOL程序通过简单的JCL修改访问不同的数据源,而无需更改源代码以引用备用数据源。

1
SELECT ACCT-REC ASSIGN TO **ACCTREC**

示例1. COBOL程序

在执行期间,编译后的COBOL程序需要的JCL语句,用于将ACCTREC重定向到z/OS控制的数据集名称MY.DATA,如示例2所示。

1
//**ACCTREC**   DD  DSN=MY.DATA,DISP=SHR

示例2. JCL语句

总之,ACCT-REC是内部文件名。ACCTREC是外部名称,其中JCL DDNAME必须与COBOL ASSIGN TO ACCTREC名称匹配。在程序执行期间,JCL ACCTREC DDNAME语句被重定向到紧

随JCL DD操作之后的数据集名称。

ACCT-REC >>> ACCTREC >>> //ACCTREC >>> DD >>> MY.DATA

因此,COBOL内部文件名ACCT-REC从名为MY.DATA的顺序数据集中读取数据记录。

JCL是一个独立的z/OS技术技能。COBOL简介仅介绍了有关JCL的足够信息,以了解COBOL内部文件名如何定位外部顺序数据集名称。要了解更多关于JCL的信息,请访问IBM知识中心:

https://www.ibm.com/docs/en/zos-basic-skills?topic=collection-basic-jcl-concepts

PROCEDURE DIVISION 顺序文件处理

在COBOL程序运行时,SELECT ASSIGN TO JCL DDNAME是强制性的。如果ASSIGN TO名称未能与相同拼写的JCL DDNAME关联,运行时将发生程序运行时错误,当尝试执行OPEN操作时。运行时输出中将显示一条消息,指示未找到DDNAME。READ和WRITE操作依赖于成功完成OPEN操作。编译器无法检测运行时错误,因为编译器不知道实际运行时的JCL DDNAME数据集名称,该名称受OPEN、READ或WRITE操作的影响。数据记录字段的FD(File Descriptor)映射要求成功的OPEN操作才能由后续的READ或WRITE操作填充。

打开输入和输出进行读取和写入

必须打开COBOL输入和输出,以将所选的内部名称连接到分配的外部名称。图4打开文件名ACCT-REC作为程序输入和文件名PRINT-LINE作为程序输出。

OPEN-FILES

图4. 打开文件

关闭输入和输出

在程序完成或更好地说是在程序不再从内部文件名读取或写入时,应关闭COBOL输入和输出。图5关闭内部文件名ACCT-REC和内部文件名PRINT-LINE,然后停止处理,STOP RUN。

CLOSE-STOP

图5. 关闭和停止

顺序读写记录的COBOL编程技巧

在读取记录时,程序首先需要检查是否没有记录需要读取,或者是否没有更多记录需要读取。如果存在记录,那么读取的记录中的字段将填充由FD子句定义的变量名。COBOL使用PERFORM语句进行迭代。在计算机编程中,迭代用于描述可以多次执行一系列指令或语句的情况。通过该序列的一次通行称为迭代。迭代执行也称为循环。在其他编程语言中,使用“DO”或“FOR”语句进行迭代执行。COBOL使用PERFORM语句进行迭代执行。图6显示了PROCEDURE DIVISION中的四个由程序员选择的段落名称。

  • READ-NEXT-RECORD

  • CLOSE-STOP

  • READ-RECORD

  • WRITE-RECORD

READ-NEXT-RECORD反复执行READ-RECORD和WRITE-RECORD,直到遇到最后一条记录。当遇到最后一条记录时,将执行CLOSE-STOP以停止程序。

Reading and writing records

图6. 读写记录

注意:COBOL类似于英语,COBOL保留字也类似于英语。程序员可以自由使用类似于英语的变量名来帮助记住变量名的用途。PROCEDURE DIVISION的结构类似于英语。一个段落包含一个或多个句子。一个句子包含一个或多个语句。隐式的范围终结符,即句点(.),终止一个句子,或者终止多个连续的语句,这相当于复合句,其中“and”将可能是独立的句子连接在一起。

READ-NEXT-RECORD段是一种COBOL编程技巧,用于从顺序文件中读取所有记录,直到读取最后一条记录为止。该段包含一个由隐式范围终结符(.)终止的复合句,该终结符位于END-PERFORM语句后的单独行上。通过PERFORM UNTIL和END-PERFORM之间的明确范围终结符,将反复执行,直到LASTREC变量包含Y。第一个PERFORM READ-RECORD导致分支到READ-RECORD段。请参阅图7中的#1。

READ-RECORD段

READ-RECORD段执行COBOL READ语句,导致外部顺序文件填充与ACCT-REC内部文件名关联的变量。如果在读取的记录的“AT END”处,然后将Y移入LASTREC变量。READ语句由明确的范围终结符END-READ终止。该段由隐式范围终结符(.)终止。控制权返回到READ-NEXT-RECORD段,以执行下一个语句,即执行PERFORM WRITE-RECORD。

WRITE-RECORD段

WRITE-RECORD段包含由隐式范围终结符(.)终止的多个句子。MOVE语句导致将每个输入文件变量名移动到输出文件变量名。该段中的最后一个句子写入了输出文件变量名的集合,即PRINT-REC。

PRINT-REC分配给了PRTREC。使用JCL执行COBOL程序。关联的JCL PRTREC DDNAME将写入的输出重定向到z/OS控制的数据集名称等,使用JCL DD操作在JCL DDNAME语句上。请参阅图7中的#2。

一旦执行了WRITE-RECORD段中的所有语句,控制权就会返回到READ-NEXT-RECORD段,其中要执行的下一个语句是第二个PERFORM READ-RECORD语句。

同样,READ-RECORD段执行COBOL READ语句,导致外部顺序文件填充与ACCT-REC内部文件名关联的变量。如果在读取的记录的“AT END”处找到Y,然后将控制权返回给READ-NEXT-RECORD段。READ-NEXT-RECORD段将继续迭代过程,直到LASTREC变量中找到Y。请参阅图7中的#3。

Iterative processing

图7. 迭代处理

COBOL编程技巧用于按顺序读取和写入记录

在读取记录时,程序首先需要检查是否没有要读取的记录,或者是否没有更多要读取的记录。如果存在记录,则从外部顺序文件中读取的字段将填充由FD子句定义的变量名。COBOL使用PERFORM语句进行迭代。在计算机编程中,迭代用于描述可以多次执行的一系列指令或语句的情况。一次通过序列被称为一个迭代。迭代执行也称为循环。在其他编程语言中,’DO’或’FOR’语句用于迭代执行。COBOL使用PERFORM语句进行迭代执行。图6显示了PROCEDURE DIVISION中的四个由程序员选择的段落名称。

  • READ-NEXT-RECORD

  • CLOSE-STOP

  • READ-RECORD

  • WRITE-RECORD

READ-NEXT-RECORD重复执行READ-RECORD和WRITE-RECORD,直到遇到最后一条记录。当遇到最后一条记录时,CLOSE-STOP被执行,停止程序的运行。

Figure 6. Reading and writing records

注意:COBOL类似于英语,COBOL保留字也类似于英语。程序员可以自由使用类似于英语的变量名,以帮助记住变量名的用途。PROCEDURE DIVISION的结构类似于英语。一个段落包含一个或多个句子。一个句子包含一个或多个语句。隐含的范围终结符,句号(.),用于终止句子或终止多个连续的语句,这相当于将潜在的独立句子连接在一起的复合句,其中“and”将潜在的独立句子连接在一起。

READ-NEXT-RECORD段是一种用于从顺序文件读取所有记录的COBOL编程技巧,直到读取到最后一条记录为止。该段包含一个由隐式范围终结符(.)终止的复合句,该终结符在END-PERFORM语句之后的单独一行上。通过PERFORM UNTIL到END-PERFORM,显式的范围终结符,重复执行,直到LASTREC变量包含Y。首次执行PERFORM READ-RECORD会导致跳转到READ-RECORD段落。请参见图7中的#1。

READ-RECORD段

READ-RECORD段执行COBOL READ语句,导致外部顺序文件填充与ACCT-REC内部文件名关联的变量。如果“AT END”处于读取的记录之后,将Y移入LASTREC变量。READ语句由显式范围终结符END-READ终止。该段由隐式范围终结符(.)终止。控制返回到READ-NEXT-RECORD段,以执行下一个语句,即PERFORM WRITE-RECORD。

WRITE-RECORD段

WRITE-RECORD段包含多个由隐式范围终结符(.)终止的句子。MOVE语句导致将每个输入文件变量名移动到输出文件变量名。段的最后一个句子写入输出文件变量名的集合,即PRINT-REC。

PRINT-REC分配给PRTREC。使用JCL执行COBOL程序。相关联的JCL PRTREC DDNAME通过JCL DD操作将写入的输出重定向到z/OS控制的数据集名称等。请参见图7中的#2。

一旦执行WRITE-RECORD段中的所有语

句,控制就会返回到READ-NEXT-RECORD段,下一个要执行的语句是第二个PERFORM READ-RECORD语句。

同样,READ-RECORD段执行COBOL READ语句,导致外部顺序文件填充与ACCT-REC内部文件名关联的变量。如果“AT END”处于读取的记录之后,将Y移入LASTREC变量,然后返回控制到READ-NEXT-RECORD段。READ-NEXT-RECORD段将继续进行迭代处理,直到LASTREC变量中找到Y。请参见图7中的#3。

Figure 7. Iterative processing

实验

下面是与本章节相关的实验,演示了用于从顺序文件中读取所有数据记录的“文件结束”COBOL编码技巧。如果某个步骤旁边有星号(*),则它将在实验内容的末尾具有相关的提示。

  1. 如果尚未打开VS Code,请选择左侧边栏中的Zowe Explorer。

    注意:如果您正在打开VS Code的新实例(即在之前使用后关闭了它),则可能需要再次“选择筛选器”。您可以通过在数据集部分中选择左侧边栏中的命名连接旁边的搜索图标!,然后重新选择以前使用的筛选器。在您选择搜索符号后,它应该出现在列出的筛选器中。

  2. 查看id.CBL数据集中列出的以下COBOL源代码成员:

    • CBL0001

    • CBL0002

  3. 查看id.JCL数据集中列出的以下三个JCL成员:

    • CBL0001J

    • CBL0002J

    • CBL0003J

    Figure 8. Id.JCL(CBL0001J).jcl

  4. 在DATA SET部分内提交作业JCL(CBL0001J)。

  5. 使用JOBS部分查看该作业的输出。

    • COBRUN:SYSPRINT(101) - COBOL程序编译器输出

    • RUN:PRTLINE(103) - COBOL程序执行输出,如图9所示。

    Figure 9. RUN:PRTLINE(103) for JCL(CBL0001J)

  6. 在DATA SET部分内提交作业JCL(CBL0002J)。

  7. 使用JOBS部分查看该作业的输出。

    • COBRUN:SYSPRINT(101) - COBOL程序编译器输出

    在步骤7中的输出文件中查找COBOL编译器的严重消息IGYPS2121-S,如图10所示。

    Figure 10. IGYPS2121-S message

  8. 编辑CBL(CBL0002):

    • 确定PRINT-REX的正确拼写,然后在源代码中进行更正,并保存更新后的源代码。
  9. 使用DATA SET部分重新提交作业JCL(CBL0002J),并在JOBS部分查看输出。

    • COBRUN:SYSPRINT(101) - COBOL程序编译器输出

    • RUN:PRTLINE(103)是COBOL程序执行输出(如果更正成功)

  10. 使用DATA SET部分提交作业JCL(CBL0003J)。

  11. 使用JOBS部分查看CBL0003J ABENDU4038输出:

    • 从COBOL程序执行输出中查看IGZ00355异常消息,位于RUN:SYSOUT(104)中。

    • IGZ00355消息显示,程序无法打开或关闭ACCTREC文件名,如图11所示,引导您找到错误的根本原因。

      Figure 11. RUN:SYSOUT(104) message

  12. 通过编辑JCL(CBL0003J)来修复此错误:

    • 确定所需的DDNAME,但缺少或拼写错误。

    • 在代码中进行更正,并保存。

  13. 使用DATA SET部分重新提交作业JCL(CBL0003J)。

  14. 使用JOBS部分查看CBL0003J输出,您的输出应如图12所示。

    • RUN:PRTLINE - COBOL程序执行输出(如果更正成功)

Figure 12. RUN:PRTLINE(103) for JCL(CBL0003J)

实验提示

  1. 错误位于第11行,请相应地调整’ACCTREX’。请参见图13。

Figure 13. Error in id.JCL(CBL0003J).jcl