LevSelector.com New York
home > Cobol
Cobol - incomplete tutorial
Introduction
The Basics
Screen Output
Data Declaration
Keyboard Input
Assigning Values to Variables
Arithmetic Operations
Procedural Abstraction : Paragraphs
Procedural Abstraction : Sections
Control Structures : Selection
Control Structures : Iteration
Records
Sequential Files
Data Editing
Arrays
Indexed Files
introduction home - top of the page -

This tutorial is adapted from: http://homepages.paradise.net.nz/milhous/cobol.htm

COBOL is an old language, it is used on mainframes.  There was quite a demand for COBOL programmers before year 2000 - to fix old programs.  Today (2001) it is still an important language, but mainly because there are thousands and thousands of applications out there written in COBOL, and someone has got to fix them and add to them. There aren't as many jobs for COBOL as for C++ or Java, but then again, not a lot of people are learning COBOL anymore.

COBOL really isn't that difficult either, especially if you know other structured languages like Pascal and C. You can easily learn enough to sound intelligent in a day. There aren't many data-structures (actually, everything is really a record), and there are no notions of scope or parameter passing.

 
The Basics home - top of the page -

A "hello world" program is as good a way as any to understand the structure of a COBOL program, so here it is:
IDENTIFICATION DIVISION
PROGRAM-ID HELLO-WORLD
ENVIRONMENT DIVISION
DATA DIVISION
PROCEDURE DIVISION
FIRST-PARA.
    DISPLAY "Hello World".
STOP RUN
This is quite a bit more complex than a similar program an Pascal or C
The first thing to notice are the four divisions :
    IDENTIFICATION DIVISION : This is where you must enter the program name. You can also enter when and by whom it was written.
    ENVIRONMENT DIVISION : This is where you define the files the program needs. You also used to put hardware information here, but that isn't really necessary anymore.
    DATA DIVISION : This is where you declare variables, records, files etc. It is similar to the top of a Pascal program.
    PROCEDURE DIVISION : This is where you write the program. In the sample above this is all in one paragraph. A paragraph is a series of statements named by a label (FIRST-PARA in this case). There is no specific mark to the end of a paragraph.

Each statement begins with a word like DISPLAY that describes the action to be performed by the statement.
There are a couple of hundred reserved words in COBOL so it is useful to put hyphens in user defined names like HELLO-WORLD.

 
Screen output home - top of the page -

Screen output is always one of the first things you want to master with a new language. You have seen an example of screen output with the
    DISPLAY "Hello World".
statement. You can also use it to write digits:
   DISPLAY 300.
Or a combination:
   DISPLAY "The number is " 300.
The problem with number is that this technique only works for unsigned integers, a way around this is as follows:
   DISPLAY "-766.32".
To find out more about using floating-point and signed numbers, see the Arithmetic Operations section.
Also note that you can't make statements like :
   DISPLAY 2 + 3
You have to store the result of 2 + 3 in a variable first. See the Assigning Values to Variables section.

 
Data Declaration home - top of the page -

The HELLO-WORLD program can be altered to demonstrate basic data declaration. In this program two string variables and one number variable are created and given initial values. These are then printed out.
IDENTIFICATION DIVISION
PROGRAM-ID HELLO-WORLD
ENVIRONMENT DIVISION
DATA DIVISION
WORKING-STORAGE SECTION.
01 WS_STRING-1    PIC X(20)
   VALUE "Hello World".
01 WS-STRING-2    PIC X(30)
   VALUE "This is program number ".
01 WS-PROGRAM-NUMBER    PIC 9(2)
   VALUE 1.
PROCEDURE DIVISION
FIRST-PARA.
   DISPLAY WS-STRING-1.
   DISPLAY WS-STRING-2.
   DISPLAY WS-PROGRAM-NUMBER.
STOP RUN

