COBOL Textbook

COBOL WRITE a report file: headers, detail lines, page breaks

A GnuCOBOL program writes a paginated report: page header with the run date, column headings, edited detail lines, a page total, and a break every 23 lines.

Published
Updated
Dialect
GnuCOBOL 3.x, free format
Status
Desk-checked, not compiled
Length
2,128 words, 10 min

A batch program that reads a file usually ends by printing one, and printing in COBOL means writing lines to a file with the WRITE statement's ADVANCING phrase. This tutorial builds a paginated transaction register: a header on every page carrying the run date and the page number, column headings, one edited detail line per record, a control total at the foot of each page, and a summary page of grand totals. The break comes every 23 detail lines, and the article walks the exact record that triggers the first one. It reads its input the way reading a sequential file to end of file does, and every amount on the page goes through one of the pictures from the edited pictures lesson.

The listing was desk-checked against the GnuCOBOL Programmer's Guide and the GnuCOBOL Manual; cobc is not installed on the machine this was written on, so nothing here claims a compile. The report arithmetic is a different matter: the Python script shipped alongside builds the same report with the same page rules and the same edited pictures, and every number quoted below came out of running it.

The program

       >>SOURCE FORMAT IS FREE
*> cobol-write-report.cob — read a fixed-width
*>          transaction file and write a paginated report
*> Dialect: GnuCOBOL 3.x, free format
*> Status : desk-checked; NOT compiled (no cobc on this machine)
IDENTIFICATION DIVISION.
PROGRAM-ID. WRITE-REGISTER.
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
    SELECT Txn-File
        ASSIGN TO "datasets/cobol-write-report-file-page-breaks.dat"
        ORGANIZATION IS LINE SEQUENTIAL
        FILE STATUS IS WS-Txn-Status.
    SELECT Report-File
        ASSIGN TO "register.txt"
        ORGANIZATION IS LINE SEQUENTIAL
        FILE STATUS IS WS-Rpt-Status.
DATA DIVISION.
FILE SECTION.
FD  Txn-File.
    COPY "cobol-write-report-file-page-breaks.cpy".
*> The report record is one print line.  Nothing is described inside it:
*> every line is built in WORKING-STORAGE and written with FROM.
FD  Report-File.
01  Report-Line          PIC X(72).
WORKING-STORAGE SECTION.
77 WS-Txn-Status         PIC XX.
77 WS-Rpt-Status         PIC XX.
01 WS-Flags.
   05 WS-EOF-Flag        PIC X VALUE "N".
      88 End-Of-File           VALUE "Y".
   05 WS-Page-Kind       PIC X VALUE "D".
      88 Summary-Page          VALUE "S".
*> Page geometry.  Change these and the whole report re-paginates:
*> 30 usable lines = 5 header + 23 detail + 2 footer.
77 WS-Page-Size          PIC 9(3)  VALUE 30.
77 WS-Header-Lines       PIC 9(3)  VALUE 5.
77 WS-Footer-Lines       PIC 9(3)  VALUE 2.
77 WS-Summary-Lines      PIC 9(3)  VALUE 7.
*> The hand-kept line counter: how many lines this page already holds.
77 WS-Page-Line          PIC 9(3)  VALUE 0.
77 WS-Page-No            PIC 9(3)  VALUE 0.
77 WS-Wanted             PIC 9(3)  VALUE 0.
77 WS-Needed             PIC 9(3)  VALUE 0.
77 WS-Read-Count         PIC 9(5)     VALUE 0.
77 WS-Detail-Count       PIC 9(5)     VALUE 0.
77 WS-Page-Total         PIC 9(9)V99  VALUE 0.
77 WS-Debit-Total        PIC 9(9)V99  VALUE 0.
77 WS-Credit-Total       PIC 9(9)V99  VALUE 0.
77 WS-Net                PIC S9(9)V99 VALUE 0.
77 WS-Show-Count         PIC ZZ,ZZ9.
*> FUNCTION CURRENT-DATE returns 21 characters: YYYYMMDDhhmmsshh then the
*> sign and size of the offset from Greenwich.  Only the first 14 are used.
01 WS-Now.
   05 WS-Now-Ymd         PIC 9(8).
   05 WS-Now-Hh          PIC 9(2).
   05 WS-Now-Mi          PIC 9(2).
   05 WS-Now-Ss          PIC 9(2).
   05 FILLER             PIC X(7).
01 WS-Head-1.
   05 FILLER             PIC X(2)  VALUE SPACES.
   05 FILLER             PIC X(26) VALUE "DAILY TRANSACTION REGISTER".
   05 FILLER             PIC X(28) VALUE SPACES.
   05 FILLER             PIC X(5)  VALUE "PAGE ".
   05 HD-Page            PIC ZZ9.
   05 FILLER             PIC X(8)  VALUE SPACES.
01 WS-Head-2.
   05 FILLER             PIC X(2)  VALUE SPACES.
   05 FILLER             PIC X(4)  VALUE "RUN ".
   05 HD-Date            PIC 9(4)/9(2)/9(2).
   05 FILLER             PIC X(2)  VALUE SPACES.
   05 HD-Hour            PIC 9(2).
   05 FILLER             PIC X     VALUE ":".
   05 HD-Minute          PIC 9(2).
   05 FILLER             PIC X     VALUE ":".
   05 HD-Second          PIC 9(2).
   05 FILLER             PIC X(46) VALUE SPACES.
01 WS-Head-Cols.
   05 FILLER             PIC X(2)  VALUE SPACES.
   05 FILLER             PIC X(6)  VALUE "REF".
   05 FILLER             PIC X(2)  VALUE SPACES.
   05 FILLER             PIC X(10) VALUE "DATE".
   05 FILLER             PIC X(2)  VALUE SPACES.
   05 FILLER             PIC X(3)  VALUE "BR".
   05 FILLER             PIC X(2)  VALUE SPACES.
   05 FILLER             PIC X(28) VALUE "DESCRIPTION".
   05 FILLER             PIC X(2)  VALUE SPACES.
   05 FILLER             PIC X(12) VALUE "      AMOUNT".
   05 FILLER             PIC X(3)  VALUE " T ".
01 WS-Head-Summary.
   05 FILLER             PIC X(2)  VALUE SPACES.
   05 FILLER             PIC X(70) VALUE "REPORT SUMMARY".
01 WS-Blank-Line         PIC X(72) VALUE SPACES.
01 WS-Rule-Line          PIC X(72) VALUE ALL "-".
01 WS-Detail-Line.
   05 FILLER             PIC X(2)  VALUE SPACES.
   05 DL-Ref             PIC X(6).
   05 FILLER             PIC X(2)  VALUE SPACES.
   05 DL-Date            PIC 9(4)/9(2)/9(2).
   05 FILLER             PIC X(2)  VALUE SPACES.
   05 DL-Branch          PIC X(3).
   05 FILLER             PIC X(2)  VALUE SPACES.
   05 DL-Desc            PIC X(28).
   05 FILLER             PIC X(2)  VALUE SPACES.
   05 DL-Amount          PIC Z,ZZZ,ZZ9.99.
   05 FILLER             PIC X     VALUE SPACE.
   05 DL-DC              PIC X.
   05 FILLER             PIC X     VALUE SPACE.
01 WS-Page-Total-Line.
   05 FILLER             PIC X(2)  VALUE SPACES.
   05 FILLER             PIC X(38) VALUE SPACES.
   05 FILLER             PIC X(5)  VALUE "PAGE ".
   05 PT-Page            PIC ZZ9.
   05 FILLER             PIC X(7)  VALUE " TOTAL ".
   05 FILLER             PIC X(2)  VALUE SPACES.
   05 PT-Amount          PIC Z,ZZZ,ZZ9.99.
   05 FILLER             PIC X(3)  VALUE SPACES.
01 WS-Total-Line.
   05 FILLER             PIC X(2)  VALUE SPACES.
   05 TL-Label           PIC X(53).
   05 FILLER             PIC X(2)  VALUE SPACES.
   05 TL-Amount          PIC Z,ZZZ,ZZ9.99CR.
   05 FILLER             PIC X     VALUE SPACE.
01 WS-Count-Line.
   05 FILLER             PIC X(2)  VALUE SPACES.
   05 CL-Label           PIC X(53).
   05 FILLER             PIC X(10) VALUE SPACES.
   05 CL-Count           PIC ZZ,ZZ9.
   05 FILLER             PIC X     VALUE SPACE.
PROCEDURE DIVISION.
Main-Paragraph.
    PERFORM Start-Report
    PERFORM Read-Next-Txn
    PERFORM UNTIL End-Of-File
       PERFORM Print-Detail
       PERFORM Read-Next-Txn
    END-PERFORM
    PERFORM End-Page
    PERFORM Print-Summary
    PERFORM End-Report
    STOP RUN.

Start-Report.
    OPEN INPUT Txn-File
    IF WS-Txn-Status NOT = "00"
       DISPLAY "Cannot open the transaction file, status " WS-Txn-Status
       STOP RUN
    END-IF
    OPEN OUTPUT Report-File
    IF WS-Rpt-Status NOT = "00"
       DISPLAY "Cannot open the report file, status " WS-Rpt-Status
       STOP RUN
    END-IF
*> The run stamp is set once and printed on every page header.
    MOVE FUNCTION CURRENT-DATE TO WS-Now
    MOVE WS-Now-Ymd TO HD-Date
    MOVE WS-Now-Hh  TO HD-Hour
    MOVE WS-Now-Mi  TO HD-Minute
    MOVE WS-Now-Ss  TO HD-Second
    PERFORM Start-Page.

Read-Next-Txn.
    READ Txn-File
       AT END
          SET End-Of-File TO TRUE
       NOT AT END
          ADD 1 TO WS-Read-Count
    END-READ
    IF WS-Txn-Status NOT = "00" AND WS-Txn-Status NOT = "10"
       DISPLAY "Read failed, file status " WS-Txn-Status
       STOP RUN
    END-IF.

*> Page 1 starts where the paper is; every later page starts with a
*> form feed, which is what ADVANCING PAGE writes when the FD has no
*> LINAGE clause.
Start-Page.
    ADD 1 TO WS-Page-No
    MOVE WS-Page-No TO HD-Page
    IF WS-Page-No = 1
       WRITE Report-Line FROM WS-Head-1 AFTER ADVANCING 1 LINE
    ELSE
       WRITE Report-Line FROM WS-Head-1 AFTER ADVANCING PAGE
    END-IF
    WRITE Report-Line FROM WS-Head-2 AFTER ADVANCING 1 LINE
    WRITE Report-Line FROM WS-Blank-Line AFTER ADVANCING 1 LINE
    IF Summary-Page
       WRITE Report-Line FROM WS-Head-Summary AFTER ADVANCING 1 LINE
    ELSE
       WRITE Report-Line FROM WS-Head-Cols AFTER ADVANCING 1 LINE
    END-IF
    WRITE Report-Line FROM WS-Rule-Line AFTER ADVANCING 1 LINE
    MOVE WS-Header-Lines TO WS-Page-Line
    MOVE 0 TO WS-Page-Total.

*> The page-break test.  WS-Wanted holds the number of lines the caller
*> is about to write; the footer must still fit under them.
Check-Room.
    COMPUTE WS-Needed = WS-Page-Line + WS-Wanted + WS-Footer-Lines
    IF WS-Needed > WS-Page-Size
       PERFORM End-Page
       PERFORM Start-Page
    END-IF.

Print-Detail.
    MOVE 1 TO WS-Wanted
    PERFORM Check-Room
    MOVE Txn-Ref    TO DL-Ref
    MOVE Txn-Date   TO DL-Date
    MOVE Txn-Branch TO DL-Branch
    MOVE Txn-Desc   TO DL-Desc
    MOVE Txn-Amount TO DL-Amount
    MOVE Txn-DC     TO DL-DC
    WRITE Report-Line FROM WS-Detail-Line AFTER ADVANCING 1 LINE
    ADD 1 TO WS-Page-Line
    ADD 1 TO WS-Detail-Count
    ADD Txn-Amount TO WS-Page-Total
    EVALUATE Txn-DC
       WHEN "D"
          ADD Txn-Amount TO WS-Debit-Total
       WHEN "C"
          ADD Txn-Amount TO WS-Credit-Total
       WHEN OTHER
          DISPLAY "Unexpected type [" Txn-DC "] on " Txn-Ref
    END-EVALUATE.

End-Page.
    WRITE Report-Line FROM WS-Rule-Line AFTER ADVANCING 1 LINE
    MOVE WS-Page-No    TO PT-Page
    MOVE WS-Page-Total TO PT-Amount
    WRITE Report-Line FROM WS-Page-Total-Line AFTER ADVANCING 1 LINE
    ADD WS-Footer-Lines TO WS-Page-Line.

*> The grand totals always start a fresh page.  To let them share the
*> last detail page instead, replace the PERFORM Start-Page below with
*> MOVE WS-Summary-Lines TO WS-Wanted and PERFORM Check-Room.
Print-Summary.
    SET Summary-Page TO TRUE
    PERFORM Start-Page
    WRITE Report-Line FROM WS-Blank-Line AFTER ADVANCING 1 LINE
    WRITE Report-Line FROM WS-Rule-Line AFTER ADVANCING 1 LINE
    MOVE "RECORDS READ"  TO CL-Label
    MOVE WS-Read-Count   TO CL-Count
    WRITE Report-Line FROM WS-Count-Line AFTER ADVANCING 1 LINE
    MOVE "DEBIT TOTAL"   TO TL-Label
    MOVE WS-Debit-Total  TO TL-Amount
    WRITE Report-Line FROM WS-Total-Line AFTER ADVANCING 1 LINE
    MOVE "CREDIT TOTAL"  TO TL-Label
    MOVE WS-Credit-Total TO TL-Amount
    WRITE Report-Line FROM WS-Total-Line AFTER ADVANCING 1 LINE
    COMPUTE WS-Net = WS-Debit-Total - WS-Credit-Total
    MOVE "NET (DEBITS LESS CREDITS)" TO TL-Label
    MOVE WS-Net          TO TL-Amount
    WRITE Report-Line FROM WS-Total-Line AFTER ADVANCING 1 LINE
    MOVE "PAGES PRINTED" TO CL-Label
    MOVE WS-Page-No      TO CL-Count
    WRITE Report-Line FROM WS-Count-Line AFTER ADVANCING 1 LINE
    ADD WS-Summary-Lines TO WS-Page-Line.

End-Report.
    CLOSE Txn-File Report-File
    IF WS-Rpt-Status NOT = "00"
       DISPLAY "Close of the report file failed, status " WS-Rpt-Status
    END-IF
    MOVE WS-Read-Count   TO WS-Show-Count
    DISPLAY "Records read  : " WS-Show-Count
    MOVE WS-Detail-Count TO WS-Show-Count
    DISPLAY "Detail lines  : " WS-Show-Count
    MOVE WS-Page-No      TO WS-Show-Count
    DISPLAY "Pages printed : " WS-Show-Count.

The input record

The copybook, shipped as /code/cobol-write-report-file-page-breaks.cpy, describes the 64 bytes the reader consumes:

01 Txn-Record.
   05 Txn-Date          PIC 9(8).
   05 Txn-Ref           PIC X(6).
   05 Txn-Branch        PIC X(3).
   05 Txn-Desc          PIC X(28).
   05 Txn-Amount        PIC 9(9)V99.
   05 Txn-DC            PIC X.
   05 FILLER            PIC X(7).
Listing 3.2-1. Lines 5 to 12 of cobol-write-report-file-page-breaks.cpy.
Table 3.2-1
Field Positions PIC Meaning
Txn-Date 1 to 8 9(8) YYYYMMDD
Txn-Ref 9 to 14 X(6) Reference, T00001 upwards
Txn-Branch 15 to 17 X(3) Branch code
Txn-Desc 18 to 45 X(28) Description, space padded
Txn-Amount 46 to 56 9(9)V99 Eleven digits, implied decimal point, unsigned
Txn-DC 57 X D for debit, C for credit
FILLER 58 to 64 X(7) Reserved, spaces

The first record of the shipped file is 20260901T00001CRKSUPPLIER INVOICE 00000430825D, followed by seven spaces and a line feed. The amount carries no sign, for the reason given in the sequential-file lesson: a separate type byte is unambiguous across languages and encodings in a way that an overpunched sign is not.

How the page break works

Everything about pagination lives in four constants and one paragraph. The page holds 30 usable lines; the header costs 5 and the footer costs 2, which leaves 23 for detail lines. Check-Room is the whole mechanism:

Check-Room.
    COMPUTE WS-Needed = WS-Page-Line + WS-Wanted + WS-Footer-Lines
    IF WS-Needed > WS-Page-Size
       PERFORM End-Page
       PERFORM Start-Page
    END-IF.
Listing 3.2-2. Lines 217 to 222 of cobol-write-report.cob.

WS-Page-Line is the hand-kept line counter: Start-Page sets it to the header size after writing the header, every detail line adds one to it, and End-Page adds the footer size. WS-Wanted is how many lines the caller is about to write, which is why the same paragraph serves a one-line detail and, if you want it to, a seven-line summary block. The footer is always included in the test, so a page never fills up so completely that its own total has nowhere to go. That is the mistake this arrangement exists to prevent.

The writing itself is one statement shape repeated. Per section 7.8.50 of the Programmer's Guide, the FROM phrase "will cause literal-1 or identifier-1 to be automatically moved into record-name-1 prior to writing record-name-1's contents", so each line is composed in WORKING-STORAGE, where it can have VALUE clauses and named fields, and copied into the file record on the way out. AFTER ADVANCING is the direction that matters: the guide says that with AFTER "the ADVANCING action writes line-terminator characters to the file and then the record data is written", while BEFORE writes the record first. Reports use AFTER because a printer positions the paper and then prints.

ADVANCING PAGE is the page eject. With no LINAGE clause in the FD, the guide is explicit about what GnuCOBOL emits: the clause "will introduce an ASCII formfeed character into the file", before the record for AFTER PAGE. Page 1 does not use it, because a form feed as the first byte of the file would eject a blank sheet and show up as a stray control character in a text editor; every later page starts with one.

Worked example: the boundary at record 24

Follow the counter through the first page. Start-Page writes five header lines and sets WS-Page-Line to 5. Detail records 1 to 23 each pass the test and each add one, so after record 23 the counter reads 28.

Record 24 arrives. Print-Detail moves 1 into WS-Wanted and performs Check-Room, which computes 28 + 1 + 2 = 31. That exceeds the page size of 30, so End-Page writes the rule and the page-total line, Start-Page writes a form feed and the page-2 header, the counter is reset to 5, and only then is record 24 printed as the first detail line of page 2. The same thing happens at record 47, which lands alone on page 3.

Those numbers come from the Python script in /code/cobol-write-report-file-page-breaks.py, which writes the data file and then builds the report using the same constants and the same edited pictures. Running it prints the arithmetic:

wrote 47 records of 64 bytes to datasets\cobol-write-report-file-page-breaks.dat
page size 30 lines = 5 header + 23 detail + 2 footer
records read     : 47
detail lines/page: 23
pages printed    : 4 (3 detail pages + 1 summary page)
report lines     : 80
detail lines     : 47
page 1 total     : 93,787.36
page 2 total     : 130,760.42
page 3 total     : 4,653.04
debit total      : 98,658.31
credit total     : 130,542.51
net (D less C)   : 31,884.20CR
page break       : record 24 arrives with the line counter at 28 on page 1; 28 + 1 + 2 > 30
page break       : record 47 arrives with the line counter at 28 on page 2; 28 + 1 + 2 > 30
Table 3.2-2
Page Detail lines Lines used Page total
1 23 30 93,787.36
2 23 30 130,760.42
3 1 8 4,653.04
4 (summary) 0 12 none

The three page totals add to 229,200.82, and so do the debit and credit totals: 98,658.31 plus 130,542.51. A control total that cross-foots against a total taken a different way is the point of printing it at all. The net is negative, and it prints as 31,884.20CR, because PIC Z,ZZZ,ZZ9.99CR puts the credit symbol after the digits and leaves two spaces when the value is not negative.

What comes out

The last two pages of the report, with the page-3 boundary and the summary block:

  DAILY TRANSACTION REGISTER                            PAGE   3
  RUN 2026/09/07  07:52:31

  REF     DATE        BR   DESCRIPTION                         AMOUNT T
------------------------------------------------------------------------
  T00047  2026/09/02  LMK  DIRECT DEBIT                      4,653.04 C
------------------------------------------------------------------------
                                        PAGE   3 TOTAL       4,653.04
  DAILY TRANSACTION REGISTER                            PAGE   4
  RUN 2026/09/07  07:52:31

  REPORT SUMMARY
------------------------------------------------------------------------

------------------------------------------------------------------------
  RECORDS READ                                                       47
  DEBIT TOTAL                                               98,658.31
  CREDIT TOTAL                                             130,542.51
  NET (DEBITS LESS CREDITS)                                 31,884.20CR
  PAGES PRINTED                                                       4

The date and time in the header come from FUNCTION CURRENT-DATE, which section 8.1.12 of the guide documents as a 21-character result laid out as year, month, day, hours, minutes, seconds, hundredths, then a signed offset from Greenwich; the program takes the first eight characters as a PIC 9(8) and moves them into PIC 9(4)/9(2)/9(2), where the slashes are simple insertion characters. The function takes no arguments and, as the guide notes, no parentheses.

The LINAGE and END-OF-PAGE alternative

COBOL can keep the line counter for you. Put a LINAGE clause on the FD and the compiler generates a LINAGE-COUNTER special register for that file, and the WRITE statement can then tell you when a page is full:

FD  Report-File
    LINAGE IS 30 LINES
        LINES AT TOP 3
        LINES AT BOTTOM 3
        WITH FOOTING AT 28.
01  Report-Line          PIC X(72).

*> ...and in the PROCEDURE DIVISION:
    WRITE Report-Line FROM WS-Detail-Line AFTER ADVANCING 1 LINE
        AT END-OF-PAGE
           PERFORM End-Page
           PERFORM Start-Page
    END-WRITE

The four numbers describe the paper. Section 6.2.1 of the guide defines LINES AT TOP and LINES AT BOTTOM as the unused margins, LINAGE IS n LINES as the "total number of used/usable lines on the page", and FOOTING AT as the "line number beyond which nothing may be printed except for any footing that is to appear on every page", adding that the three page numbers "should be the total number of possible lines available on one printed page". Here they add to 36, which is a short page; a 66-line page at six lines per inch would be LINAGE IS 60 with three lines top and bottom.

FOOTING AT 28 reproduces the break in the listing above exactly. IBM's Language Reference states the rule crisply: an end-of-page condition occurs when a WRITE "causes the value in the LINAGE-COUNTER special register to equal or exceed the value specified in the WITH FOOTING phrase", and "The WRITE statement is executed, and then the END-OF-PAGE imperative-statement is executed". Five header lines plus 23 detail lines put the counter at 28, so the twenty-third detail line prints and the AT END-OF-PAGE branch runs after it, which is where the footer and the next header belong.

LINAGE and AT END-OF-PAGE are language features, documented as such by both compilers; the counter in the main listing is ordinary program logic, which is why it runs anywhere and survives being ported. The reasons to keep the manual version are that the arithmetic is visible, the same test handles a multi-line block, and nothing about the output depends on how a particular runtime interprets a logical page. The reason to prefer LINAGE is that the counter cannot drift out of step with what was actually written, since the register "cannot be modified" by the program and is maintained by the runtime. GnuCOBOL also ships a third option: section 2.1.13 of the guide describes its implementation of the Report Writer Control System, "fully implemented as of version 3.0", which takes over pagination, headings and totals through declarations in a REPORT SECTION.

What the two dialects do

