0x- 7 cobol examples with explanations.
You may have heard COBOL before. If you search for it you will find images like this:
This is a picture of a COBOL program editor running in a mainframe. Below we will go over 7 examples to COBOL (COmmon Business Oriented Language). We’ll be running these programs on Linux. We are not going cover mainframe tutorials here; I’ve added some mainframe resources at the end.
The sail head dinosaur inside me thanks you for sharing.
How to Install the GnuCobol compiler
This compiler transpiles COBOL to C bytecode that can run on your linux bash command line. Not all the features of COBOL are supported but most are.
Run (to install):
sudo apt-get install open-cobol
How to write a Program
We will write a simple program in cobol called ‘hello.cbl’. There are a lot of strange keywords in cobol. I will explain them after compilation.
*> setup the identification division | |
IDENTIFICATION DIVISION. | |
*> setup the program id | |
PROGRAM-ID. HELLO. | |
*> setup the procedure division (like 'main' function) | |
PROCEDURE DIVISION. | |
*> print a string | |
DISPLAY 'WILLKOMMEN'. | |
*> end our program | |
STOP RUN. |
How to Compile and run
Create the runable bytecode file with the instructions below. This transpiles our COBOL program called ‘hello.cbl’ to C then it takes the C and produces an executable object/bytecode file called ‘hello’.
Compile and then run with:
cobc -x -o hello hello.cbl
./hello
Should output:
WILLKOMMEN
Understanding the Program Structure
First and foremost to write comments in cobol use the *>
characters. In a cobol program there are several possible divisions. A division is just a way to break up the program into areas responsible for different things. So IDENTIFICATION DIVISION
is responsible for identifying the program (docs). We are only going to use the PROGRAM-ID
keyword, giving our program a name, to keep it simple. The DATA DIVISION
is a place where we can declare variables we want to use in our program, we will use the WORKING-STORAGE
keyword (docs). The PROCEDURE DIVISION
is like the main function of our program. It actually runs our code and can access anything defined in the data division (docs). The STOP RUN
sentence (sentence=one or more ‘statements’ that ends with a ‘.’) exits the program (like sys.exit() in python). You will also notice 6 spaces on the left of all my programs. This is not a mistake; the compiler expects this and on a mainframe these 6 spaces would be used for line numbers. Also: EVERYTHING IN COBOL IS CAPITALIZED SO ITS OFTEN EASIER TO TYPE WITH CAPSLOCK ON. You may notice the dot/period at the end of some lines. This is how you end a sentence (a series of one or more statements) in cobol. Below I may refer to anything that ends with a period as a statement. Ok so now that we have gotten through the basics of the program structure let’s write some programs.
Declaring Variables
I will write a script below that explains how to declare and print variables. We will declare several variables in the data division (FIRST-VAR, SECOND-VAR, etc) and then print them in the procedure division using DISPLAY.
IDENTIFICATION DIVISION. | |
PROGRAM-ID. VARS. | |
DATA DIVISION. | |
*> working storage defines variables | |
WORKING-STORAGE SECTION. | |
*> define a number with a sign, 3 numbers, a decimal, and then | |
*> two numbers aafter the decimal. by default it should be 0 filled | |
01 FIRST-VAR PIC S9(3)V9(2). | |
*> do the same thing as above but actually initialize | |
*> to a number -123.45 | |
01 SECOND-VAR PIC S9(3)V9(2) VALUE -123.45. | |
*> defines an alphabetic string and initialize it to abcdef | |
01 THIRD-VAR PIC A(6) VALUE 'ABCDEF'. | |
*> define an alphanumeric string and initialize it to a121$ | |
01 FOURTH-VAR PIC X(5) VALUE 'A121$'. | |
*> create a grouped variable | |
01 GROUP-VAR. | |
05 SUBVAR-1 PIC 9(3) VALUE 337. | |
*> create 3 alphanumerics, but use less than | |
*> the allocated space for each of them | |
05 SUBVAR-2 PIC X(15) VALUE 'LALALALA'. | |
05 SUBVAR-3 PIC X(15) VALUE 'LALALA'. | |
05 SUBVAR-4 PIC X(15) VALUE 'LALALA'. | |
*> print our variables | |
PROCEDURE DIVISION. | |
DISPLAY "1ST VAR :"FIRST-VAR. | |
DISPLAY "2ND VAR :"SECOND-VAR. | |
DISPLAY "3RD VAR :"THIRD-VAR. | |
DISPLAY "4TH VAR :"FOURTH-VAR. | |
DISPLAY "GROUP VAR :"GROUP-VAR. | |
STOP RUN. |
PIC stands for picture (not sure why it is called this) and it is a keyword we use to define a variable. We use functions of the form type(elements)
. So 9(3) would correspond to laying aside enough room in memory for storing a number with 3 values. Above we define many variables and then print them out. You may also notice the 01 values before each declaration, and the 05 value before sub variables. These numbers are called level numbers and indicate to cobol what kind of variable we are declaring. 01 is for top level variables, 05 is group level variables under some other variable. Below are the functions and what each data type above corresponds to.
9 — numeric
A — alphabetic
X — alphanumeric
V — decimal
S — sign
A final example:
01 CAT-PEOPLE PIC X(15) VALUE '12@4A!D$'.
Would create a variable called CAT-PEOPLE
with space for 15 alphanumeric elements that only actually fills out 8 of them. You may have noticed that I do this above in the subgroup. Here is another data declaration resource.
Common Verbs
In cobol a verb is a keyword that does something (docs). We will cover the compute, divide, multiply, subtract, add, move, and initialize verbs. These are verbs you will use often in cobol programming to calculate, say the result of a business transaction.
IDENTIFICATION DIVISION. | |
PROGRAM-ID. VERBS. | |
DATA DIVISION. | |
WORKING-STORAGE SECTION. | |
*> numbers we will perform operations on | |
*> with verbs | |
01 NUM1 PIC 9(9) VALUE 10. | |
01 NUM2 PIC 9(9) VALUE 10. | |
01 NUMA PIC 9(9) VALUE 100. | |
01 NUMB PIC 9(9) VALUE 15. | |
*> variables we will use to store results | |
*> of operations we do | |
01 NUMC PIC 9(9). | |
01 RES-DIV PIC 9(9). | |
01 RES-MULT PIC 9(9). | |
01 RES-SUB PIC 9(9). | |
01 RES-ADD PIC 9(9). | |
01 RES-MOV PIC X(9). | |
PROCEDURE DIVISION. | |
*> compute num1 times num2 and store result in numc | |
COMPUTE NUMC = (NUM1 * NUM2). | |
*> divide numa by numb and store result in res-div | |
DIVIDE NUMA BY NUMB GIVING RES-DIV. | |
*> multiply numa by numb storing result in res-mult | |
MULTIPLY NUMA BY NUMB GIVING RES-MULT. | |
*> subtract numa from numb store result in res-sub | |
SUBTRACT NUMA FROM NUMB GIVING RES-SUB. | |
*> add numa to numb and store result in res-add | |
ADD NUMA TO NUMB GIVING RES-ADD. | |
*> the pointer from numa to | |
MOVE NUMA TO RES-MOV. | |
*> reinitilize num1 | |
INITIALIZE NUM1. | |
*> reinitilize num2 but replace numeric data with 12345 | |
INITIALIZE NUM2 REPLACING NUMERIC DATA BY 12345. | |
DISPLAY "NUMC:"NUMC | |
DISPLAY "RES-DIV:"RES-DIV | |
DISPLAY "RES-MULT:"RES-MULT | |
DISPLAY "RES-SUB:"RES-SUB | |
DISPLAY "RES-ADD:"RES-ADD | |
DISPLAY "RES-MOV:"RES-MOV | |
DISPLAY "REINITIALIZED NUM1: "NUM1 | |
DISPLAY "REINITIALIZED NUM2: "NUM2 | |
STOP RUN. |
compute — can be used to do arithmetic and store the result in a variable
divide — can be used to divide two numbers
multiply — can be used to you guessed it, multiply
add — adds two variables/numbers
move — moves a value or reference from a variable into another variable.
initialize — this is used above to reset a variable after its been set
Conditionals
In this section we will look at if/else statements and switch statements.
IDENTIFICATION DIVISION. | |
PROGRAM-ID. CONDITIONALS. | |
DATA DIVISION. | |
WORKING-STORAGE SECTION. | |
*> setting up places to store values | |
*> no values set yet | |
01 NUM1 PIC 9(9). | |
01 NUM2 PIC 9(9). | |
01 NUM3 PIC 9(5). | |
01 NUM4 PIC 9(6). | |
*> create a positive and a negative | |
*> number to check | |
01 NEG-NUM PIC S9(9) VALUE -1234. | |
*> create variables for testing classes | |
01 CLASS1 PIC X(9) VALUE 'ABCD '. | |
*> create statements that can be fed | |
*> into a cobol conditional | |
01 CHECK-VAL PIC 9(3). | |
88 PASS VALUES ARE 041 THRU 100. | |
88 FAIL VALUES ARE 000 THRU 40. | |
PROCEDURE DIVISION. | |
*> set 25 into num1 and num3 | |
*> set 15 into num2 and num4 | |
MOVE 25 TO NUM1 NUM3. | |
MOVE 15 TO NUM2 NUM4. | |
*> comparing two numbers and checking for equality | |
IF NUM1 > NUM2 THEN | |
DISPLAY 'IN LOOP 1 - IF BLOCK' | |
IF NUM3 = NUM4 THEN | |
DISPLAY 'IN LOOP 2 - IF BLOCK' | |
ELSE | |
DISPLAY 'IN LOOP 2 - ELSE BLOCK' | |
END-IF | |
ELSE | |
DISPLAY 'IN LOOP 1 -ELSE BLOCK' | |
END-IF | |
*> use a custom pre-defined condition | |
*> which checks CHECK-VAL | |
MOVE 65 TO CHECK-VAL. | |
IF PASS | |
DISPLAY 'PASSED WITH 'CHECK-VAL' MARKS.'. | |
IF FAIL | |
DISPLAY 'FAILED WITH 'CHECK-VAL' MARKS.'. | |
*> a switch statment | |
EVALUATE TRUE | |
WHEN NUM1 < 2 | |
DISPLAY 'NUM1 LESS THAN 2' | |
WHEN NUM1 < 19 | |
DISPLAY 'NUM1 LESS THAN 19' | |
WHEN NUM1 < 1000 | |
DISPLAY 'NUM1 LESS THAN 1000' | |
END-EVALUATE. | |
STOP RUN. |
All this should be familiar to you if you have done any programming. You have your standard if/else, not/and/or operators, type comparisons, and switch statements. The only thing that might be a little weird are the pre-defined statements. What has essentially happened here is that the variable CHECK-VAL
has these two conditions that depend on it (hence why PASS
/FAIL
are indented underneath CHECK-VAL
) 88 is a special level number (like 01) for indicating that a statement is a custom conditional that depends on the 01 variable above it. You will also notice our STOP RUN
is not indented here. This exits the whole program which is why you can indent or un-indent it from the procedure division.
Here are some examples using NOT/AND/OR as well as some other extras (imagine putting these into the procedure division above):
*> NOT, negating a conditional | |
MOVE 50 TO NUM1. | |
MOVE 60 TO NUM2. | |
IF NOT NUM2 IS LESS THAN NUM1 THEN | |
DISPLAY NUM2' IS NOT LESS THAN 'NUM1 | |
END-IF | |
*> AND, having multiple conditionals | |
IF NUM1 IS LESS THAN NUM2 AND NUM1 IS LESS THAN 100 THEN | |
DISPLAY 'COMBINED CONDITION' | |
ELSE | |
DISPLAY 'NAH' | |
END-IF | |
*> checking for negative or positive values | |
IF NEG-NUM IS POSITIVE OR NEG-NUM IS NEGATIVE THEN | |
DISPLAY 'A NUMBER IS POSITIVE'. | |
*> checking for negative or positive values | |
IF NEG-NUM IS NEGATIVE THEN | |
DISPLAY 'A NUMBER IS NEGATIVE'. | |
*> checking if a variable is a certain | |
*> data type | |
IF CLASS1 IS ALPHABETIC OR CLASS1 IS NUMERIC THEN | |
DISPLAY 'CLASS1 IS ALPHABETIC or numeric'. |
One last thing to notice: IF
statements that do not have END-IF
need a period to end them inside their last ‘sub statement.’
String Handling
String handling in cobol is very verbose and requires a lot of typing. Let’s try it.
IDENTIFICATION DIVISION. | |
PROGRAM-ID. STRINGHANDLE. | |
DATA DIVISION. | |
WORKING-STORAGE SECTION. | |
01 WS-CNT1 PIC 9(2) VALUE 0. | |
01 WS-CNT2 PIC 9(2) VALUE 0. | |
01 WS-STRING PIC X(25) VALUE 'ABCDADADADABVDFDFFAF'. | |
01 WS-STRING-DEST PIC A(30). | |
01 WS-STR1 PIC A(15) VALUE 'TUTORIALSPOINT'. | |
01 WS-STR2 PIC A(7) VALUE 'WELCOME'. | |
01 WS-STR3 PIC A(7) VALUE 'TO AND'. | |
01 WS-COUNT PIC 99 VALUE 1. | |
01 WS-UNSTR PIC A(30) VALUE 'WELCOME TO TUTORIALSPOINT'. | |
PROCEDURE DIVISION. | |
*> count the number of chars in string, store in ws-cnt1 | |
INSPECT WS-STRING TALLYING WS-CNT1 FOR ALL CHARACTERS. | |
DISPLAY "WS-CNT1 : "WS-CNT1. | |
*> count just the A characters | |
INSPECT WS-STRING TALLYING WS-CNT2 FOR ALL 'A'. | |
DISPLAY "WS-CNT2 : "WS-CNT2. | |
*> replace A chars with X in strings | |
DISPLAY "OLD STRING : "WS-STRING. | |
INSPECT WS-STRING REPLACING ALL 'A' BY 'X'. | |
DISPLAY "NEW STRING : "WS-STRING. | |
*> string concatenate | |
STRING WS-STR2 DELIMITED BY SIZE | |
WS-STR3 DELIMITED BY SPACE | |
WS-STR1 DELIMITED BY SIZE | |
INTO WS-STRING-DEST | |
WITH POINTER WS-COUNT | |
ON OVERFLOW DISPLAY 'OVERFLOW!' | |
END-STRING. | |
DISPLAY 'WS-STRING : 'WS-STRING-DEST. | |
DISPLAY 'WS-COUNT : 'WS-COUNT. | |
*> string split | |
UNSTRING WS-UNSTR DELIMITED BY SPACE | |
INTO WS-STR3, WS-STR2, WS-STR1 | |
END-UNSTRING. | |
DISPLAY 'WS-STR1 : 'WS-STR1. | |
DISPLAY 'WS-STR2 : 'WS-STR2. | |
DISPLAY 'WS-STR3 : 'WS-STR3. | |
STOP RUN. |
Tallying all or just specific characters is pretty clear. The replacing keyword is also pretty clear, it replaces specified data in the string with some other data. Whats really worth digging into here is the string concatenation and the splitting. In the STRING
statement we pass in the original strings WS-STR2
, WS-STR3
, WS-STR1
and we use a DELIMITED
BY to tell the string statement how to combine them. If we delimit by SIZE
we are telling cobol to add the entire input string to the final string. If we delimit by SPACE
we are saying to take the input string up to the first space and omit the rest. The INTO
keyword tells us the variable (WS-STRING-DEST
) where the resulting concatenated string will be stored. WITH POINTER
here manages to count the things in the final string. So somehow putting a pointer to the string counts it as things are concatenated in. I think what happens is it sets a pointer to the beginning and as you push things into the final string it pushes the pointer down several locations which are then stored as a count. The ON OVERFLOW
tells cobol what to do if the input strings are too large; here it prints/displays ‘OVERFLOW!’
The string docs on mainframetechhelp are very useful for understanding this section.
Looping
We will now cover some of the looping logic in cobol. One thing I’ll mention before we get into it: We can name parts of the procedure division; these named parts, called paragraphs, can be used kind of similarly to functions or named lambda functions in python. In cobol a paragraph can contain many sentences/statements.
IDENTIFICATION DIVISION. | |
PROGRAM-ID. LOOPS. | |
DATA DIVISION. | |
WORKING-STORAGE SECTION. | |
01 WS-CNT PIC 9(1) VALUE 0. | |
01 WS-A PIC 9 VALUE 0. | |
01 WS-Z PIC 9 VALUE 2. | |
PROCEDURE DIVISION. | |
*> run the b-para-times paragraph | |
*> 3 times | |
PERFORM B-PARA-TIMES 3 TIMES. | |
*> run b-para-until the count variable | |
*> ws-cnt incremented inside the paragraph is greater than 3 | |
PERFORM B-PARA-UNTIL WITH TEST AFTER UNTIL WS-CNT>3. | |
*> you can actually run from one paragraph | |
*> to another ending paragraph | |
*> through all paragrpahs in between the two | |
PERFORM C-PARA-THRU THRU E-PARA-THRU. | |
*> perform a varying style loop which specified | |
*> a value to increment until it reaches some | |
*> final | |
PERFORM B-PARA-VARY VARYING WS-A FROM 1 BY 1 UNTIL WS-A=5. | |
DISPLAY 'WS-A AFTER VARYING 'WS-A. | |
STOP RUN. | |
*> define paragraphs/functions that will | |
*> be called in our loops above | |
B-PARA-TIMES. | |
DISPLAY 'IN B-PARA-TIMES'. | |
B-PARA-UNTIL. | |
DISPLAY 'WS-CNT : 'WS-CNT | |
ADD 1 TO WS-CNT. | |
C-PARA-THRU. | |
DISPLAY 'IN C-PARA-THRU'. | |
D-PARA-THRU. | |
DISPLAY 'IN D-PARA-THRU'. | |
E-PARA-THRU. | |
DISPLAY 'IN E-PARA-THRU'. | |
B-PARA-VARY. | |
DISPLAY 'IN B-PARA ' WS-A. |
So I think the loops are pretty well explained above. You will notice here we set aside code to be called outside the procedure division. This is because we want to define these as things we can do but we don’t actually want to run them in the procedure division so we put them in paragraphs outside the procedure division. To do this each one needs to have a name that we can reference/use in the procedure division. So B-PARA-TIMES
will only be run when its called in our loop on line 13.
Files
Files in cobol usually have a rigid structure like a table. This is because of what they were created for dealing with: well organized business data. There are a few kinds of files in cobol (docs, another example); we are going to deal with sequential files as they are the most basic. A sequential file consists of records (rows) and each record contains some number of fields (columns). Let’s get to the code.
IDENTIFICATION DIVISION. | |
PROGRAM-ID. FILES. | |
*> create an environment section | |
ENVIRONMENT DIVISION. | |
*> input output is where used files | |
*> will be declared | |
INPUT-OUTPUT SECTION. | |
FILE-CONTROL. | |
*> we will have one file called | |
*> transactions that is sequantially written | |
*> and accessed sequentially as well | |
SELECT TRANSACTIONS ASSIGN TO 'transactions.txt' | |
ORGANIZATION IS SEQUENTIAL. | |
DATA DIVISION. | |
FILE SECTION. | |
*> create a file specification | |
FD TRANSACTIONS. | |
01 TRANSACTION-STRUCT. | |
02 UID PIC 9(5). | |
02 DESC PIC X(25). | |
02 DETAILS. | |
03 AMOUNT PIC 9(6)V9(2). | |
03 START-BALANCE PIC 9(6)V9(2). | |
03 END-BALANCE PIC 9(6)V9(2). | |
02 ACCOUNT-ID PIC 9(7). | |
02 ACCOUNT-HOLDER PIC A(50). | |
*> create a single record for insertion | |
*> this has the same structure as the | |
*> record above but with actual values | |
WORKING-STORAGE SECTION. | |
01 TRANSACTION-RECORD. | |
02 UID PIC 9(5) VALUE 12345. | |
02 DESC PIC X(25) VALUE 'TEST TRANSACTION'. | |
02 DETAILS. | |
03 AMOUNT PIC 9(6)V9(2) VALUE 000124.34. | |
03 START-BALANCE PIC 9(6)V9(2) VALUE 000177.54. | |
03 END-BALANCE PIC 9(6)V9(2) VALUE 53.2. | |
02 ACCOUNT-ID PIC 9(7). | |
02 ACCOUNT-HOLDER PIC A(50). | |
PROCEDURE DIVISION. | |
*> print the record we are writing | |
DISPLAY 'WRITING RECORD: 'TRANSACTION-RECORD. | |
*> open the file in output mode | |
*> this will re-create the file | |
OPEN OUTPUT TRANSACTIONS | |
*> write a record of type transaction-struct | |
*> the actual record being transaction-record | |
WRITE TRANSACTION-STRUCT FROM TRANSACTION-RECORD | |
*> close the file | |
CLOSE TRANSACTIONS | |
STOP RUN. |
So in cobol you need to specify the file and what kind of file it is in the INPUT-OUTPUT SECTION
Then you need to specify what kind of records are in your file. Then you need to create such a record with the exact same structure. Then in the PROCEDURE DIVISION
you open the file (see open modes for details). Then you when writing you specify what kind of record you are adding and the record itself. Here we have opened our file in OUTPUT
mode which always re-creates a file when you open it even if it already exists. You can make writing a file take 100 lines of code in cobol so I tried to keep it as tight as I know how. This is a nice guide on sequential files in cobol. To understand the syntax of WRITE
here are the ibm docs.
Resources for mainframe programming
If you are interested in legacy systems and cobol you will probably want to play around on a mainframe. They are hard to use and look something like this:
The below resources may be helpful:
https://medium.com/@bellmar/hello-world-on-z-os-a0ef31c1e87f (mainframe tutorial)
https://medium.com/@bellmar/mainframe-on-the-macbook-51bc1806d869
https://www.youtube.com/watch?v=Uv7ThVwb7m8 (programming mainframe cobol)
http://www.csis.ul.ie/cobol/examples/default.htm (more cobol examples)
http://www3.sympatico.ca/bredam/GoodBadUgly.html (overview of cobol quirks)
https://devops.com/the-beauty-of-the-cobol-programming-language-v2/ (another programming tutorial with cobol)
https://github.com/mickaelandrieu/awesome-cobol (cobol software)
Conclusion
Cobol is interesting. I think it’s really fascinating that a language like this has been around since the 1950s in some form and to be honest it will probably be around for the foreseeable future. It’s probably useful for some folks to have a grasp on the basics. It has some obvious issues though; it is extremely verbose and the documentation is a bit scattered.
If you liked this share it with a like minded friend.
Thank you for Sharing.