COBOL:简单文件读取问题

4

我有一个非常基础的COBOL程序,它读取一个名为input.dat的文件,并将其简单地输出到控制台。 input.dat 文件长这样:

John                Johnson             
Peter               Peterson            
Juliette            Julietteson         
Natasha             Natashason          
Justin              Justinson           

虽然这里显示不正确,但我确定名字的第一个和最后一个字母分别有20个字符

这是我的COBOL程序:

    IDENTIFICATION DIVISION.
    PROGRAM-ID. ATEST4.
    ENVIRONMENT DIVISION.
    INPUT-OUTPUT SECTION.
    FILE-CONTROL.
        SELECT INPUTFILE ASSIGN TO "files/input.dat".
    DATA DIVISION.
    FILE SECTION.
    FD  INPUTFILE LABEL RECORDS ARE OMITTED.
    01  INPUTRECORD              PIC X(40).    
    WORKING-STORAGE SECTION.
    01  FILE-STATUS              PIC 9 VALUE 0.
    PROCEDURE DIVISION.
    001-MAIN.
        OPEN INPUT INPUTFILE.
        PERFORM 002-READ UNTIL FILE-STATUS = 1.
        CLOSE INPUTFILE.
        STOP RUN.
            
    002-READ.
        READ INPUTFILE
            AT END MOVE 1 TO FILE-STATUS
            NOT AT END DISPLAY INPUTRECORD
        END-READ.  

相反,输出看起来像这样:
John                Johnson             
Peter               Peterson            
Juliette            Julietteson         
Natasha             Natashason          
Justin              Justinson           
ustin              Justinson       

最后一行似乎是前一行的复制品,缺少第一个字符和几个尾随空格(总共35个字符)。

这是为什么呢?看起来像是对AT END子句的误解,但我无法解决它。

编辑:按建议更新了编译器。结果仍然相同。如果有帮助,这是我的输入文件的链接。


我不熟悉opencobol,但我记得cobol的WRITE FROM是从工作区存储器中提取FROM,而不是输入文件描述符。 - Gilbert Le Blanc
@GilbertLeBlanc 我也使用了那种方法,但输出结果是一样的。 - Alvaro
@GilbertLeBlanc 我也删除了写入部分,将输出到控制台,结果相同...也许我需要在文件上加上一些EOF标记? - Alvaro
请检查您的输入数据。最后一行似乎只有35个字符,包括尾随空格。使用GnuCOBOL 2.0.0(尚未发布),您粘贴的输入数据也会产生奇怪的输出。将最后一个输入记录设置为40个字符会产生预期的输出。您应该检查您的文件,并下载至少1.1版(当前版本)或2.0.0版(下一个版本的基础)。 - Bill Woodger
@BillWoodger 如果您愿意,可以将评论重新表述为答案,我会接受它。 - Alvaro
显示剩余7条评论
2个回答

6

好的,错过了一招或两招。您正在使用40字节的定长记录。当您使用定长记录时,与行顺序不同,在读取时没有单个尾随空值剥离和在写入时没有空值附加。

我还从问题中复制了您的数据,它以包括空记录分隔符的40字节记录形式到达我这里。

现在我有了您的真实数据...

您有五条长度为41的记录,而不是五条长度为40的记录。如果您将其视为数据块,COBOL程序将每次读取40个字节,这将给您五条长度为40的记录和一条长度为5的记录。

在没有将空值附加到记录的情况下,我应该将所有输出数据看作一个长行。但是我没有。为什么?

这次,随着“长”记录,除第一条记录外,所有记录都有前导记录分隔符为空。

以下是一些供您测试的数据:

John                Johnson   0123456789
Peter               Peterson  0123456789
Juliette            Julietteson123456789
Natasha             Natashason0123456789
Justin              Justinson 0123456789
1234511111111111111111111111111111111110

每个记录都包含40字节的数据,后面跟着一个空记录终止符。

这是您修改后的程序,可以编译并运行数据。我使用`cobc -x -free prog.cob`来修复从问题中粘贴的列(从SO中粘贴不太适合COBOL)。由于它是有缺陷的,所以我没有特别关注新内容的位置。

DISPLAYs中的">"和"<"的目的是限定字段的边界。然后,您可以通过识别空值来确定它们的位置,因为空值会导致中断。

IDENTIFICATION DIVISION.
PROGRAM-ID. FILE-TEST.
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT INPUTFILE ASSIGN TO "files/input.dat"
file status is fs1.
SELECT OUTPUTFILE ASSIGN TO "files/output.dat"
file status is fs2.
DATA DIVISION.
FILE SECTION.
FD INPUTFILE
    LABEL RECORDS ARE OMITTED
    record is varying depending on record-length.