The WORKING-STORAGE SECTION is used for declaring variables other than files and associated records. The 01 before each variable is its level number - the need for these becomes apparent when we deal with records. This is followed by the data name, and then a PIC clause. The PIC clause establishes the type and size of the variable. 'X' represents a character, '9' represents a number. The number following in brackets tells the size (for X(4) you could also write XXXX).

 
Keyboard Input home - top of the page -

Keyboard input is simple. We can use the variable WE-PROGRAM-NUMBER from above to store a new program number:
DISPLAY "Enter the program number".
ACCEPT WS-PROGRAM-NUMBER.
This can only be used to obtain a single variable at a time.

 
Assigning Values to Variables home - top of the page -

The two main keywords used for this are MOVE and COMPUTE.
Continuing with our variable WS-PROGRAM-NUMBER from the Data Declaration section, we can assign a new value for it with the following structure in the PROCEDURE DIVISION section :
      MOVE TO  WS-PROGRAM-NUMBER.
We can give WS-STRING-1 a new value like this :
      MOVE "Hello Everyone" TO WS-STRING-1.
There is a more advanced type of assignment that MOVE can't help us with. Given correctly declared variables, we can write the following in Pascal :
      numA := numB * numC;
The value of numA becomes the value of the expression numB * numC. To do this in COBOL we need to use the COMPUTE  keyword.

Suppose we have created two number variables in the DATA DIVISION section called WS-NUMBER-1 and WS-NUMBER-2. These have been given values, and we want to multiply them together and store the result in WS-PROGRAM-NUMBER. We can do this with the following code in the PROCEDURE DIVISION section :
      COMPUTE WS-PROGRAM-NUMBER = WS-NUMBER-1 * WS-NUMBER-2.
You could also just use numbers :
      COMPUTE WS-PROGRAM-NUMBER = 10 * 5.
Remember to be careful when assigning values to variables. COBOL won't check to see if the new value is appropriate for the variable in either size or type. In fact, it is worth pointing out here, COBOL has very little built in error checking, so be careful.

 
Arithmetic Operations home - top of the page -

The COMPUTE keyword above introduced arithmetic. Bare in mind that there is no mod operator available, and division is done with /.
With division, numbers can be rounded up like this :
      COMPUTE WS-NUMBER-1 ROUNDED = 37/10.
COBOL can also handle floating point numbers. Floating point numbers are declared like this in DATA DIVISION :
      WS-NUMBER-10  PIC 99V99.
This number can hold values with two leading and two trailing spaces like 23.98.
A confusing aspect of this is that to enter a number like this with ASSIGN the user would have to enter 2398.
COBOL can also deal with signed numbers. Simply put an S at the front :
      WS-NUMBER-10  PIC S99V99.
We could now give it the value -32.76.

There are also a collection of keywords that deal with arithmetic. Given the right declarations statements like these sometimes occur :
      ADD WS-NUMBER-1 TO WS-NUMBER-2.
      MULTIPLY WS-NUMBER-1 BY WS-NUMBER-2 GIVING WS-NUMBER-3.
      DIVIDE WS-NUMBER-1 INTO WS-NUMBER-2 GIVING WS-NUMBER-3 REMAINDER WS-NUMBER-4.
      SUBTRACT WS-NUMBER-1 FROM WS-NUMBER-2 GIVING WS-NUMBER-3.
This is all very silly, but COBOL is an old language. As you can see, the GIVING clause is optional.

There is an additional clause you may add to the end :
      ADD WS-NUMBER-1 TO WS-NUMBER-2
      ON SIZE ERROR
          DISPLAY "Size error"
      NOT SIZE ERROR
          DISPLAY WS-NUMBER-1
      END-ADD

 
Procedural Abstraction home - top of the page -

Just like Pascal and C, COBOL allows you to split your code up into small procedures. This is what the PROCEDURE DIVISION portion of a program will resemble when the PERFORM keyword is used to split the code into separate procedures. In this example the procedures (with names like PARA-ONE) call each other in turn :
CONTROL-PARA.
.....................
PERFORM PARA-TWO.
DISPLAY "Paragraph two has been executed".
STOP RUN.

PARA-ONE.
.....................

PARA-TWO.
.....................

PERFORM PARA-THREE.
DISPLAY "Paragraph three has been executed"

PARA-THREE.
DISPLAY "This is the last paragraph"
..................

Note that the paragraph names are arbitrary, and nothing explicitly marks the end of them. They can be called with PERFORM followed by their name. Also, unlike in Pascal, the code to be executed when the program begins is before the other procedures. Make sure the last statement in the first paragraph is STOP RUN or you will find yourself in a lot of trouble.

 
Sections home - top of the page -

A whole group of paragraphs can be combined in a larger structure called a SECTION. A section can then be called with PERFORM, so in effect a section is like a sub-program.
SEC-ONE SECTION.
SEC-CONTROL.
................
PERFORM SEC-ONE-ONE.
..............
GO TO SEC-EXIT.
SEC-ONE-ONE.
.....................
SEC-EXIT.
EXIT.
This could then be called by :
      PERFORM SECTION-ONE.
Control is then passed to the first paragraph in SECTION-ONE, and doesn't return to the rest of the program until GO TO is executed. This is the equivalent of STOP RUN.

In case you were wondering, these aren't really procedures in the Pascal sense. You cannot pass them parameters, and they can't create their own independent variables - everything is global in COBOL.

 
Control Structures : Selection home - top of the page -

COBOL has fewer structures for controlling program flow than other languages like C, but it does the job.
Control Structures rely on conditionals to evaluate between alternatives. COBOL includes the following :
      <, >, >=, <=, =, NOT=
These are self explanatory, and they also have word form equivalents:
      LESS, GREATER, GREATER OR EQUAL, LESS OR EQUAL, EQUAL, NOT EQUAL
There are also the three main logical operators :
      AND, NOT, OR
Selection statements can now be made as follows :
      IF  WS-NUMBER-1 > 100
      THEN
          DISPLAY "Greater than 100".
         ...........
      END IF.
We can also add an ELSE clause :
      IF  WS-NUMBER-1 > 100
      THEN
          DISPLAY "Greater than 100".
      ELSE
         DISPLAY "Not greater than 100"
      END IF.
You can also nest selection structures inside other selection structures, just remember to put the right number of END IF statements in.

There is also the equivalent of the Pascal Case statement. This one evaluates WS-NUMBER-1, and calls a different procedure for each possible value :
      ACCEPT WS-NUMBER-1.
      EVALUATE WS-NUMBER-1
         WHEN "1" PERFORM PARA-ONE
         WHEN "2" PERFORM PARA-TWO
         WHEN "3" PERFORM PARA-THREE
         WHEN OTHER PERFORM PARA-FOUR
      END-EVALUATE

You can also test to see if a piece of data is one of four class types :
      ALPHABETIC, ALPHABETIC-UPPER, ALPHABETIC-LOWER, NUMERIC
So we can have statements like
      IF WS-NUMBER-1 NUMERIC ......

 
Control Structures : Iteration home - top of the page -

The Pascal While statement can be performed like this :
      ACCEPT WS-NUMBER-1
      PERFORM UNTIL WS-NUMBER-1 > 100
                     AND WS-NUMBER-1 < 1000
           DISPLAY "Still between 100 and 1000"
          ACCEPT WS-NUMBER-1
      END-PERFORM

The Pascal For statement is implemented  like this :
       PERFORM TEST AFTER VARYING WS-INDEX FROM
             1 BY 1 UNTIL WS-INDEX = 10
         DISPLAY "WS-INDEX is now equal to " WS-INDEX
      END-PERFORM
This is more complex than Pascal, but it is still pretty obvious what is going on - it starts at 1, increments by 1, and goes until 10.

 
Records home - top of the page -

A record is a set of related information grouped together. They are extremely common in COBOL applications. Here is how one might be declared in COBOL to hold information about a person :
      DATA DIVISION
      WORKING STORAGE SECTION
      01 WS-PERSON-RECORD
          03 WS-NAME       PIC X(20)
          03 WS-ADDRESS  PIC X(40)
          03 WS-AGE           PIC 9(2)
      01 WS-STRING         PIC X(80)

Note that the top line has no PIC clause - this is the group item. The others are the elementary items.

We can now use this is various ways. Firstly, we can initialize it as empty like this :
      MOVE SPACES TO WS-PERSON-RECORD
We can now put some values into the elements :
      MOVE 22 TO WS-AGE
      MOVE "Dane" TO WS-NAME
      MOVE "New Zealand" TO WS-ADDRESS
      MOVE WS-ADDRESS TO WS-STRING
      DISPLAY WS-STRING.
This is also possible :
      MOVE WS-PERSON-RECORD TO WS-STRING
      DISPLAY WS-STRING
The data in a record is alphanumeric. Even if it is a number, though, arithmetic arguments can't be applied to it.

You can also nest records inside records :
      01 WS-PERSON-RECORD
          03 WS-NAME             PIC X(20)
          03 WS-ADDRESS
              05 WS-STREET       PIC X(20)
              05 WS-CITY          PIC X(20)

Note now that WS-ADDRESS doesn't have a PIC clause.

The reason the levels have been ordered 01, 03, 05 is simple. They could be ordered 01, 02, 03; but then if you wanted to insert a new level between 01 and 02, you would also have to change the value of 03.

Notice that you can directly reference a field, unlike in Pascal where you need to use A dot operator like  RecordName.FieldName. However, if a field name is used elsewhere for another purpose you need to give the record name :
      MOVE SPACES TO WS-AGE IN WS-PERSON-RECORD

 
Sequential Files home - top of the page -

A sequential file consists of a sequence of data items terminated by an end marker, and stored on a disk or some other permanent storage device.
To use a sequential file in COBOL you must make declarations in three of the four divisions.
Firstly, the following code is placed in the ENVIRONMENT DIVISION :
ENVIRONMENT DIVISION
INPUT-OUTPUT SECTION.
FILE-CONTROL.
    SELECT DATA-FILE-1
    ASSIGN TO DEVICE-NAME
    ORGANIZATION IS SEQUENTIAL
    ACCESS MODE IS SEQUENTIAL

Actually, the last two lines aren't needed since they are the default conditions for a sequential file.
Each file that is going to be used must have its own SELECT division.
With regards to DEVICE-NAME, this can vary, but usually DISK and PRINTER will be two of the options available.
Each file must also be defined in the DATA DIVISION section:
DATA DIVISION.
FILE SECTION.
FD DATA-FILE-1.
    01 PERSON-REC.
        03 NAME          PIC X(20)
        03 ADDRESS    PIC X(30)

These records are defined just the same as those in the WORKING-STORAGE section.

Now we can use READ and WRITE to work with these files in the PROCEDURE DIVISION section, but first it must be opened in this section. This code opens the file and transfers the first record to the record area defiled in FILE SECTION :
PROCEDURE DIVISON.
PARA-1.
    OPEN INPUT DATA-FILE-1.
    READ DATA-FILE-1
        AT END
            MOVE "E" TO WS-END-OF-DATA-FILE.

To open a file for writing use the structure :
      OPEN OUTPUT DATA-FILE-1.

The AT END clause is necessary : it tells the file what to do when it hits the end of the file. In this example a status variable is assigned to WS-END-OF-DATA-FILE. Naturally, this needs to be declared and initialized earlier like this :
WORKING-STORAGE SECTION.
    01 WS-END-OF-DATA-FILE      PIC X
                     VALUE "N".

It is also useful to add a condition name like this on the next line :
        88 END-OF-DATA-FILE      VALUE "E"

We can now test for the end of the file in a loop :
PROCEDURE DIVISION
PARA-1.
    OPEN INPUT DATA-FILE-1.
    READ DATA-FILE-1.
        AT END
            MOVE "E" TO WS-END-OF-DATA-FILE
        NOT AT END
            ADD 1 TO RECORD-COUNT
    END READ
............

Given that RECORD-COUNT had been declared, it would count the number of records in the file.

We use the keyword WRITE to transfer data to a file. This code opens a file and writes a record :
PROCEDURE DIVISION.
PARA-1.
    OPEN OUTPUT DATA-FILE-1.
    MOVE "Dane" TO NAME
    MOVE "New Zealand" TO ADDRESS.
    WRITE PERSON-REC.
    CLOSE DATA-FILE-1.
............

Take into account though, that is DATA-FILE-1 already had data in it, a new file would be created, and the old data would be written over. Also note that unlike READ which accepted a file name as its parameter, WRITE accepts a record name. Also remember to CLOSE each file you open or bad things will happen.

WRITE also allows you to append records to an existing file. Simply declare the file as in the previous examples, and then open it with the following code :
      OPEN     EXTEND  DATA-FILE-1.

It is also possible to edit existing records in place by opening the file for input and output. This code searches the file for a record with a certain address, and then changes that address (note : RECORD-FOUND must first be declared in a similar fashion to WS-END-OF-DATA-FILE) :
PROCEDURE DIVISION.
PARA-1.
        OPEN  I-O DATA-FILE-1.
        READ DATA-FILE-1
          AT END
                MOVE "E" TO WS-END-OF-DATA-FILE
             DISPLAY "DATA NOT FOUND"
        END-READ
        PERFORM UNTIL END-OF-DATA-FILE OR RECORD-FOUND
          IF ADDRESS = "New Zealand"
             THEN
                    MOVE "Australia" TO ADDRESS
                 REWRITE PERSON-REC
                 MOVE "Y" TO WS-RECORD-FOUND
          ELSE
                READ DATA-FILE-1
             AT END
                    MOVE "E" TO WS-END-OF-DATA-FILE
                 DISPLAY "DATA NOT FOUND"
             END-READ
            END-IF
        END-PERFORM
        CLOSE DATA-FILE-1
        STOP RUN.
COBOL also allows you to create indexed files.

 
Data Editing home - top of the page -

COBOL is mainly a language for creating data storage applications, so it has many built in facilities for data editing and report making. COBOL can handle these topics far better than other structured languages like Pascal and C.

A common situation in report writing is the need to write right aligned output like this

$200.50
$45,600.00
$20.00
$1,800,900.00
This would be very difficult in many languages, but COBOL can easily handle it. We simply create a data type like this :
    01 WS-CURRENCY-OUTPUT      PIC $$,$$$,$$9.99.
This is how the following numbers would be outputted :
2050            $20.50
3045000    $30,450.00
50             $0.50
As you can see, a number signaled by a "$" is only included if it's needed, and the output is right aligned.
Remember that these special data types are only used for output, you can't do arithmetic with them.
You can also do the same thing with "+" and "-" signs :
      PIC ++++++9.99
      PIC ----9.9
This allows you to get rid of irrelevant zeros when you are outputting data.

 
Arrays home - top of the page -

Arrays aren't quite as natural to COBOL as they are to most other languages, but that is a sign of COBOL's age. Here is how an array might be created to store the salaries of ten employees :
01 WS-EMPLOYEE-REC
    03 WS-EMPLOYEE-SALARY   PIC 99999
        OCCURS 10 TIMES

You may think it would make sense to declare all this at level 01, but the OCCURS clause cannot be used at level 01.
You can then use parenthesis to refer to a particular element in the array :
      DISPLAY  WS-EMPLOYEE-SALARY(3)
This will print out the fifth element (as opposed to C, where it would print out the sixth element).
We can create a variable like WS-INDEX, and use it in place of the number :
      DISPLAY  WS-EMPLOYEE-SALARY(WS-INDEX)
But we can't use expressions like :
      DISPLAY  WS-EMPLOYEE-SALARY(WS-INDEX + 1)
The value of WS-INDEX would need to be incremented before hand.
It is also simple to declare an array of records. This code creates 20 records with three fields each :
01 EMPLOYEE-REC-TABLE
    03 WS-EMPLOYEES   OCCURS 50 TIMES.
          05 WS-NAME        PIC X(20)
            05 WS-ADDRESS  PIC X(40)
            05 WS-SALARY    PIC 99999