Table 3.2-3
Question GnuCOBOL 3.x Programmer's Guide IBM Enterprise COBOL 6.4 Language Reference
ADVANCING omitted AFTER ADVANCING 1 LINE on a line-advancing file, BEFORE ADVANCING 1 LINE otherwise "as if AFTER ADVANCING 1 LINE had been specified"
ADVANCING PAGE, no LINAGE writes an ASCII form feed into the file repositions to line 1 of the next page
ADVANCING PAGE, with LINAGE writes line terminators to reach the next page, "No formfeed characters will be generated" repositions to the first printable line of the next page, per the clause
AT END-OF-PAGE fires when the write lands in the footing area defined by LINAGE requires LINAGE in the FD; fires when LINAGE-COUNTER reaches FOOTING
LINAGE-COUNTER BINARY-LONG SIGNED, one per file with LINAGE, "cannot be modified" initialised to 1 by OPEN, reset to 1 by ADVANCING PAGE, must not be modified
Where LINAGE is allowed the FD of a sequential or line sequential file; a sequential file becomes line sequential "effective for sequential files opened as OUTPUT or EXTEND"
ADVANCING on other organizations rejected for relative and indexed files, and can corrupt a record sequential file not supported for VSAM files

Two of those rows are worth pausing on. The default differs between the dialects, so a WRITE with no ADVANCING phrase is not portable and is best written out in full. And on z/OS the line-sequential format of WRITE offers AFTER ADVANCING only, with no BEFORE and no END-OF-PAGE phrase; a print report there is normally a sequential file whose FD carries the LINAGE clause.

Compile and run

python code/cobol-write-report-file-page-breaks.py
cobc -x -I code -o write-register code/cobol-write-report.cob
./write-register

Run all three from the folder that holds code/ and datasets/, since both ASSIGN clauses name paths relative to the working directory. -I code tells cobc where the copybook lives. The guide notes in section 5.2.1 that if the compiler configuration has filename mapping enabled, the runtime first looks for an environment variable to supply the real path, which is how the same program reads a different file under a different job.

Limitations

The output is a text file, and text files lose trailing spaces. The GnuCOBOL Manual documents COB_LS_FIXED as defaulting to false, meaning line sequential files are variable length "by removing trailing spaces", so a 72-character record ends at its last non-blank character. That is invisible in a printed report and fatal to a downstream program that expects fixed-length records; set COB_LS_FIXED if something is going to read the report back. The same appendix documents COB_LS_VALIDATE as on by default, rejecting non-display data on a write with status 71, which is a reason to keep binary fields out of print lines.

Form feeds are the other soft spot. Because AFTER performs the advance before the data, the first WRITE emits its line terminator ahead of the title, so the file opens with an empty line, and at a page boundary the advance is a form feed rather than a newline; the guide's wording implies the previous page's last line and the next page's title are separated by that form feed alone. A pager or a printer handles this correctly, a plain text editor may not, and we did not run the program to confirm the byte order. Whether the final line of the file receives a terminator at CLOSE is not stated in the guide either, so check it on your build if something downstream cares.

Two smaller ones. The detail amount picture holds seven integer digits while the input field holds nine, so an amount of ten million or more would be silently truncated on the high order; size print fields from the largest value the data can hold, not the typical one. And the Programmer's Guide used throughout here says on its own first page that it documents the July 2020 build of GnuCOBOL 3.1 RC-1, so anything that changed in 3.2 would not appear in it. Where the two GnuCOBOL documents disagree, they disagree about line endings: section 5.2.1.2 lists MinGW among the builds that write a bare line feed, while the WRITE section puts MinGW with native Windows on carriage return plus line feed. Do not build a record layout on top of that difference without testing it.

Correction, 29 October 2026: the build command shown before named the source cobol-write-report-file-page-breaks.cob, and GnuCOBOL 3.2's cobc refuses a source file whose base name is longer than 31 characters ("invalid file base name ... length exceeds maximum"), even with -o, so it failed as printed. The listing is now shipped as cobol-write-report.cob, and the old download address redirects there.

Index terms cobol, gnucobol, write, report, page break