COBOL Module 7 – Sequential files and the classic batch patterns
Printed reports: headings, pages and Report Writer
Print COBOL reports with WRITE ADVANCING, page headings, page numbers and totals; count lines with LINAGE, handle END-OF-PAGE and read Report Writer.
What you will learn
- Write a paginated report with headings, page numbers, detail lines and totals
- Use WRITE ... ADVANCING so that every report line lands where it should
- Count page lines with LINAGE and handle END-OF-PAGE without losing statements
- Read Report Writer's RD, DETAIL and CONTROL FOOTING entries and say where they compile
Before you start
What you’ll learn · Before you start
What you will learn
- Write a paginated report with headings, page numbers, detail lines and totals
- Use WRITE ... ADVANCING so that every report line lands where it should
- Count page lines with LINAGE and handle END-OF-PAGE without losing statements
- Read Report Writer's RD, DETAIL and CONTROL FOOTING entries and say where they compile
Before you start
On this page
A branch’s seven transactions have to be printed three to a page, each page under a title with its page number and a line of column heads, with the totals at the end. COBOL writes such a report one line at a time, and the program decides how far the paper moves before each line and where each page ends.
Real reports put far more lines on a page; three make every page break of this lesson’s runs visible. The programs are GnuCOBOL 3.2 runs, and each place where z/OS behaves differently is named where it matters.
WRITE ADVANCING places every line of a report
A report file is a file of print lines. paginate.cob describes its line as RPT-LINE PIC X(40), and every WRITE of it says how far the paper moves: AFTER ADVANCING 1 LINE moves one line and then prints, AFTER ADVANCING 2 LINES leaves a blank line first, and AFTER ADVANCING PAGE starts a new page. In a line sequential file GnuCOBOL writes newlines for the lines and a form feed: The control character X'0C' that tells a printer to start a new page. GnuCOBOL writes one into a line sequential report file for each WRITE ... ADVANCING PAGE of a file without LINAGE., X’0C’, for each new page. The session shows every form feed as [new page]:
BR01AC0001C000250000
BR01AC0002D000012500
BR01AC0003D000004999
BR01AC0004C000001250
BR01AC0005D000030000
BR01AC0006D000000999
BR01AC0007C000150000 IDENTIFICATION DIVISION.
PROGRAM-ID. PAGINATE.
* Prints a branch's transactions three to a page, with two
* heading lines and a page number on every page, then the
* totals. The page break is tested before each detail line.
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT TXN-FILE ASSIGN TO "TXNIN"
ORGANIZATION IS LINE SEQUENTIAL
FILE STATUS IS TXN-STATUS.
SELECT RPT-FILE ASSIGN TO "RPTOUT"
ORGANIZATION IS LINE SEQUENTIAL
FILE STATUS IS RPT-STATUS.
DATA DIVISION.
FILE SECTION.
FD TXN-FILE.
01 TXN-REC.
05 TX-BRANCH PIC X(4).
05 TX-ACCT PIC X(6).
05 TX-TYPE PIC X.
88 TX-CREDIT VALUE "C".
05 TX-AMOUNT PIC 9(7)V99.
FD RPT-FILE.
01 RPT-LINE PIC X(40).
WORKING-STORAGE SECTION.
01 TXN-STATUS PIC XX.
88 TXN-AT-END VALUE "10".
01 RPT-STATUS PIC XX.
01 LINES-PER-PAGE PIC 99 VALUE 3.
01 LINE-COUNT PIC 99 VALUE 99.
01 PAGE-NO PIC 99 VALUE 0.
01 CREDIT-TOTAL PIC 9(9)V99 VALUE 0.
01 DEBIT-TOTAL PIC 9(9)V99 VALUE 0.
01 TITLE-LINE.
05 FILLER PIC X(30) VALUE "BRANCH BR01 TRANSACTIONS".
05 FILLER PIC X(5) VALUE "PAGE".
05 TL-PAGE PIC Z9.
01 COLUMN-LINE.
05 FILLER PIC X(10) VALUE "ACCOUNT".
05 FILLER PIC X(10) VALUE "TYPE".
05 FILLER PIC X(12) VALUE " AMOUNT".
01 DETAIL-LINE.
05 DL-ACCT PIC X(10).
05 DL-TYPE PIC X(10).
05 DL-AMOUNT PIC Z,ZZZ,ZZ9.99.
01 TOTAL-LINE.
05 TT-LABEL PIC X(18).
05 TT-AMOUNT PIC ZZZ,ZZZ,ZZ9.99.
PROCEDURE DIVISION.
OPEN INPUT TXN-FILE OUTPUT RPT-FILE
IF TXN-STATUS NOT = "00" OR RPT-STATUS NOT = "00"
DISPLAY "OPEN failed, status " TXN-STATUS " " RPT-STATUS
CLOSE TXN-FILE RPT-FILE
MOVE 12 TO RETURN-CODE
STOP RUN
END-IF
READ TXN-FILE
PERFORM UNTIL TXN-AT-END
IF LINE-COUNT >= LINES-PER-PAGE
PERFORM PRINT-HEADINGS
END-IF
MOVE TX-ACCT TO DL-ACCT
MOVE TX-TYPE TO DL-TYPE
MOVE TX-AMOUNT TO DL-AMOUNT
WRITE RPT-LINE FROM DETAIL-LINE AFTER ADVANCING 1 LINE
ADD 1 TO LINE-COUNT
IF TX-CREDIT
ADD TX-AMOUNT TO CREDIT-TOTAL
ELSE
ADD TX-AMOUNT TO DEBIT-TOTAL
END-IF
READ TXN-FILE
END-PERFORM
MOVE "TOTAL CREDITS" TO TT-LABEL
MOVE CREDIT-TOTAL TO TT-AMOUNT
WRITE RPT-LINE FROM TOTAL-LINE AFTER ADVANCING 2 LINES
MOVE "TOTAL DEBITS" TO TT-LABEL
MOVE DEBIT-TOTAL TO TT-AMOUNT
WRITE RPT-LINE FROM TOTAL-LINE AFTER ADVANCING 1 LINE
CLOSE TXN-FILE RPT-FILE
DISPLAY "pages printed " PAGE-NO
STOP RUN.
PRINT-HEADINGS.
ADD 1 TO PAGE-NO
MOVE PAGE-NO TO TL-PAGE
WRITE RPT-LINE FROM TITLE-LINE AFTER ADVANCING PAGE
WRITE RPT-LINE FROM COLUMN-LINE AFTER ADVANCING 1 LINE
MOVE 0 TO LINE-COUNT. # Prints a report file with every form feed (X'0C') shown as a line [new page], so that the pages can be seen.
awk 'NR == 1 { sub(/^\f/, "[new page]\n") } { gsub(/\f/, "\n[new page]\n"); print }' "$1" from session import files, run
# The branch report of seven transactions, three detail lines to a page; showpages.sh prints each form feed as
# [new page].
files("paginate.cob", "branch.txt", "showpages.sh")
run("""
cobc -x paginate.cob
DD_TXNIN=branch.txt ./paginate && sh showpages.sh RPTOUT
""") Output
$ cobc -x paginate.cob $ DD_TXNIN=branch.txt ./paginate && sh showpages.sh RPTOUT pages printed 03 [new page] BRANCH BR01 TRANSACTIONS PAGE 1 ACCOUNT TYPE AMOUNT AC0001 C 2,500.00 AC0002 D 125.00 AC0003 D 49.99 [new page] BRANCH BR01 TRANSACTIONS PAGE 2 ACCOUNT TYPE AMOUNT AC0004 C 12.50 AC0005 D 300.00 AC0006 D 9.99 [new page] BRANCH BR01 TRANSACTIONS PAGE 3 ACCOUNT TYPE AMOUNT AC0007 C 1,500.00 TOTAL CREDITS 4,012.50 TOTAL DEBITS 484.98
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 paginate_session.py
Three pages came out, the last with one detail line, a blank line and the two totals: 4,012.50 of credits and 484.98 of debits.
IBM’s WRITE statement assumes AFTER ADVANCING 1 LINE when a WRITE has no ADVANCING phrase. GnuCOBOL’s Programmer’s Guide says that on files other than line-advancing ones it assumes BEFORE ADVANCING 1 LINE: print, then move. In one report the two meet badly. Take the ADVANCING phrase off the first total line and run it again:
from session import files, run
# The same report with one ADVANCING phrase taken out: the WRITE of the first total line.
files("paginate.cob", "branch.txt", "showpages.sh")
run("""
sed 's/FROM TOTAL-LINE AFTER ADVANCING 2 LINES/FROM TOTAL-LINE/' paginate.cob > joined.cob
!diff paginate.cob joined.cob
cobc -x joined.cob
DD_TXNIN=branch.txt ./joined && sh showpages.sh RPTOUT | tail -4
""") Output
$ sed 's/FROM TOTAL-LINE AFTER ADVANCING 2 LINES/FROM TOTAL-LINE/' paginate.cob > joined.cob $ diff paginate.cob joined.cob 77c77 < WRITE RPT-LINE FROM TOTAL-LINE AFTER ADVANCING 2 LINES --- > WRITE RPT-LINE FROM TOTAL-LINE [exit status 1] $ cobc -x joined.cob $ DD_TXNIN=branch.txt ./joined && sh showpages.sh RPTOUT | tail -4 pages printed 03 ACCOUNT TYPE AMOUNT AC0007 C 1,500.00TOTAL CREDITS 4,012.50 TOTAL DEBITS 484.98
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 joined_session.py
The last detail was written AFTER ADVANCING, which moves the paper before the line and never after it. The total that followed it without a phrase printed on the same line, right after 1,500.00, and then moved one line, so a blank line appeared above TOTAL DEBITS. On z/OS the same WRITE … ADVANCING puts a printer control character: The byte at the front of each record of a z/OS print data set that tells the printer how far to move before the line. With the ADV compiler option, the compiler adds that byte to the record length. in front of each line: IBM’s ADV option, the default, makes the compiler add “1 byte to the record length” for it.
Mixing WRITEs with and without ADVANCING
Wrong: detail lines written AFTER ADVANCING 1 LINE and a total line written with no ADVANCING phrase, which GnuCOBOL prints on the same line as the last detail. Right: give every WRITE of a report file an ADVANCING phrase, AFTER ADVANCING as the IBM default has it, so the program says where each line goes.
Checkpoint
Check: WRITE ADVANCING and print lines
A line counter decides where each page breaks
paginate.cob counts the detail lines of the current page in LINE-COUNT. Before each detail line it asks whether the page is full: when LINE-COUNT has reached LINES-PER-PAGE, 3 here, it performs PRINT-HEADINGS, which adds 1 to PAGE-NO, writes the title with that number AFTER ADVANCING PAGE, writes the column heads and sets LINE-COUNT back to 0. LINE-COUNT starts at 99, so the first detail line finds the page full and the first page gets its headings without a special case. Step through the decision for the first, fourth and seventh records:
The drawing shows the whole scene; its 7 steps are listed below.
Every step as text
- Record 1. AC0001 arrives with LINE-COUNT at 99, its starting value, so the test finds the page full before anything is printed.
- Page 1 starts. PRINT-HEADINGS makes PAGE-NO 1, writes the title AFTER ADVANCING PAGE, writes the column heads and sets LINE-COUNT to 0.
- AC0001 printed. The WRITE moves one line and prints AC0001, and LINE-COUNT becomes 1. Its 2,500.00 goes to the credits.
- Records 2 and 3. For AC0002 and AC0003 the count is 1 and then 2, below 3, so the test sends them straight to the WRITE. After AC0003 the count is 3.
- Record 4. AC0004 finds LINE-COUNT at 3, so PRINT-HEADINGS runs first. Page 2 starts with a form feed, and AC0004 is its first detail line.
- Record 7. After AC0006 the count is 3 again, so AC0007 starts page 3 and is the only detail line on it.
- The totals. The READ after AC0007 returns 10 and the loop ends. The totals print on page 3 below a blank line, credits 4,012.50 and debits 484.98.
Testing before the WRITE means a page is started only when there is a line to put on it. The totals come after the loop, on whatever page the last detail used, which is why page 3 held one detail line and both totals.
COBOL has two other ways to place lines on pages. LINAGE, a clause of the FD, makes the file count the lines of each page and runs AT END-OF-PAGE when a WRITE reaches the page’s footing area. The program then keeps no line counter of its own, and the statements of the WRITE’s AT END-OF-PAGE phrase start the next page. Report Writer describes a report’s headings, detail lines and totals in a REPORT SECTION, and its GENERATE statement prints them. The program then needs no page test of its own: Report Writer works out from those declarations when each heading, line and total prints.
Key idea
A hand-coded report owns its pages: a counter of lines on the page, a page number, and one headings paragraph that every page break performs, tested before a detail line is written.
Checkpoint
Check: A line counter and page headings
Practise: lines and pages
These questions come from the two runs above, and each answer can be worked out from the program and its output.
Practice that adapts to you
Practise this lesson
7 questions about 5 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
LINAGE and END-OF-PAGE: pages the file counts, and a heading WRITE that swallows statements Pro: opens with a Pro, Premium or Ultimate pass
LINAGE moves the line count into the file itself. This part runs the same report on six-line pages, follows LINAGE-COUNTER line by line, and reproduces the trap of a WRITE nested inside AT END-OF-PAGE.
In this part:
- LINAGE-COUNTER line by line, with the numbers of a six-line page
- A heading WRITE inside END-OF-PAGE that swallowed two statements, reproduced
1 diagram · 3 runnable examples (COB, Python) · about 5 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
A page that ends on its last line, and a day with no transactions: two drills Pro: opens with a Pro, Premium or Ultimate pass
Most report bugs show only on the day the input has a particular size. This part runs paginate.cob and two variants on seven transactions, on six, which fill exactly two pages, and on an empty file, and keeps the version that is right for all three.
In this part:
- Six transactions: where the page test sits decides whether a blank page prints
- An empty file: headings that never print, and the fix
5 runnable examples (COB, Python, Text) · about 4 minutes
Opening this part with your pass…Pro part. It opens with the same pass as the part above.
Report Writer: the branch summary described in the REPORT SECTION Pro: opens with a Pro, Premium or Ultimate pass
Report Writer turns hand-coded page and total logic into declarations. This part runs a branch summary with subtotals and a grand total, maps each entry to the code it replaces, and shows a column mistake that the compiler lets through.
In this part:
- RD, PAGE HEADING, DETAIL and CONTROL FOOTING, run on three branches
- Columns that overlap, and where Report Writer compiles
1 diagram · 3 runnable examples (COB, Python, Text) · about 5 minutes
Opening this part with your pass…Pro part. It opens with the same pass as the part above.
Interview questions
The warm-up is answered in full. The three after it start from a report’s totals, its pages and its move to z/OS:
-
Fresher to mid · Warm-up
What do WRITE ... AFTER ADVANCING 2 LINES and WRITE ... AFTER ADVANCING PAGE do?
Both move the paper first and then print the line. AFTER ADVANCING 2 LINES moves two lines, so one blank line comes before the line, and AFTER ADVANCING PAGE moves to the top of a new page, which is how a report's title starts each page. BEFORE ADVANCING does it the other way round: print, then move. In a GnuCOBOL line sequential file the moves become newline characters and a form feed, X'0C', for a new page; on z/OS, with the default ADV option, the compiler adds one byte to each print record for a printer control character. Because GnuCOBOL assumes BEFORE ADVANCING 1 LINE when a WRITE has no phrase and IBM assumes AFTER, a report should give every WRITE its own ADVANCING phrase.
-
Mid to senior
A listing prints every detail line correctly, but its record count and amount total at the end are far too small. Each detail line is written by a WRITE with an AT END-OF-PAGE phrase that writes the next page's title. Nobody changed the totalling code. Where do you look, and how do you prove the cause?
-
Fresher to mid
Users of a daily report complain about two things: some days the report ends with a page that has only headings and totals, and on days without transactions the report has totals but no headings at all. Explain both and redesign the page logic.
-
Senior to staff
A suite of report programs written with Report Writer builds and runs with GnuCOBOL. They must now build and run with IBM Enterprise COBOL on z/OS, with the same reports. What do you check, what are your options, and how do you prove the reports did not change?
Organisations named on this page
No tie exists between MySmartCoPilot and IBM or the GnuCOBOL project. The WRITE, LINAGE and ADV pages of IBM’s documentation and GnuCOBOL’s Programmer’s Guide are the sources of the rules quoted, and the reports were printed by GnuCOBOL 3.2.
Flashcards · 6 cards
Recall it, then check
Say the answer to yourself before you look: recalling it is what makes it stick.
-
What do AFTER ADVANCING 2 LINES and AFTER ADVANCING PAGE do?
The first moves the paper two lines, leaving a blank line, and then prints; the second starts a new page first. In a line sequential file GnuCOBOL writes newlines, and a form feed, X'0C', for a page.
-
A WRITE to a line sequential report file has no ADVANCING phrase. What does GnuCOBOL do, and IBM's compiler?
GnuCOBOL assumes BEFORE ADVANCING 1 LINE, printing first; IBM assumes AFTER ADVANCING 1 LINE. Mixed in one report, a total landed on the same line as the last detail.
-
What does IBM's ADV compiler option add to a print record?
One byte in front of each line for the printer control character that WRITE ... ADVANCING sets, which makes the record a byte longer.
-
How does a hand-coded report decide where a page breaks?
Before each detail line it compares a count of the lines on the page with the lines a page holds. When the page is full it performs one headings paragraph, which adds 1 to the page number, writes the title AFTER ADVANCING PAGE and resets the count.
-
Why does paginate.cob start its line counter at 99?
So that the first detail line finds the page full and gets the first headings from the same test, with no special case for page 1.
-
Which two other ways does COBOL have to place lines on pages?
LINAGE, a clause of the FD, has the file count the lines and run AT END-OF-PAGE at the footing area. Report Writer declares headings, details and totals in a REPORT SECTION, and GENERATE prints them.
Key takeaways
- A report is a file of print lines: AFTER ADVANCING n LINES moves before the line is printed, AFTER ADVANCING PAGE starts a page, and in a line sequential file GnuCOBOL writes newlines and a form feed for them.
- GnuCOBOL assumes BEFORE ADVANCING 1 LINE when a WRITE to a line sequential file has no phrase, where IBM assumes AFTER, so a report gives every WRITE its own ADVANCING phrase.
- A hand-coded page break keeps a line count and a page number and performs one headings paragraph, tested before each detail line, so pages start only when a line needs one.
Terms in this lesson
- form feed
- The control character X'0C' that tells a printer to start a new page. GnuCOBOL writes one into a line sequential report file for each WRITE ... ADVANCING PAGE of a file without LINAGE.
- printer control character
- The byte at the front of each record of a z/OS print data set that tells the printer how far to move before the line. With the ADV compiler option, the compiler adds that byte to the record length.
References
- Enterprise COBOL for z/OS 6.5: WRITE statement (IBM)
- Enterprise COBOL for z/OS 6.5: LINAGE clause (IBM)
- Enterprise COBOL for z/OS 6.5: LINAGE-COUNTER (IBM)
- Enterprise COBOL for z/OS 6.5: ADV compiler option (IBM)
- Enterprise COBOL for z/OS 6.5 Programming Guide: Writing QSAM files to a printer or spooled data set (IBM)
- Enterprise COBOL for z/OS 6.5 Migration Guide: Report Writer (IBM)
- GnuCOBOL 3.2 Programmer's Guide (2.1.13 Report Writer Features, 7.8.54 WRITE, chapter 9 Report Writer Usage) (GnuCOBOL project (GNU))
- GnuCOBOL 3.2 NEWS (GnuCOBOL project (GNU))
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress