FILE SECTION: чтение и запись файлов

Разбираемся, как COBOL-программа читает файл с данными и создаёт новый — на примере обработки списка клиентов банка.

Файл записей — это набор данных на диске, где каждая «строка» (запись) имеет одинаковую структуру: одни и те же поля в одном и том же порядке.

Зачем это нужно

Представь банк, в котором миллионы счетов. Все данные о счетах не живут в оперативной памяти — они хранятся в файлах на дисках. Ночью, когда клиенты не проводят операции, специальные COBOL-программы («батч-задания») читают эти файлы, начисляют проценты, применяют комиссии и сохраняют результат обратно. Это называется пакетная обработка (batch processing), и именно с ней COBOL справляется лучше почти любого другого языка — он придуман для потоковой работы с большими файлами записей.

Поэтому работа с файлами в COBOL — не факультативная тема, а, можно сказать, весь смысл существования языка. Разберёмся, из каких шагов это состоит.

Файл описывается дважды

В COBOL про каждый файл нужно рассказать компилятору дважды: сначала — где физически лежит файл и как к нему обращаться (в ENVIRONMENT DIVISION), потом — как устроена одна запись внутри файла, из каких полей она состоит (в FILE SECTION, которая является частью DATA DIVISION).

       ENVIRONMENT DIVISION.
       INPUT-OUTPUT SECTION.
       FILE-CONTROL.
           SELECT CLIENT-FILE ASSIGN TO "clients.dat"
               ORGANIZATION IS LINE SEQUENTIAL.

       DATA DIVISION.
       FILE SECTION.
       FD  CLIENT-FILE.
       01  CLIENT-RECORD.
           05  CLIENT-ID       PIC 9(6).
           05  CLIENT-NAME     PIC X(20).
           05  CLIENT-BALANCE  PIC 9(7)V99.

Разберём построчно. SELECT CLIENT-FILE ASSIGN TO "clients.dat" — говорим: «файл, с которым я буду работать под именем CLIENT-FILE, физически лежит в файле clients.dat». ORGANIZATION IS LINE SEQUENTIAL — это самый простой тип файла: записи идут одна за другой, как строки в текстовом файле (в реальных банковских системах чаще используют ORGANIZATION IS INDEXED с быстрым поиском по ключу, но для знакомства с темой хватит и последовательного файла).

FD CLIENT-FILE (File Description) открывает описание структуры записи. Дальше — привычный по прошлым темам курса уровень 01 и вложенные поля 05: у каждого клиента есть CLIENT-ID (шестизначный номер), CLIENT-NAME (текст до 20 символов) и CLIENT-BALANCE (баланс с двумя цифрами после запятой — помнишь V99 из темы про данные? Это подразумеваемая десятичная точка).

Четыре глагола на весь жизненный цикл файла

Работа с файлом в COBOL укладывается всего в четыре команды, и они всегда идут в одном порядке: открыть → читать/писать → закрыть.

КомандаЧто делает
OPEN INPUT / OUTPUTоткрывает файл на чтение или на запись (перезаписывает файл целиком)
READчитает следующую запись из открытого файла
WRITEзаписывает одну запись в открытый файл
CLOSEзакрывает файл, сохраняя все изменения на диск

Обрати внимание: файл нужно явно открыть, указав режим — INPUT (только читать) или OUTPUT (только писать, причём если файл уже существовал, он будет перезаписан с нуля). Есть ещё режим I-O, когда файл и читают, и правят «на месте», но с ним разберёмся в других разделах курса — пока сосредоточимся на базовом цикле.

Цикл чтения файла до конца

Самая частая задача — прочитать файл целиком, обрабатывая каждую запись по очереди, пока файл не кончится. Для этого используют флаг конца файла (end-of-file, сокращённо EOF) и глагол READ ... AT END.

       WORKING-STORAGE SECTION.
       01  WS-EOF-FLAG         PIC X VALUE "N".

       PROCEDURE DIVISION.
       MAIN-LOGIC.
           OPEN INPUT CLIENT-FILE.

           PERFORM UNTIL WS-EOF-FLAG = "Y"
               READ CLIENT-FILE
                   AT END
                       MOVE "Y" TO WS-EOF-FLAG
                   NOT AT END
                       DISPLAY CLIENT-NAME " -- " CLIENT-BALANCE
               END-READ
           END-PERFORM.

           CLOSE CLIENT-FILE.
           STOP RUN.