01 INPUTRECORD PIC X(40).
FD OUTPUTFILE
    LABEL RECORDS ARE OMITTED.
01 OUTPUTRECORD PIC X(40).
WORKING-STORAGE SECTION.
    01 EOF PIC 9 VALUE 0.
    01  fs1 pic xx.
        88  fs1-ok value zero.
        88  fs1-eof value "10".
    01  fs2 pic xx.
        88  fs2-ok value zero.
    01 CUSTOMER.
        02 FIRST-NAME PIC X(20).
        02 LAST-NAME PIC X(20).
    01  rec-count comp-3 pic 999 value zero.
PROCEDURE DIVISION.
    001-MAIN.
        OPEN INPUT INPUTFILE OUTPUT OUTPUTFILE.
        if not fs1-ok
            display "bad fs1 O>" fs1 "<"
        end-if
        if not fs2-ok
           display "bad fs2 O>" fs2 "<"
        end-if
        PERFORM 002-READWRITELOOP UNTIL EOF = 1.
        CLOSE INPUTFILE. 
       if not fs1-ok
           display "bad fs1 C>" fs1 "<"
       end-if
       CLOSE OUTPUTFILE.
       if not fs2-ok
           display "bad fs2 C>" fs2 "<"
       end-if
       STOP RUN.

   002-READWRITELOOP.
       READ INPUTFILE INTO CUSTOMER
           AT END MOVE 1 TO EOF
              display "at end"
       if not fs1-ok
           display "bad fs1 R>" fs1 "< " rec-count
       end-if
           NOT AT END WRITE OUTPUTRECORD FROM CUSTOMER
      DISPLAY ">" CUSTOMER "<"
      DISPLAY ">" inputrecord "<"
      add 1 to rec-count
       display rec-count
       if not fs1-ok
           display "bad fs1 R>" fs1 "< " rec-count
       end-if
       if not fs2-ok
           display "bad fs2 W>" fs2 "<"
       end-if
       END-READ
      .

我将尝试理解为什么最后一行会像原始文本中那样出现,并在GnuCOBOL讨论区中讨论。解决方法:要么在SELECT语句中使用LINE SEQUENTIAL并保留数据,要么删除数据中的所有空行/换行符。这两种方法都将为输入中的6条记录提供40个字节的正确对齐数据。我通过在两个SELECT语句中添加FILE STATUS子句来更改您的原始程序。我测试了每个IO(OPEN、CLOSE、READ和WRITE)之后我定义的文件状态字段。OPEN和CLOSE给出“00”的文件状态,这是预期的。前四个READ给出“00”的文件状态,这是预期的。五个WRITE给出“00”的文件状态,这是预期的。第五个READ给出“04”的文件状态。这意味着:
A READ statement was successfully executed, but the length of the record being processed did not conform to the fixed file attributes for that file.