We can now refer to an entire record at a time with :
      WS-EMPLOYEE(10)
Or to a single field of a record :
      WS-ADDRESS(30)
We can also create multi-dimensional arrays :
01 WS-TABLE VALUE ZEROS.
    03 WS-MONTH
          OCCURS 12 TIMES
       05 WS-FIVE-HIGHEST-TEMPERATURES  PIC 999
          OCCURS 5 TIMES

Note here the VALUE ZEROS. clause : this initializes all the elements to zero (including strings).
We can now access a specific element like this (which shows the 2nd highest temperature in May):
      WS-TABLE(5, 2)
Or we could access all the temperatures for November like this :
      WS-MONTH(11)

 
Indexed Files home - top of the page -

Indexed Files have an advantage over sequential files in the sense that they can be accessed randomly. To find a record in a sequential file we need to look through all the records in order, but with an indexed file we can identify and retrieve records according to their unique key values.

This is how a file might be declared in the ENVIRONMENT DIVISION section :
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
        SELECT EMPLOYEE-FILE
        ASSIGN TO DISK
        ORGANIZATION IS INDEXED
        ACCESS MODE IS RANDOM
        RECORD KEY IS EMPLOYEE-NUMBER
        FILE STATUS IS EMPLOYEE-STATUS

The RECORD KEY is equivalent to a primary key in a relational database. Bare in mind that this cannot be changed once the record has been created.
The FILE STATUS clause is optional. It is must be declared as a two byte character in WORKING-STORAGE, and is used mainly for error handling.
The declaration in DATA DIVISION is the same as with sequential files, but remember to use the same key value as in ENVIRONMENT DIVISION :
DATA DIVISION.
FILE SECTION.
FD    EMPLOYEE-FILE.
    01 EMPLOYEE-REC.
        03 EMPLOYEE-NUMBER       PIC X(6).
        03 EMPLOYEE-NAME          PIC X(20).
        03 EMPLOYEE-SALARY         PIC 9(5).
WORKING-STORAGE SECTION.
    01 EMPLOYEE-STATUS            PIC XX.

We can now move onto the PROCEDURE DIVISION section.
Firstly we must open the file for input and output :
      OPEN    I-O    EMPLOYEE-FILE.
Also, remember to close it when you have finished :
      CLOSE EMPLOYEE-FILE.

To work with the file we use the keywords READ, WRITE, DELETE and REWRITE.

If we want to read a file, we give the key value of the record we want, and if it exists it will be transferred into the file's record area :
       MOVE "AR8763" TO EMPLOYEE-NUMBER.
       READ EMPLOYEE-FILE
          INVALID KEY
                DISPLAY "No such file exists."

This is how we write a record to file, supposing the variables mentioned had been created in the WORKING-STORAGE section :
       MOVE "RW3425" TO WS-EMPLOYEE-NUMBER
       MOVE "JERRY SEINFELD" TO WS-EMPLOYEE-NAME.
       MOVE 56000 TO WS-EMPLOYEE-SALARY
       WRITE EMPLOYEE-REC FROM WS-EMPLOYEE-REC.
We have first put the values into an intermediary storage area, and then written the record to file in one go.

To modify a record we would usually do a READ followed by a REWRITE  :
        MOVE "RW3425" TO WS-EMPLOYEE-NUMBER
        MOVE "KRAMER" TO WS-EMPLOYEE-NAME
        MOVE 65000 TO WS-EMPLOYEE-SALARY
        REWRITE EMPLOYEE-REC FROM WS-EMPLOYEE-REC
          INVALID KEY
                DISPLAY "Record doesn't exist."

This is how we could delete the record we just created (you would normally do a READ first to make sure you have the right record :
       MOVE "RW3425" TO EMPLOYEE-NUMBER
       DELETE EMPLOYEE-FILE
          INVALID KEY
                DISPLAY "That record doesn't exist."