Смотри, как это работает. Сначала объявляем в WORKING-STORAGE SECTION обычную переменную-флаг WS-EOF-FLAG со значением "N" («нет, файл ещё не кончился»). Открываем файл на чтение — OPEN INPUT CLIENT-FILE. Дальше цикл PERFORM UNTIL: «выполняй, пока флаг не станет "Y"».

Внутри цикла — READ CLIENT-FILE. У этой команды две ветки исхода. AT END срабатывает ровно один раз — когда записи в файле закончились: тогда мы ставим флаг "Y", и на следующей проверке условия цикл остановится. NOT AT END срабатывает при каждом успешном чтении: запись прочиталась, поля CLIENT-RECORD заполнены свежими данными, и мы можем их использовать — здесь просто выводим имя и баланс через DISPLAY. END-READ закрывает условную конструкцию, как END-IF закрывает IF.

После цикла — обязательно CLOSE CLIENT-FILE. Если про закрытие забыть, часть данных может не сохраниться на диск (это касается записи), а сам файл может остаться заблокированным для других программ.

Запись в новый файл

Симметрично устроена и запись. Допустим, мы хотим сохранить в отдельный файл только клиентов с балансом больше нуля.

       OPEN INPUT CLIENT-FILE.
       OPEN OUTPUT ACTIVE-CLIENTS-FILE.

       PERFORM UNTIL WS-EOF-FLAG = "Y"
           READ CLIENT-FILE
               AT END
                   MOVE "Y" TO WS-EOF-FLAG
               NOT AT END
                   IF CLIENT-BALANCE > 0
                       WRITE ACTIVE-RECORD FROM CLIENT-RECORD
                   END-IF
           END-READ
       END-PERFORM.

       CLOSE CLIENT-FILE.
       CLOSE ACTIVE-CLIENTS-FILE.

Здесь открыты сразу два файла: один на чтение (CLIENT-FILE), другой на запись (ACTIVE-CLIENTS-FILE) — это нормально, COBOL-программа может держать открытыми много файлов одновременно. Внутри цикла читаем запись как раньше, а если баланс положительный — вызываем WRITE ACTIVE-RECORD FROM CLIENT-RECORD: записываем в выходной файл содержимое, взятое из только что прочитанной записи. Оба файла закрываются в конце — порядок закрытия не важен, но забывать про него нельзя ни для одного из них.

Вывод (то, что программа напечатает через DISPLAY на примере с тремя клиентами в файле):

Иванов И.И.  -- 0015000.50
Петров П.П.  -- 0000320.00
Сидорова А.А. -- 0198450.75

Частые ошибки

  • Забыть открыть файл перед READ/WRITE. Программа завершится с ошибкой файлового статуса — COBOL не разрешает работать с закрытым файлом.
  • Перепутать режим OPEN. Если открыть файл, который нужно читать, в режиме OUTPUT, COBOL послушно сотрёт его содержимое и начнёт с чистого листа — данные будут потеряны.
  • Забыть флаг конца файла или не проверить его в условии PERFORM. Тогда после того, как записи закончатся, программа попытается читать «в пустоту» ещё раз, что тоже считается ошибкой.
  • Не закрыть файл в конце программы. Особенно опасно для записи: часть данных может остаться «застрявшей» в буфере и не попасть на диск.

Итоги

  • Файл описывается дважды: физическое расположение — в FILE-CONTROL, структура записи — в FILE SECTION с FD.
  • Жизненный цикл файла — всегда OPEN → серия READ или WRITECLOSE.
  • Чтение до конца файла делают через флаг и связку PERFORM UNTIL + READ ... AT END / NOT AT END.
  • Именно этот шаблон лежит в основе ночной пакетной обработки в банках: прочитать миллионы записей, обработать, записать результат.
Проверьте себя
1. Что произойдёт, если открыть уже существующий файл в режиме OPEN OUTPUT?
AПрограмма не скомпилируется
BФайл будет очищен и перезаписан заново
CCOBOL сам переключится в режим добавления записей
DНичего — OUTPUT работает как INPUT
2. Какая ветка условия READ сработает, когда в файле закончились записи?
ANOT AT END
BAT END
CON ERROR
DEND-READ