所以,预期的情况。无论是AT END还是NOT AT END都不涉及这个问题。
如果您使用了 FILE STATUS,您的程序就可以知道您已读取了短记录或长记录。
如果只执行了五个 WRITE 语句,为什么会有六个输出记录呢?
好吧,因为你有35字节的数据加上一个“新行”,因为新行只会在第40个字节后被削减,当你显示数据时,你会得到两行。在文件中,有一个“记录”,但它有一个嵌入的换行符。我使用了cat而不是显示十六进制值的编辑器,所以看到了一个“第六条记录”,然后是一个文本编辑器,再次看到了第六条记录。
为什么你会看到一个几乎完整的“第六个记录”我不知道,但这是历史性的兴趣。如果你想查看 OpenCOBOL 源代码以尝试发现原因,你可以在 GnuCOBOL 网站的文件部分找到它。
在 GnuCOBOL 中,你可以用嵌入的空格来显示或写入40字节的字段。DISPLAY 总是在嵌入的“null”值上换行,这给了我表面上的35字节后跟四字节的记录,第40个字节(实际上是第36个)是“不可见”的 null 值。
直到你使用一些期望数据为文本而不是二进制的东西来“查看”文件,WRITE 才会导致嵌入 null 的换行。
在 GnuCOBOL 中,“问题”不是问题,因为 DISPLAY 的工作方式(期望文本数据而不是二进制数据)或者如果使用 WRITE,那么你就需要“查看”文件的方式。
您得到的实际 OpenCOBOL 输出实际上是一个 bug,但它不能在 GnuCOBOL 中复现。
您的程序输出的 GnuCOBOL 输出可以解释为最后一条记录有35个字节的数据。我得到的空格是由于 COBOL 在将较短的字段移动到较长的字段时进行“填充”。READ ... INTO ...包含了一个隐式 MOVE,所以你会得到填充的结果。
如果您只是使用 FD 下的数据区域中的记录,您将得到更加不可预测的结果。
所以它确实使问题与主题相关。我想。因此问题应该保留,因为其他人几乎肯定会在某个时候遇到类似的问题。问题是使用非文本数据的 DISPLAY 或使用仅限文本的工具查看输出文件。还是表示这最后一点意味着使用 WRITE 会使其成为无关话题?:-)
解决方法有两个。升级到 GnuCOBOL。始终使用 FILE STATUS,并在每次 IO 后始终检查(每个文件一个最好的)正确的文件状态字段,并在发生意外情况时采取一些明智的行动。
文件结束时的文件状态值为“10”。我将88添加到文件状态字段,并始终使用“10”进行文件结束检查。我从不使用AT END / NOT AT END的混乱。
如果您使用了Gilbert的建议,带有引导读取且没有INTO,我认为您会得到不同的结果,这将有助于解决问题。引导读取(在进入读取循环之前始终有当前记录,然后将下一个记录(或获取文件结束)作为循环中的最后一个逻辑事情)是更“COBOL”方式做事情。就像FILE STATUS和文件状态字段上的88一样,并且每次都要检查。
您还可以查看在SELECT上使用LINE SEQUENTIAL。这是Linux / Unix / Windows更自然的文件类型,“记录”则已知为分隔符,然后您将获得40、40、40、40和35字节的有效记录。
然后您有可变长度记录,需要知道如何处理这些记录。
这种类型的问题,数据问题,通常不在Stack Overflow上讨论。
但是,行为不正确,有理由期望您不会获得最后一个记录的最后五个字节(因为您最初使用了READ INTO)。但是,这样的数据错误不应使您的程序认为存在额外的记录。
我将在GnuCOBOL讨论页面上提出此问题,其中(披露)我是Moderator。
我建议您升级到GnuCOBOL 1.1.0,因为它比OpenCOBOL 1.1具有更多的错误修复,并且正在积极开发(GnuCOBOL是OpenCOBOL的新名称,因此不再开发OpenCOBOL本身)。
关于您的代码有几点要注意。
在COBOL程序中,先前由Gilbert LeBlanc提供的结构对于使用实际SELECT语句上的FILE STATUS比使用AT END及其相关项要好得多。每次IO之后测试文件状态(最好是使用88),以便尽快识别问题。我将测试这是否会在这种情况下有所帮助。

不同意,因为数据问题不是问题的初始条件。 - Alvaro
不同意什么?如果唯一的问题是数据,那么这个问题就不在话题范围内。因为不只是数据有问题,所以这个问题是在话题范围内的。或者你是说数据没问题,那么问题就无法用GnuCOBOL重现。 - Bill Woodger
我想说的是,程序期望的数据结构是一个相关话题。即使逻辑正确,我也提供了一个意外的格式。如果必须解释为什么它不属于编程网站,请解释原因。 - Alvaro
如果你搞砸了测试日期,你应该自己解决。这不会帮助任何其他人(他们没有你的数据)。修复数据,不是编程问题。这就像打字错误一样。如果你想确认这一点(当然我可能错了),你可以在 Meta 网站上提问(从你的个人资料中选择 Meta 图标)。我以前曾投票关闭有关数据打字错误的问题。我希望如果你的数据完全混乱,你会查看文件并修复错误。只是在这种情况下,似乎出现了一些问题。 - Bill Woodger
我们的想法不同,仅此而已。顺便说一句:我对另一个答案进行了负评,因为它质量很差,没有别的原因。仅仅发布代码是无助于任何人的,特别是如果更改似乎微不足道,而且你没有解释为什么要这样做(例如,你关于88个值的评论包含比他的回答更多的信息)。 - Alvaro
好的,现在有一个答案了。我认为你对吉尔伯特的回答过于草率并且给他投了反对票。这就是生活。 - Bill Woodger

0
在002-Read中,文件状态被设置为文件末尾。直到段落结束之前,它才会被检查,所以无论缓冲区中有什么内容都会被打印出来,因此最后一条记录会重复显示。
如果你修改你的代码为 读取INPUTFILE 在结尾处,将1移动到FILE-STATUS 否则 显示INPUTRECORD 结束读取。

“ELSE”需要一个“IF”。你发布的代码中在哪里可以找到“IF”? - Rick Smith

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接