I have written the following COBOL program:
*************************************************************
* VERKOOP
*************************************************************
IDENTIFICATION DIVISION.
PROGRAM-ID. VERKOOP.
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT PRODUCTEN ASSIGN TO "BESTANDEN/PRODUCTEN"
ACCESS MODE IS RANDOM
ORGANIZATION IS INDEXED
RECORD KEY IS PRODUCTID
FILE STATUS IS WS-FILE-STATUS.
DATA DIVISION.
FILE SECTION.
FD PRODUCTEN BLOCK CONTAINS 10 RECORDS.
01 PRODUCT.
02 PRODUCTID PIC X(6).
02 LEVERANCIERID PIC X(6).
02 AANTAL PIC 9(6).
WORKING-STORAGE SECTION.
77 FOUT PIC X.
88 PRODUCT-NIET-GEVONDEN VALUE 1.
77 WS-PRODUCTID PIC X(6).
77 WS-AANTAL PIC 9(6).
77 WS-FILE-STATUS PIC XX.
LINKAGE SECTION.
01 LS-PRODUCTID PIC X(6).
01 LS-AANTAL PIC 9(6).
PROCEDURE DIVISION.
* USING LS-PRODUCTID, LS-AANTAL.
MAIN.
PERFORM INITIALISEER
PERFORM LEES-PRODUCT-IN
PERFORM LEES-BESTAND
PERFORM SLUIT-BESTAND
STOP RUN.
INITIALISEER.
MOVE ZEROS TO PRODUCT
OPEN I-O PRODUCTEN.
* DISPLAY WS-FILE-STATUS..
LEES-PRODUCT-IN.
* MOVE LS-PRODUCTID TO WS-PRODUCTID
* MOVE LS-AANTAL TO WS-AANTAL.
DISPLAY "GEEF PRODUCTID OP: "
ACCEPT WS-PRODUCTID
DISPLAY "GEEF AANTAL OP: "
ACCEPT WS-AANTAL.
LEES-BESTAND.
* DISPLAY "LEES-BESTAND"
MOVE WS-PRODUCTID TO PRODUCTID
* DISPLAY PRODUCTID
READ PRODUCTEN INVALID KEY SET PRODUCT-NIET-GEVONDEN TO TRUE
END-READ
DISPLAY "END-READ" WS-FILE-STATUS
IF PRODUCT-NIET-GEVONDEN PERFORM FOUTJE
ELSE
MOVE WS-PRODUCTID TO PRODUCTID
SUBTRACT WS-AANTAL FROM AANTAL
PERFORM UPDATE-PRODUCT
END-IF.
UPDATE-PRODUCT.
REWRITE PRODUCT INVALID KEY PERFORM FOUTJE.
SLUIT-BESTAND.
* DISPLAY "SLUIT-BESTAND"
CLOSE PRODUCTEN.
FOUTJE.
DISPLAY "ER IS EEN FOUT OPGETREDEN"
* DISPLAY WS-FILE-STATUS
STOP RUN.
The idea is that I find a product by its productid in the file PRODUCTEN.dat and subtract the amount (aantal) by a given number. However everytime I run it I get the following error: WARNING - Implicit CLOSE of PRODUCTEN <"BESTANDEN/PRODUCTEN">. I don't really see the problem, the WS-FILE-STATUS line even gives me back a 00 status. I am 100% sure the product is in the file so I'm not trying to subtract from a non-existing product or anything.
UPDATE: I fixed it by assign PRODUCTEN to a newly declared file as the last one (somehow) got corrupt and was behaving in an unintended way.
To get that Implicit Close message, you must have a STOP RUN before you close the file.
You have a STOP RUN in paragraph FOUTJE, before the file is closed, so paragraph FOUTJE is being used.
You use paragraph FOUTJE in a PERFORM when PRODUCT-NIET-GEVONDEN is true.
PRODUCT-NIET-GEVONDEN is set to true on the INVALID KEY of the READ.
So INVALID KEY is true.
You get a FILE STATUS of ZERO. Unexpected, but fits what you have presented.
I don't have COBOL-IT and I don't know what OS you are using.
I also don't know in your set-up what a READ of a keyed file which does not explicitly reference a key does.
I don't know in any set-up, because I don't do it. If I'm doing a keyed read, I always specify the key.
I don't put data in the key on the file. I use a WORKING-STORAGE field for the key.
Why, well, implementation-dependent for the compiler, but unless your file is OPEN and unless there is a current record on the file, then the content, even the address, of a file record is/can be (implementation dependent) undefined.
As far as I am concerned, the KEY on the SELECT is to define the presence of the key on the file. The key you are using to READ the file obviously comes from elsewhere.
So, I would remove these:
MOVE ZEROS TO PRODUCT
MOVE WS-PRODUCTID TO PRODUCTID
I'd change this to include the KEY of WS-PRODUCTID
READ PRODUCTEN INVALID KEY SET PRODUCT-NIET-GEVONDEN TO TRUE
I'd not use INVALID KEY, I'd just use the value of WS-FILE-STATUS, which I'd expect to be "23" for "not found". I'd do the test with an 88. You then don't need your "flag" (FOUT and PRODUCT-NIET-GEVONDEN) anyway. Check the FILE STATUS field after each IO. This time you spelled your filename correctly, another time you won't and you may waste more time chasing your tail.
Work on consistent indentation, it will make your program easier to read, for you, and anyone else.
If you want to use DISPLAY to verify the logical path, you need to DISPLAY the value which is used to determine the logical path (FOUT in this case).
There are two "formats" of the READ statement. One is for sequential reads, one is for reads using a key. When each is reduced to its mandatory-only content, they are identical. Therefore it is not clear, per compiler, which type of READ is the default (when not explicit) or when it is the default (per file). So I always make it explicit:
READ PRODUCTEN KEY IS WS-PRODUCTID
I would then use the FILE STATUS field to determine whether the key was read (00 in the status) or not found (23) or something else (something else).
NOTE: This Answer as a resolution to your problem only works if everything is as you have described. Further information may invalidate this Answer as a Resolution.
The Answer does work as a generally clearer (therefore better) way to code your COBOL program.
Turns out to have been a suspected corrupted file. This may have caused a disparity between INVALID KEY and FILE STATUS, but in the normal course of events that is not going to happen. It is the only thing which fits all the evidence, but this is an exceptional case, perhaps not able to reproduce without the exact-same file corruption and clutching at this straw in a general case for why a given program is not working is probably the first refuge of a scoundrel.
Related
Hi i am learning cobol from tutorialpoints and every program from there works as i've tested them in OpenCobolIDE(some needed a little editing). Then i came across the File Handling chapter and in there the program had a lot of errors. I did manage to rewrite the program until it didn't show me any errors but it doesn't do anything.
Here's my code:
IDENTIFICATION DIVISION.
PROGRAM-ID. HELLO.
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT STUDENT ASSIGN TO
'C:\Cobol\FIle Handling\input.txt'
ORGANIZATION IS INDEXED
ACCESS IS RANDOM
RECORD KEY IS STUDENT-ID
FILE STATUS IS FS.
DATA DIVISION.
FILE SECTION.
FD STUDENT.
01 STUDENT-FILE.
05 STUDENT-ID PIC 9(5).
05 NAME PIC A(25).
WORKING-STORAGE SECTION.
01 WS-STUDENT-FILE.
05 WS-STUDENT-ID PIC 9(5).
05 WS-NAME PIC A(25).
01 FS PIC 9(02).
PROCEDURE DIVISION.
OPEN I-O STUDENT.
MOVE 20005 TO STUDENT-ID.
READ STUDENT RECORD INTO WS-STUDENT-FILE
KEY IS STUDENT-ID
INVALID KEY DISPLAY 'Invalid Key'
NOT INVALID KEY DISPLAY WS-STUDENT-FILE
END-READ.
CLOSE STUDENT.
STOP RUN.
This is the text file:
20003 Mohtashim M.
20004 Nishant Malik
20005 Amitabh Bachhan
The result should be the text:
20005 Amitabh Bachhan
It's doing something: It's reading the file. But that's all; you didn't ask for it to display or do anything else beyond reading the record into memory. You might want to look at using the DISPLAY statement or maybe create another file to write the output to.
Might I make a couple of suggestions?
In modern COBOL, stylistically, you don't put a period after everything in the procedure division -- you only put it in where it is necessary. For example:
PROCEDURE DIVISION.
OPEN I-O STUDENT
MOVE 20005 TO STUDENT-ID
READ STUDENT RECORD INTO WS-STUDENT-FILE
KEY IS STUDENT-ID
INVALID KEY DISPLAY 'Invalid Key'
NOT INVALID KEY DISPLAY WS-STUDENT-FILE
END-READ
CLOSE STUDENT
STOP RUN
.
Although the compiler doesn't care about spaces and returns, if I were you, I'd try to indent my code a bit better (I like how I indented the above :-) ). It's up to you and a lot of people like to do it differently, but if you are consistent you can spot problems that might sneak through your code.
Edit: I didn't notice that you were reading with a key from a text file. So, either you need to:
read from a pre-built indexed file, or
read the file sequentially and search for what you want by comparing what you read for the student id you wanted.
I have fixed-point numbers in file, one in each line, in this format S9(6)V9(2) but when they are actually read, I'm getting non numeric errors while trying to put them in math operations. What is more, when I try to display them in program, a number that is written in file as 567123.45 is saved in variable as +567123.04. And for example the number from file 123.45 is saved in variable as +123.45.00 and it provokes the following error 'WS-VALUE' not numeric: '123.45 0' during a math operation. Why is that?
I'm using OpenCobolIDE 4.7.4 for Windows.
EDIT:
File has records of the following form separated by new lines (read by READ operation record after record):
01 WS-OPERATION.
05 WS-ID PIC A(2).
05 WS-CLIENT PIC 9(5).
05 WS-COUNTRY PIC A(4).
05 WS-VALUE PIC S9(6)V9(2).
The reason is that you try to un-edit a field. 567123.45 in the data is not conforming to PIC S9(6)V9(2) but to -9(6).9(2). - internal stored data vs. print-data.
Simply changing the definition and use MOVE WS-VALUE TO WS-VALUE-INTERNAL (which is defined like you want to) may work with a specific compiler (and specific data) but I'd go a different route:
I'd suggest to always validate the data before doing something with it (the file may be broken or external edited).
At least check the simple numeric data like WS-CLIENT for IS NUMERIC and either do a full validation on the data field WS-VALUE or at least use MOVE FUNCTION NUMVAL(WS-VALUE) TO WS-VALUE-INTERNAL.
I am fairly new to Cobol and am writing a basic application to get started with the language. Right now it is a 100% console application and I ran into a problem that isn't really a problem unless the user inputs the wrong data... And after not being able to find an answer to my "problem" through google I can't seem to get it out of my head, hence the thread here.
I have a variable USER-RECORD X(4) and during run-time I ask the user to enter his/her user record. Let's say they enter '1234', then 1234 is being saved and stored in a file along with the other data being requested. However, if the user were to enter "11234" by mistake, then the program will store 1123 instead of 1234, which is wrong because of the wrongly entered data.
My question is if it is possible to surround the ACCEPT verb with some kind of statement, or "catch" the incoming data in some way that if the data being sent is larger than 4 characters something will happen?
It is probably smarted to handle these things afterwards making the user confirm the data and all, but I can't seem to get this out of my head. Is this possible at all?
01 USER-RECORD PIC X(4).
01 USER-RECORD-REDEFINED REDEFINES USER-RECORD PIC 9(4).
GET-USER-INPUT.
MOVE 'N' TO WS-NUMERIC.
PERFORM UNTIL WS-NUMERIC = 'Y'
DISPLAY 'ENTER YOUR 4 DIGIT RECORD NUMBER: ' NO ADVANCING
ACCEPT USER-RECORD *>---THIS IS WHERE MY QUESTION LIES---
IF USER-RECORD-REDEFINED IS NUMERIC
MOVE 'Y' TO WS-NUMERIC
ELSE
DISPLAY 'VALUE WAS NOT NUMERIC OR 2 SHORT, TRY AGAIN'
END-PERFORM.
Your Micro Focus compiler supports the SCREEN SECTION. If you use a SCREEN, then your data can only be entered up to the size of the field.
See the FORMAT 4 of ACCEPT in the Micro Focus documentation here, https://supportline.microfocus.com/documentation/books/nx51ws01/nx51indx.htm, and from there locate the documentation of the SCREEN SECTION and other documentation about using screens in Micro Focus COBOL.
ACCEPT and DISPLAY are the COBOL verbs which suffer the most difference from compiler to compiler. When COBOL was originally developed, the idea of a user sitting at a screen to interact with a program was, let's say, futuristic.
Assuming that you have already seen the effect you describe and want to use a very simple, and standard-ish, format of ACCEPT, I'd suggest this:
01 USER-RECORD PIC X(80).
01 FILLER REDEFINES USER-RECORD.
05 USER-ID-give-it-a-good-name PIC 9(4).
05 FILLER PIC X(76).
88 USER-RECORD-EXTRAS-BLANK VALUE SPACE.
SET USER-RECORD-EXTRAS-BLANK TO TRUE
ACCEPT USER-RECORD
[your existing code using the nice new name]
IF NOT ( USER-RECORD-EXTRAS-BLANK )
[do something for also an error]
END-IF
The user may be able to circumvent this by idly using the spacebar to get 76 blanks, but probably not worth dealing with that.
I have written a code to match the date given by user(instream data) with the date mentioned in an input file. When the date matches, the record is moved to an output file. The program ran successfully. Now i am inserting a header in output file by declaring a variable 'header' in working-storage section, moving the 'header' field to output record and writing the output record. Though it is running successfully i have a doubt. I looked for it everywhere but unable to find it. The problem is-
The concept of buffer tells us that while declaring File Description(FD) it creates a structure. The file description(FD) given with all the records and fields creates a buffer structure. Now all the read/write operation happens through it.
If a program processes more than one file, a record buffer must be defined for each file.
To process all the records in an INPUT file, we must ensure that each record instance is copied from the file, into the record buffer, when required.
To create an OUTPUT file containing data records, we must ensure that each record is placed in the record buffer and then transferred to the file.
To transfer a record from an input file to an output file we must read the record into the input record buffer, transfer it to the output record buffer and then write the data to the output file from the output record buffer.
So when i am giving the date separately and moving it to the output record it is SURELY not going into that buffer which i created using FD in output record. So how is it going there?? If it is creating its own buffer then all data can be moved directly, what is the need of declaring FD??
This is the link to see the concept of buffer
[1]: http://www.csis.ul.ie/cobol/course/SequentialFiles1.htm
This is my code as given below-JUST EMPHASIZE ON STATEMENTS IN SLASHES
IDENTIFICATION DIVISION.
PROGRAM-ID. TXNRPT02 .
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT D01-TXN-FILE ASSIGN TO DETLFILE
ORGANIZATION IS SEQUENTIAL
ACCESS MODE IS SEQUENTIAL
FILE STATUS IS WS-INSERT-FILE-STATUS1.
SELECT D02-TXN-FILE ASSIGN TO DATEOUT
ORGANIZATION IS SEQUENTIAL
ACCESS MODE IS SEQUENTIAL
FILE STATUS IS WS-INSERT-FILE-STATUS2.
DATA DIVISION.
FILE SECTION.
FD D01-TXN-FILE. // CREATES A BUFFER
01 D01-TXN-REC.
05 TXN-DATE PIC 9(8).
05 FILLER PIC X(1).
05 TXN-NUMBER PIC 9(5).
05 FILLER PIC X(1).
05 SBACCT-CODE PIC 9(5).
05 FILLER PIC X(1).
05 TXN-AMOUNT PIC 9(5)V9(2).
05 FILLER PIC X(52).
FD D02-TXN-FILE. //CREATES A BUFFER STRUCTURE IN OUTPUT FILE
01 D02-TXN-REC.
05 TXN-DATE1 PIC 9(8).
05 FILLER PIC X(1).
05 TXN-NUMBER1 PIC 9(5).
05 FILLER PIC X(1).
05 SBACCT-CODE1 PIC 9(5).
05 FILLER PIC X(1).
05 TXN-AMOUNT1 PIC 9(5)V9(2).
05 FILLER PIC X(52).
WORKING-STORAGE SECTION.
01 HEAD1 // declaring header
02 FILLER PIC X(20).
02 TEXT PIC X(40) VALUE 'REPORT OF BANK'
01 FILE-STATUS PIC X(02) VALUE '00'.
88 EOF VALUE '10'.
88 SUCCESS VALUE '00'.
01 WS-INSERT-FILE-STATUS1 PIC X(2).
01 WS-INSERT-FILE-STATUS2 PIC X(2).
01 WS-DATE PIC 9(8). // DATE TO BE ACCEPTED FROM INSTREAM DATA
PROCEDURE DIVISION.
MAIN-PARA.
ACCEPT WS-DATE.
PERFORM OPEN-PARA.
PERFORM INIT-PARA.
PERFORM PROCESS-PARA UNTIL EOF.
PERFORM CLOSE-PARA.
STOP RUN.
OPEN-PARA. // OPENS INPUT AND OUTPUT FILES
OPEN INPUT D01-TXN-FILE.
DISPLAY 'INFILE OPEN STATUS' , WS-INSERT-FILE-STATUS1.
OPEN OUTPUT D02-TXN-FILE.
DISPLAY 'OUTFILE OPEN STATUS' ,WS-INSERT-FILE-STATUS2.
INIT-PARA. // INITIALIZES BOTH RECORDS
INITIALIZE D01-TXN-REC.
INITIALIZE D02-TXN-REC.
PROCESS-PARA.
MOVE HEAD1 TO D02-TXN-REC. // MOVING HEADER TO OUTPUT RECORD
WRITE D02-TXN-REC. // WRITING THE HEADER
READ D01-TXN-FILE
AT END MOVE '10' TO FILE-STATUS
NOT AT END PERFORM OPPO-PARA
END-READ.
OPPO-PARA. //PERFORMS EQUAL OPERATION ON BOTH DATES
IF WS-DATE = TXN-DATE
MOVE D01-TXN-REC TO D02-TXN-REC
WRITE D02-TXN-REC
END-IF.
CLOSE-PARA. // CLOSING BOTH FILES
CLOSE D01-TXN-FILE, D02-TXN-FILE.
DISPLAY 'INFILE CLOSE STATUS' , WS-INSERT-FILE-STATUS1.
DISPLAY 'OUTFILE CLOSE STATUS' , WS-INSERT-FILE-STATUS2.
I think you are over-complicating things.
In the FILE SECTION you logically connect a record-layout to a file that will be read, or to a record which will be written (to a file).
No storage is allocated in your COBOL program by the definition of the FD or any 01-levels subordinate to an FD. You simply establish a "map" which will be used to understand the data read from a file, or to use to construct the data which you will write to a file.
When you OPEN a file, some run-time code does that work behind the scenes, and will make the address of the first byte of your record layout(s) under the FD equal to the first byte of the piece of storage that will be used as the "buffer".
A "buffer" in this context is just a piece of storage which is the same size as your BLKSIZE and which will be used, without you having to be aware of it, to store records before they are actually written to the physical output file.
What happens when you WRITE a record is that the record-pointer simply gets changed to the first available byte after the current record.
It is similar when you read.
Although installation-dependent, mostly you will have the default number of buffers for a simple sequential file, which is five. Once a buffer is full the, for an output file, the IO-subsystem will be told it has data available for writing, and the filling will continue, asynchronously, with the next buffer area. What this does is reduce the amount of time your COBOL program is waiting for IO to complete.
It is similar, which obvious reversals, for the READ of a sequential file.
As a beginner, you don't need to know what the buffering is doing. Just keep it conceptually simple. You OPEN, you READ, the record is "under" the FD. You continue doing that until end-of-file, then you CLOSE. At the same time (usually) you OPEN, you WRITE, and you CLOSE at the end. What gets written is the data that is under the FD for the output file.
Also review READ ... INTO ... and WRITE ... FROM .... The concept is the same, it is just that COBOL will "move" the data to the FD for you, rather than you having detailed record-layouts under the FD. Whether you use READ file or READ file INTO record-layout is usually down to local standards for your site - do it the way your team does it.
You have now added some code to your Question.
First thing, you are writing your header inside a loop. You will get one header per record, which is probably not what you want.
Secondly, you've changed to use the FILE STATUS but not fully grasped how it works yet. You don't need the AT END/NOT AT END. The COBOL IO will maintain the FILE STATUS field. When end-of-file is identified by the COBOL run-time the FILE STATUS field will be set to "10", "automatically" as far as you are concerned.
Thirdly, review your use of INITIALIZE. There is never any point in INITIALIZE on an input record. READ always advances the record-pointer. The storage INITIALIZEd is the storage before the record you read. Even if it was not, what is the point of INITIALIZE, just to read a record over the top of the initialised storage? Same with your output.
All that your use of INITIALIZE is doing is using CPU time. Yes, it is not at all uncommon that this happens, but that is no excuse for you to pick up bad habits as well.
My problem is, given a variable which I read from a file, see if it contains or matches another string.
In other words, find in a file all the records whose variable
BRADD PIC X(30)
matches or contains a string introduced by keyboard.
I'm very confident this problem is resolved through the INSPECT instruction, and I've tried something like this in my code:
READ BRANCHFILE NEXT RECORD
AT END SET EndOfFile TO TRUE
END-READ.
PERFORM UNTIL EndOfFile
INSPECT BBRADD
TALLYING CONT for CHARACTERS
BEFORE INITIAL CITY
IF CONT>1
DISPLAY " BRANCH CODE :" BBRID
DISPLAY " BRANCH NAME :" BBRNAME
DISPLAY " BRANCH ADDRESS :" BBRADD
DISPLAY " PHONE :" BBRPH
DISPLAY " E-MAIL :" BEMAIL
DISPLAY " MANAGER NAME :" BMGRNAME
DISPLAY " ------------------"
DISPLAY " ------------------"
END-IF
READ BRANCHFILE NEXT RECORD
AT END SET EndOfFile TO TRUE
END-READ
MOVE 0 TO CONT
END-PERFORM.
Where CITY is the variable I introduce through keyboard.
¿Anyone knows how to find a "substring" in a "string"?
For example, if I introduced "Zaragoza" my program have to print all the records in the file which variable BBRADD contains "Zaragoza".
01 BRANCHREC.
88 EndOfFile VALUE HIGH-VALUE.
02 BBRID PIC X(6).
02 BBRNAME PIC X(15).
02 BBRADD PIC X(30).
02 BBRPH PIC X(10).
02 BEMAIL PIC X(20).
02 BMGRNAME PIC X(25).
You would need to set CONT to zero before the INSPECT, every time.
CONT just gets updated from its initial value when the INSPECT starts. After you find your first one, every record will look like it has CITY in it.
If may initially seem odd that it works that way, but if it didn't you'd be limited on the occasions when that is how you want it to work.
Ah, looking a little closer, you are setting CONT to an initial value, you are just doing it in an unexpected place. If it needs to be zero, set it to zero immediately before it should be zero. Much easier to find, less easy for someone changing the program in the future to make a mess of.
However, you have another problem. Let's say CITY is PIC X(20). The user enters SEVILLA and your INSPECT will now search for SEVILLA followed by 13 spaces. Ideally you'd want SEVILLA followed by one space.
You need to be able to test for a value that the user has entered, with a trailing blank, but not more.
The current popular way to do this is with reference-modification.
You need to take your user-input, find out how many trailing spaces it contains, calculate how long the data is, add one for the trailing blank, and hold that value in a field (preferably a BINARY field).
Then your INSPECT can look like this:
INSPECT BBRADD
TALLYING CONT for CHARACTERS
BEFORE INITIAL CITY ( 1 : length-of-data-plus-one )
However, then you have a problem if SEVILLA is actually in the start of the field.
So you make a small change, not to count characters which appear before it, but to count occurrences of it.
INSPECT BBRADD
TALLYING CONT for ALL
CITY ( 1 : length-of-data-plus-one )
Many people will instead code a PERFORM loop with reference-modification and do the test that way. With the final version of the INSPECT above have to code the termination logic yourself. For learning purposes it would be good to do it both ways.
When doing file-io, always use and check the FILE STATUS. Put your READ into a paragraph and perform it, you don't need two different pieces of code. If you use the FILE STATUS you don't need the AT END (or the END-READ) as the field you use to receive the FILE STATUS value will be "10" for end-of-file. Just use your 88 on that field, with the value of "10".
The Edit on your question now indicates where your existing 88-level is.
On the one hand, this is a good idea, because the end-of-file is associated with the record, and there can be no valid accidental content.
On the other hand, this is not a "portable" solution: if you use other COBOLs you may find that once end-of-file is reached it is no longer valid to access data under the FD. In the standard what happens in this situation is not defined, so you get differences amongst compilers.
You can retain the 88 on the group-item had have it portable by using READ ... INTO ... and having your record-layout in WORKING-STORAGE. This takes slightly longer to execute, as the data has to be transferred from one location to another.
I prefer the 88 on the FILE STATUS field and simplify the READ by being able to remove the AT END and END-READ. I already can't access the record-area under the FD so I can't accidentally get wrong values which look good.