Devin.KR

순차 파일 읽기 - 레코드 단위 처리

개발자KR 조회 0

이 장에서 배우는 것

앞 장까지 만든 프로그램은 숫자와 문자를 프로그램 안에 직접 적어 두고 처리했다. 실제 일괄 처리는 그렇지 않다. 그날의 거래는 파일에 담겨 도착하고, 프로그램은 그 파일을 처음부터 끝까지 한 건씩 읽는다. 이 장에서는 거래 파일 한 개를 열어 레코드(record, 파일에서 한 건을 이루는 데이터 묶음)를 하나씩 읽고, 입금과 출금을 나누어 합계를 내는 프로그램을 만든다.

  • SELECT 와 ASSIGN 으로 프로그램 안의 파일 이름을 실제 파일에 연결한다.
  • FILE SECTION 의 FD 아래에 고정 길이 레코드의 칸 배치를 정의한다.
  • OPEN INPUT, READ ... AT END, CLOSE 를 올바른 순서로 쓴다.
  • FILE STATUS 값을 확인해 파일이 없거나 읽기가 실패한 경우를 알아챈다.
  • 선행 읽기(priming read)를 쓰는 읽기 루프의 표준 모양을 익힌다.

문제 상황

작은 신용협동조합의 전산 담당자가 되었다고 하자. 지점 창구에서 받은 입금과 출금은 하루 동안 쌓였다가, 마감 후 trans.dat 라는 파일 하나로 모인다. 한 줄이 거래 한 건이다. 맡은 일은 이 파일을 읽어 입금 건수와 출금 건수, 각 합계를 확인하는 것이다.

이 일에는 세 가지 어려움이 있다. 첫째, 파일에는 글자만 이어져 있을 뿐이라 어디까지가 계좌 번호이고 어디부터가 금액인지는 프로그램이 알려 주어야 한다. 둘째, 거래가 몇 건인지 미리 알 수 없으므로 끝에 닿을 때까지 읽어야 한다. 셋째, 파일이 없거나 이름이 틀린 경우에도 프로그램은 조용히 잘못된 결과를 내면 안 된다. 이 장의 내용은 이 세 가지를 차례로 해결한다.

파일을 프로그램에 연결하기

SELECT 와 ASSIGN

COBOL 프로그램은 파일을 두 단계로 다룬다. 먼저 ENVIRONMENT DIVISION 에서 "프로그램 안에서 부를 이름"과 "실제 파일"을 연결하고, 다음으로 DATA DIVISION 에서 그 파일 한 줄의 모양을 정의한다. 연결은 INPUT-OUTPUT SECTION 의 FILE-CONTROL 문단에 쓴다.

       INPUT-OUTPUT SECTION.
       FILE-CONTROL.
           SELECT TRANS-FILE ASSIGN TO "trans.dat"
               ORGANIZATION IS LINE SEQUENTIAL
               FILE STATUS IS WS-FS.

TRANS-FILE 은 프로그램 안에서만 쓰는 이름이다. ASSIGN TO "trans.dat" 는 디스크의 실제 파일 이름이며, 상대 경로이므로 프로그램을 실행하는 폴더를 기준으로 찾는다. ORGANIZATION IS LINE SEQUENTIAL 은 파일이 줄 단위의 텍스트이고 앞에서부터 차례로만 읽을 수 있다는 뜻이다. 이 책의 데이터 파일은 모든 줄의 길이가 같으므로 고정 길이 레코드로 다룬다. FILE STATUS IS WS-FS 는 파일 작업이 끝날 때마다 결과 코드 두 글자를 WS-FS 에 넣어 달라는 요청이다. WS-FS 는 WORKING-STORAGE 에서 PIC XX 로 직접 정의해야 한다.

FILE SECTION 과 FD

FILE SECTION 에는 파일마다 FD(File Description, 파일 기술)를 하나씩 쓰고, 그 아래에 레코드 구조를 둔다. 앞 장에서 배운 그룹 항목과 PIC 를 그대로 쓰면 된다.

       FILE SECTION.
       FD  TRANS-FILE.
       01  TRANS-REC.
           05  TR-ACCT        PIC 9(6).
           05  TR-TYPE        PIC X.
           05  TR-AMOUNT      PIC 9(9).
           05  TR-DATE        PIC 9(8).
           05  TR-BRANCH      PIC X(4).

TRANS-REC 는 READ 가 성공할 때마다 새 줄의 내용으로 채워지는 입력 영역이다. 별도로 MOVE 하지 않아도 READ 가 한 줄을 이 영역에 펼쳐 놓는다.

고정 길이 레코드 배치

고정 길이 레코드에서는 각 필드가 몇 번째 글자에서 시작해 몇 글자를 차지하는지가 곧 파일의 규칙이다. 필드의 PIC 길이를 앞에서부터 더한 값이 한 줄의 길이이고, 이 합이 데이터 파일의 실제 줄 길이와 같아야 한다. 이 장의 거래 레코드는 다음과 같다.

거래 레코드 한 줄(28바이트)의 칸 배치
필드PIC위치뜻
TR-ACCT9(6)1~6계좌 번호
TR-TYPEX7D 는 입금, W 는 출금
TR-AMOUNT9(9)8~16금액(원 단위, 앞을 0으로 채움)
TR-DATE9(8)17~24거래 일자(YYYYMMDD)
TR-BRANCHX(4)25~28지점 코드

6+1+9+8+4 는 28이다. 아래 그림은 같은 배치를 폭에 비례하게 그리고, 실제 한 줄이 어떻게 나뉘는지 보여 준다.

28바이트 한 줄은 계좌, 구분, 금액, 일자, 지점의 다섯 칸으로 나뉘며 각 칸의 길이는 PIC 가 정한다.

읽기 문장 - OPEN, READ, CLOSE

세 문장의 순서

파일을 읽는 일은 항상 열기, 읽기, 닫기의 순서로 진행된다.

  • OPEN INPUT TRANS-FILE 은 파일을 읽기 전용으로 연다. 열기 전에는 READ 를 할 수 없다.
  • READ TRANS-FILE 은 다음 한 줄을 TRANS-REC 로 가져온다. 더 읽을 줄이 없으면 AT END 아래의 문장이 실행된다. 읽기에 성공했을 때만 실행할 문장은 NOT AT END 아래에 쓴다.
  • CLOSE TRANS-FILE 은 파일을 닫는다. 작업이 끝나면 반드시 닫는다.

READ 는 매번 파일의 다음 줄로 한 칸씩 나아갈 뿐 되돌아가지 못한다. 순차 파일(sequential file)이라는 이름이 여기서 나왔다.

FILE STATUS 확인

FILE STATUS 를 지정하면 열기, 읽기, 닫기의 결과가 두 글자 코드로 남는다. 이 코드를 확인하지 않으면 실패해도 프로그램은 계속 진행한다. 자주 만나는 값은 다음과 같다.

순차 파일을 읽을 때 자주 만나는 FILE STATUS 값
값뜻보통의 대응
00작업 성공계속 진행
10파일 끝에 닿음읽기 루프 종료
35열려는 파일이 없음이름과 실행 폴더 확인 후 중단
46끝에 닿은 뒤 다시 READ루프 구조 점검

이 장의 프로그램은 OPEN 직후에 00 이 아니면 중단하고, READ 직후에는 00 과 10 이외의 값이면 중단한다. 파일 끝(10)은 오류가 아니라 정상적인 종료 신호이므로 걸러 내면 안 된다.

읽기 루프의 표준 모양

끝까지 읽는 반복을 쓰는 방법은 여러 가지처럼 보이지만, 안전한 모양은 하나다. 루프에 들어가기 전에 READ 를 한 번 먼저 하고(선행 읽기), 루프 안에서는 처리를 먼저 한 뒤 맨 끝에서 다음 READ 를 한다. 루프의 조건은 "끝에 닿았는가"이다.

           PERFORM READ-TRANS
           PERFORM UNTIL WS-EOF
               PERFORM PROCESS-TRANS
               PERFORM READ-TRANS
           END-PERFORM

이렇게 하면 루프 몸체가 실행되는 시점에는 항상 유효한 레코드가 입력 영역에 들어 있다. 빈 파일이어도 선행 읽기에서 곧바로 끝이 확인되어 몸체가 한 번도 실행되지 않는다. READ 문장이 프로그램 안에 한 군데뿐이므로 AT END 처리와 상태 검사도 한 군데에 모을 수 있다. 아래 그림은 이 흐름을 나타낸다.

첫 READ 를 루프 앞에서 한 번 하고, 처리 뒤에 다음 READ 를 하는 구조여서 끝 검사는 항상 방금 읽은 결과를 본다.

끝 여부는 앞 장에서 다룬 조건 이름(88 레벨)으로 표현했다. WS-EOF-FLAG 에 "Y"가 들어 있으면 WS-EOF 가 참이 되고, SET WS-EOF TO TRUE 가 그 값을 넣는다.

완성 코드

아래 두 파일을 같은 폴더에 둔다. 데이터 파일의 각 줄은 28글자이며, 줄 끝에 개행 문자가 하나씩 있어야 한다.

trans.dat

100001D00005000020261001SEOL
100002W00001200020261001BUSN
100001W00002000020261001SEOL
100003D00012000020261001DAEG
100002D00000300020261001BUSN

main.cob

       IDENTIFICATION DIVISION.
       PROGRAM-ID. MAIN.
       ENVIRONMENT DIVISION.
       INPUT-OUTPUT SECTION.
       FILE-CONTROL.
           SELECT TRANS-FILE ASSIGN TO "trans.dat"
               ORGANIZATION IS LINE SEQUENTIAL
               FILE STATUS IS WS-FS.
       DATA DIVISION.
       FILE SECTION.
       FD  TRANS-FILE.
       01  TRANS-REC.
           05  TR-ACCT         PIC 9(6).
           05  TR-TYPE         PIC X.
           05  TR-AMOUNT       PIC 9(9).
           05  TR-DATE         PIC 9(8).
           05  TR-BRANCH       PIC X(4).
       WORKING-STORAGE SECTION.
       01  WS-FS               PIC XX.
       01  WS-EOF-FLAG         PIC X VALUE "N".
           88  WS-EOF          VALUE "Y".
       01  WS-COUNTERS.
           05  WS-READ-CNT     PIC 9(3) VALUE 0.
           05  WS-DEP-CNT      PIC 9(3) VALUE 0.
           05  WS-WDR-CNT      PIC 9(3) VALUE 0.
           05  WS-BAD-CNT      PIC 9(3) VALUE 0.
       01  WS-TOTALS.
           05  WS-DEP-TOT      PIC 9(9) VALUE 0.
           05  WS-WDR-TOT      PIC 9(9) VALUE 0.
       01  WS-AMT-EDIT         PIC ZZZ,ZZZ,ZZ9.
       01  WS-CNT-EDIT         PIC ZZ9.
       PROCEDURE DIVISION.
       MAIN-PARA.
           OPEN INPUT TRANS-FILE
           IF WS-FS NOT = "00"
               DISPLAY "OPEN FAILED, STATUS=" WS-FS
               STOP RUN
           END-IF
           DISPLAY "--- DAILY TRANSACTIONS ---"
           PERFORM READ-TRANS
           PERFORM UNTIL WS-EOF
               PERFORM PROCESS-TRANS
               PERFORM READ-TRANS
           END-PERFORM
           DISPLAY "LAST STATUS   : " WS-FS
           CLOSE TRANS-FILE
           DISPLAY "--- SUMMARY ---"
           MOVE WS-READ-CNT TO WS-CNT-EDIT
           DISPLAY "RECORDS READ  : " WS-CNT-EDIT
           MOVE WS-DEP-CNT TO WS-CNT-EDIT
           DISPLAY "DEPOSITS      : " WS-CNT-EDIT
           MOVE WS-WDR-CNT TO WS-CNT-EDIT
           DISPLAY "WITHDRAWALS   : " WS-CNT-EDIT
           MOVE WS-BAD-CNT TO WS-CNT-EDIT
           DISPLAY "UNKNOWN TYPE  : " WS-CNT-EDIT
           MOVE WS-DEP-TOT TO WS-AMT-EDIT
           DISPLAY "DEPOSIT TOTAL : " WS-AMT-EDIT
           MOVE WS-WDR-TOT TO WS-AMT-EDIT
           DISPLAY "WITHDRAW TOTAL: " WS-AMT-EDIT
           STOP RUN.
       READ-TRANS.
           READ TRANS-FILE
               AT END
                   SET WS-EOF TO TRUE
               NOT AT END
                   ADD 1 TO WS-READ-CNT
           END-READ
           IF WS-FS NOT = "00" AND WS-FS NOT = "10"
               DISPLAY "READ FAILED, STATUS=" WS-FS
               CLOSE TRANS-FILE
               STOP RUN
           END-IF.
       PROCESS-TRANS.
           EVALUATE TR-TYPE
               WHEN "D"
                   ADD 1 TO WS-DEP-CNT
                   ADD TR-AMOUNT TO WS-DEP-TOT
               WHEN "W"
                   ADD 1 TO WS-WDR-CNT
                   ADD TR-AMOUNT TO WS-WDR-TOT
               WHEN OTHER
                   ADD 1 TO WS-BAD-CNT
           END-EVALUATE
           MOVE TR-AMOUNT TO WS-AMT-EDIT
           DISPLAY TR-ACCT " " TR-TYPE " " WS-AMT-EDIT " " TR-BRANCH.

줄별 해설

  • SELECT ... FILE STATUS IS WS-FS: 프로그램 안의 이름 TRANS-FILE 을 trans.dat 에 연결하고, 작업 결과를 WS-FS 에 받는다.
  • FD TRANS-FILE 과 TRANS-REC: 한 줄을 5개 필드로 나눈다. 길이의 합 28이 데이터 파일의 줄 길이와 같다.
  • WS-EOF-FLAG 와 88 WS-EOF: 끝에 닿았는지를 기억하는 표지다. 처음에는 "N"이다.
  • WS-COUNTERS, WS-TOTALS: 읽은 건수, 입금과 출금 건수, 알 수 없는 구분 건수, 입금과 출금 합계를 담는다. 모두 0으로 시작한다.
  • WS-AMT-EDIT, WS-CNT-EDIT: 앞 장에서 배운 숫자 편집 항목이다. 출력 직전에 MOVE 로 값을 옮겨 천 단위 쉼표를 넣고 앞의 0을 지운다.
  • OPEN INPUT 과 첫 IF: 열기에 실패하면 상태 코드를 보여 주고 곧바로 끝낸다. 열리지 않은 파일을 읽는 일을 막는다.
  • PERFORM READ-TRANS (루프 앞): 선행 읽기다. 첫 번째 거래가 입력 영역에 들어온다.
  • PERFORM UNTIL WS-EOF: 끝에 닿을 때까지 처리와 읽기를 번갈아 한다.
  • DISPLAY "LAST STATUS": 루프를 빠져나온 직후의 상태 코드를 보여 준다. 끝에 닿았으므로 10 이다.
  • CLOSE: 사용을 마친 파일을 닫는다.
  • READ-TRANS 문단: READ 가 이 문단에만 있다. AT END 에서 표지를 켜고, NOT AT END 에서 읽은 건수를 센다. 뒤의 IF 는 00 과 10 이 아닌 코드를 오류로 보고 파일을 닫고 중단한다.
  • PROCESS-TRANS 문단: EVALUATE 로 구분 코드를 나눈다. "D"는 입금, "W"는 출금으로 세고 합산하며, 그 밖의 코드는 알 수 없는 구분으로 센다. 마지막에 금액을 편집해 한 줄로 출력한다.

실행 결과

trans.dat 가 있는 폴더에서 컴파일하고 실행한다.

$ cobc -x -Wall main.cob
$ ./main
--- DAILY TRANSACTIONS ---
100001 D      50,000 SEOL
100002 W      12,000 BUSN
100001 W      20,000 SEOL
100003 D     120,000 DAEG
100002 D       3,000 BUSN
LAST STATUS   : 10
--- SUMMARY ---
RECORDS READ  :   5
DEPOSITS      :   3
WITHDRAWALS   :   2
UNKNOWN TYPE  :   0
DEPOSIT TOTAL :     173,000
WITHDRAW TOTAL:      32,000

입금은 50,000 + 120,000 + 3,000 으로 173,000 원이고, 출금은 12,000 + 20,000 으로 32,000 원이다. 마지막 상태 코드가 10 이라는 점도 확인해 둔다.

실무에서 자주 틀리는 것

1. 선행 읽기 없이 루프 맨 위에서 읽는다

틀린 코드는 루프 안에서 READ 직후 곧바로 처리한다.

           PERFORM UNTIL WS-EOF
               READ TRANS-FILE
                   AT END SET WS-EOF TO TRUE
               END-READ
               PERFORM PROCESS-TRANS
           END-PERFORM

마지막 READ 에서 끝이 확인되어도 같은 반복 안에서 PROCESS-TRANS 가 실행된다. 이때 입력 영역의 내용은 믿을 수 없고, 보통은 직전 레코드가 그대로 남아 있어 마지막 거래가 두 번 처리된 것처럼 보인다. 고친 코드는 앞에서 본 표준 모양이다. 루프 앞에서 한 번 읽고, 처리 뒤에 읽는다.

           PERFORM READ-TRANS
           PERFORM UNTIL WS-EOF
               PERFORM PROCESS-TRANS
               PERFORM READ-TRANS
           END-PERFORM

2. OPEN 결과를 확인하지 않는다

파일 이름이 틀렸는데도 열기 결과를 보지 않는 코드다.

           OPEN INPUT TRANS-FILE
           PERFORM READ-TRANS
           PERFORM UNTIL WS-EOF
               PERFORM PROCESS-TRANS
               PERFORM READ-TRANS
           END-PERFORM

열기는 상태 코드 35 로 실패하지만 프로그램은 계속 진행한다. 열리지 않은 파일을 READ 하면 AT END 조건이 아니라 다른 오류가 나므로 WS-EOF 가 영영 켜지지 않아 반복이 끝나지 않을 수 있다. 열기 직후와 읽기 직후에 상태를 확인한다.

           OPEN INPUT TRANS-FILE
           IF WS-FS NOT = "00"
               DISPLAY "OPEN FAILED, STATUS=" WS-FS
               STOP RUN
           END-IF

3. 레코드 길이의 합이 데이터와 다르다

금액을 9자리가 아니라 8자리로 잘못 정의한 경우다.

       01  TRANS-REC.
           05  TR-ACCT         PIC 9(6).
           05  TR-TYPE         PIC X.
           05  TR-AMOUNT       PIC 9(8).
           05  TR-DATE         PIC 9(8).
           05  TR-BRANCH       PIC X(4).

금액은 한 글자가 모자라 앞 8자리만 읽히고, 일자와 지점은 한 칸씩 앞으로 당겨져 엉뚱한 값이 된다. 컴파일러는 데이터 파일을 보지 않으므로 경고도 나오지 않는다. 필드 길이를 더해 줄 길이와 맞는지 확인하고, 고친 코드는 PIC 9(9) 로 되돌린다.

           05  TR-AMOUNT       PIC 9(9).

4. 다른 폴더에서 실행한다

ASSIGN TO "trans.dat" 는 현재 폴더를 기준으로 파일을 찾는다. 파일이 있는 폴더의 바깥에서 실행하면 열리지 않는다.

$ ./work/main
OPEN FAILED, STATUS=35

프로그램과 데이터 파일이 있는 폴더로 이동한 뒤 실행한다.

$ cd work
$ ./main

한눈에 보기

파일 읽기에 쓰는 구문과 놓는 위치
구문놓는 곳역할주의
SELECT ... ASSIGN TOFILE-CONTROL내부 이름과 실제 파일 연결상대 경로는 실행 폴더 기준
FILE STATUS ISSELECT 안결과 코드 두 글자를 받음PIC XX 로 직접 정의
FD 와 01 레코드FILE SECTION한 줄의 칸 배치 정의길이의 합이 줄 길이와 같아야 함
OPEN INPUTPROCEDURE DIVISION읽기용으로 열기직후에 00 확인
READ ... AT ENDPROCEDURE DIVISION다음 레코드 가져오기끝은 상태 10, 오류가 아님
CLOSEPROCEDURE DIVISION파일 닫기종료 전에 반드시 실행
읽기 루프를 구성하는 세 단계
단계위치하는 일
선행 읽기루프 앞첫 레코드를 가져오고 빈 파일이면 끝을 알아챔
처리루프 몸체 앞부분입력 영역에 있는 레코드를 계산하고 출력
다음 읽기루프 몸체 끝다음 레코드를 가져오거나 끝 표지를 켬

연습 문제

  1. 지점 코드가 "SEOL" 인 거래가 몇 건인지 세어, 요약 끝에 SEOL COUNT : 형태로 출력하도록 고쳐 보라. 어떤 문장을 어디에 추가해야 하는가.
  2. 입금 거래 중 가장 큰 금액을 구해 요약에 출력하려 한다. 필요한 변수와 PROCESS-TRANS 의 변경 부분을 써 보라.
  3. 선행 읽기를 지우고 루프 안에서 PROCESS-TRANS 를 먼저 실행한 뒤 READ-TRANS 를 하도록 순서만 바꾸지 않고, 첫 줄의 PERFORM READ-TRANS 만 삭제하면 처음 반복에서 무슨 일이 일어나는지 설명하라.
  4. ASSIGN TO "trans.dta" 로 오타를 냈을 때 실행 결과는 어떻게 되는가. 그리고 프로그램이 왜 끝까지 안전하게 종료되는지 설명하라.

정답과 해설

  1. WORKING-STORAGE 에 01 WS-SEOL-CNT PIC 9(3) VALUE 0. 을 추가하고, PROCESS-TRANS 안에 IF TR-BRANCH = "SEOL" 아래 ADD 1 TO WS-SEOL-CNT 를 둔다. 요약에서는 MOVE WS-SEOL-CNT TO WS-CNT-EDIT 후 DISPLAY "SEOL COUNT : " WS-CNT-EDIT 로 출력한다. 이 데이터에서는 2건이다. 조건 검사는 읽은 레코드마다 해야 하므로 처리 문단 안에 둔다.
  2. 01 WS-MAX-DEP PIC 9(9) VALUE 0. 을 추가하고, EVALUATE 의 "D" 가지에 IF TR-AMOUNT > WS-MAX-DEP 아래 MOVE TR-AMOUNT TO WS-MAX-DEP 를 넣는다. 요약에서는 WS-AMT-EDIT 로 옮겨 출력한다. 이 데이터의 결과는 120,000 이다. 초깃값을 0으로 두면 첫 입금이 반드시 최댓값으로 바뀐다.
  3. 선행 읽기가 없으면 첫 반복에서 아직 한 줄도 읽히지 않은 입력 영역을 처리하게 된다. 영역의 내용은 정해지지 않았으므로 구분 코드는 "D"도 "W"도 아닐 가능성이 크고, 알 수 없는 구분으로 세어진다. 그 뒤 READ 가 첫 거래를 가져오지만, 반복 순서가 어긋나 있으면 마지막 거래 처리 시점도 함께 어긋난다. 읽은 값을 처리하기 전에 반드시 READ 가 한 번 있어야 한다는 것이 선행 읽기의 이유다.
  4. 열기에서 상태 코드 35 가 나오고 WS-FS NOT = "00" 이 참이 되어 OPEN FAILED, STATUS=35 를 출력한 뒤 STOP RUN 으로 끝난다. READ 를 한 번도 실행하지 않으므로 끝나지 않는 반복도 생기지 않는다. 열기 직후의 상태 확인이 이 안전장치의 전부다.

댓글 0

아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.

댓글을 남기려면 로그인이 필요합니다.