Program Structure
Hello, World
puts "Hello, World!" >>SOURCE FORMAT IS FREE
IDENTIFICATION DIVISION.
PROGRAM-ID. HELLO.
PROCEDURE DIVISION.
DISPLAY "Hello, World!"
STOP RUN. Every COBOL program begins with
IDENTIFICATION DIVISION and ends with STOP RUN. The four DIVISIONS must appear in a fixed order: IDENTIFICATION, ENVIRONMENT, DATA, PROCEDURE. The minimal program above has only IDENTIFICATION and PROCEDURE β ENVIRONMENT and DATA are optional when the program needs no file access or stored data.Comments
# Single-line comment
name = "Alice" # inline
puts name >>SOURCE FORMAT IS FREE
IDENTIFICATION DIVISION.
PROGRAM-ID. COMMENTS.
PROCEDURE DIVISION.
*> This is a comment
DISPLAY "COBOL uses *> for comments" *> inline too
STOP RUN. In free-format COBOL,
*> begins a comment to the end of the line β the direct equivalent of Ruby's #. In traditional fixed-format COBOL, an asterisk in column 7 makes the entire line a comment. The *> syntax was introduced in COBOL 2002 and works in both free and fixed format.DATA DIVISION
name = "Alice"
age = 30
puts name
puts age >>SOURCE FORMAT IS FREE
IDENTIFICATION DIVISION.
PROGRAM-ID. DATA-DIVISION.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-NAME PIC X(20) VALUE "Alice".
01 WS-AGE PIC 9(3) VALUE 30.
PROCEDURE DIVISION.
DISPLAY WS-NAME
DISPLAY WS-AGE
STOP RUN. COBOL separates data declarations from executable code entirely. The DATA DIVISION's WORKING-STORAGE SECTION holds variables that persist for the program's lifetime β similar to Ruby's instance variables but all at the same scope level. Every variable must be declared with a PICTURE (PIC) clause that precisely defines its format and size.
The Four DIVISIONS
# Ruby: data and logic freely intermixed
message = "Hello"
def build_greeting(name)
"Hello, #{name}!"
end
puts build_greeting("World") >>SOURCE FORMAT IS FREE
IDENTIFICATION DIVISION. *> Who wrote this?
PROGRAM-ID. FOUR-DIVISIONS.
ENVIRONMENT DIVISION. *> Platform & file config
*> (empty β no files used here)
DATA DIVISION. *> What data does it use?
WORKING-STORAGE SECTION.
01 WS-GREETING PIC X(30).
PROCEDURE DIVISION. *> What does it do?
MOVE "Hello, World!" TO WS-GREETING
DISPLAY WS-GREETING
STOP RUN. Every COBOL program has up to four DIVISIONS in a fixed order: IDENTIFICATION (program metadata), ENVIRONMENT (platform and file configuration), DATA (variable declarations), and PROCEDURE (executable code). Ruby has no equivalent structure β code and data intermingle freely in any order. The rigid DIVISION structure was designed to make programs self-documenting and machine-readable from punch cards.
Free vs Fixed Format
# Ruby has no column restrictions
message = "COBOL once required column alignment"
puts message >>SOURCE FORMAT IS FREE
IDENTIFICATION DIVISION.
PROGRAM-ID. FREE-FORMAT.
*> Modern COBOL: no column restrictions (COBOL 2002+)
*>
*> Traditional fixed format required:
*> Columns 1-6: Sequence numbers (ignored)
*> Column 7: Indicator (* = comment, - = continuation)
*> Columns 8-11: Area A (DIVISION/SECTION/paragraph names)
*> Columns 12-72: Area B (statements)
PROCEDURE DIVISION.
DISPLAY "Free-format COBOL since COBOL 2002"
STOP RUN. The infamous COBOL column restrictions were a relic of 80-column punch cards from the 1960s. In fixed format, division and section names had to start in column 8 (Area A) and statements in column 12 (Area B) β getting this wrong caused cryptic errors. Modern COBOL (2002+) eliminates these restrictions with the
>>SOURCE FORMAT IS FREE directive. All examples on this page use free format.Data Types & PIC Clauses
PIC X β Alphanumeric
name = "Alice"
puts name >>SOURCE FORMAT IS FREE
IDENTIFICATION DIVISION.
PROGRAM-ID. PIC-X.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-NAME PIC X(20) VALUE "Alice".
PROCEDURE DIVISION.
DISPLAY WS-NAME
STOP RUN. PIC X(n) declares a fixed-width alphanumeric field of n characters β like a Ruby String, but with a fixed maximum length that never changes. When you store a shorter string, COBOL pads it with spaces on the right. When you store a longer string, COBOL silently truncates it. There is no dynamic resizing β a PIC X(20) field is always exactly 20 bytes in memory.PIC 9 β Unsigned Integer
count = 42
puts count >>SOURCE FORMAT IS FREE
IDENTIFICATION DIVISION.
PROGRAM-ID. PIC-9.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-COUNT PIC 9(5) VALUE 42.
PROCEDURE DIVISION.
DISPLAY WS-COUNT
STOP RUN. PIC 9(n) declares an unsigned decimal integer with exactly n digits. A PIC 9(5) field holds values from 0 to 99999 β the 9 in the PICTURE clause represents one decimal digit, and (5) is a repetition count. COBOL stores numbers in decimal by default, not binary. This guarantees exact decimal arithmetic, which is critical for the financial calculations COBOL was designed to perform.PIC S9 β Signed Integer
balance = -500
puts balance >>SOURCE FORMAT IS FREE
IDENTIFICATION DIVISION.
PROGRAM-ID. PIC-S9.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-BALANCE PIC S9(7) VALUE -500.
PROCEDURE DIVISION.
DISPLAY WS-BALANCE
STOP RUN. The
S prefix in PIC S9(n) makes a numeric field signed so it can hold negative values. Without S, a COBOL numeric field cannot store a negative number β attempting to do so causes silent data corruption. The sign is stored as a zone code in the last digit or as a separate byte, depending on the USAGE clause. Omitting S from a field that participates in signed arithmetic is a common COBOL bug.PIC 9V9 β Decimal (Fixed Point)
price = 19.99
puts price >>SOURCE FORMAT IS FREE
IDENTIFICATION DIVISION.
PROGRAM-ID. PIC-V.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-PRICE PIC 9(5)V99 VALUE 19.99.
PROCEDURE DIVISION.
DISPLAY WS-PRICE
STOP RUN. The
V in a PICTURE clause marks the implied decimal point position β it is not stored as a character. PIC 9(5)V99 defines a field with 5 digits before the decimal and 2 after. No decimal character is stored in memory; COBOL tracks where the decimal falls conceptually. This is how COBOL avoids floating-point precision problems: all financial arithmetic uses exact decimal storage, never IEEE 754 binary floating-point.VALUE β Initial Values
count = 0
greeting = "Hello"
flag = false
puts count
puts greeting
puts flag >>SOURCE FORMAT IS FREE
IDENTIFICATION DIVISION.
PROGRAM-ID. VALUE-CLAUSE.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-COUNT PIC 9(5) VALUE 0.
01 WS-GREETING PIC X(20) VALUE "Hello".
01 WS-FLAG PIC X(1) VALUE "N".
PROCEDURE DIVISION.
DISPLAY WS-COUNT
DISPLAY WS-GREETING
DISPLAY WS-FLAG
STOP RUN. The VALUE clause initializes a WORKING-STORAGE field when the program starts. Without it, numeric fields default to zero and alphanumeric fields default to spaces. COBOL has no boolean type; flags are typically represented as
PIC X(1) fields with values "Y"/"N" or "T"/"F". VALUE is only valid in WORKING-STORAGE β not in FILE SECTION or LINKAGE SECTION fields.Group Items (Nested Data)
person = { first_name: "Alice", last_name: "Smith", age: 30 }
puts person[:first_name]
puts person[:last_name]
puts person[:age] >>SOURCE FORMAT IS FREE
IDENTIFICATION DIVISION.
PROGRAM-ID. GROUP-ITEM.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-PERSON.
05 WS-FIRST-NAME PIC X(20) VALUE "Alice".
05 WS-LAST-NAME PIC X(20) VALUE "Smith".
05 WS-AGE PIC 9(3) VALUE 30.
PROCEDURE DIVISION.
DISPLAY WS-FIRST-NAME
DISPLAY WS-LAST-NAME
DISPLAY WS-AGE
STOP RUN. A COBOL group item is a level-01 item with subordinate items at higher level numbers (05, 10, 15, etc.). It is similar to a Ruby hash or struct β a named collection of related fields. The group item itself (
WS-PERSON) has no PIC clause; its size is the sum of all its children. Referencing the group item as a whole treats it as a flat PIC X field of the combined length of all children concatenated.88-Level Condition Names
status = "ACTIVE"
if status == "ACTIVE"
puts "Account is active"
end >>SOURCE FORMAT IS FREE
IDENTIFICATION DIVISION.
PROGRAM-ID. LEVEL-88.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-STATUS PIC X(8) VALUE "ACTIVE".
88 ACCOUNT-ACTIVE VALUE "ACTIVE".
88 ACCOUNT-INACTIVE VALUE "INACTIVE".
88 ACCOUNT-FROZEN VALUE "FROZEN".
PROCEDURE DIVISION.
IF ACCOUNT-ACTIVE
DISPLAY "Account is active"
END-IF
STOP RUN. Level-88 condition names define named boolean conditions attached to a data field. The condition is TRUE when the parent field equals one of the VALUES listed. They make business logic read like plain English:
IF ACCOUNT-ACTIVE is self-documenting in a way that IF WS-STATUS = "ACTIVE" is not. The closest Ruby analogy is predicate methods like account.active?. Level-88 items are one of COBOL's most celebrated and distinctive features.COMP and COMP-3 Usage
# Ruby Integer has no fixed storage format
big_number = 12345678
puts big_number >>SOURCE FORMAT IS FREE
IDENTIFICATION DIVISION.
PROGRAM-ID. COMP-USAGE.
DATA DIVISION.
WORKING-STORAGE SECTION.
*> DISPLAY: one digit per byte (default)
01 WS-DISPLAY PIC 9(8) VALUE 12345678.
*> COMP: binary storage (faster arithmetic)
01 WS-BINARY PIC 9(8) COMP VALUE 12345678.
*> COMP-3: packed decimal (2 digits per byte)
01 WS-PACKED PIC 9(8) COMP-3 VALUE 12345678.
PROCEDURE DIVISION.
DISPLAY WS-DISPLAY
DISPLAY WS-BINARY
DISPLAY WS-PACKED
STOP RUN. The USAGE clause controls how a numeric field is stored in memory. The default (DISPLAY) stores one digit per byte as a character. COMP stores the value in binary like a C integer, which is faster for arithmetic. COMP-3 (packed decimal) stores two digits per byte β the IBM mainframe standard that is space-efficient and still common in legacy systems today. All three produce the same numeric value when displayed; only the internal storage representation differs.
Variables & MOVE
WORKING-STORAGE (All Variables)
# Ruby: local variables live in method scope
message = "Hello from Ruby"
puts message >>SOURCE FORMAT IS FREE
IDENTIFICATION DIVISION.
PROGRAM-ID. WORKING-STORAGE.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-MESSAGE PIC X(30) VALUE "Hello from COBOL".
PROCEDURE DIVISION.
DISPLAY WS-MESSAGE
STOP RUN. COBOL has no local variables. All variables in a standalone COBOL program live in WORKING-STORAGE SECTION and persist for the entire run. They are initialized once via VALUE clause and retain their values between paragraph calls β equivalent to Ruby instance variables, except there is no concept of scope. All WORKING-STORAGE fields are effectively global within the program.
MOVE (Assignment)
greeting = "Hello"
greeting = "Goodbye"
puts greeting >>SOURCE FORMAT IS FREE
IDENTIFICATION DIVISION.
PROGRAM-ID. MOVE-BASIC.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-GREETING PIC X(10).
PROCEDURE DIVISION.
MOVE "Hello" TO WS-GREETING
DISPLAY WS-GREETING
MOVE "Goodbye" TO WS-GREETING
DISPLAY WS-GREETING
STOP RUN. The MOVE statement is COBOL's assignment operator β there is no
= for assignment. MOVE always copies; it never moves in the C++ or Rust sense. MOVE pads with spaces (for alphanumeric) or zeros (for numeric) when the source is shorter than the destination. MOVE SPACES TO field clears an alphanumeric field; MOVE ZEROS TO field clears a numeric field.MOVE to Multiple Targets
source = "Alice"
copy_one = source
copy_two = source
puts copy_one
puts copy_two >>SOURCE FORMAT IS FREE
IDENTIFICATION DIVISION.
PROGRAM-ID. MOVE-MULTIPLE.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-SOURCE PIC X(10) VALUE "Alice".
01 WS-COPY-ONE PIC X(10).
01 WS-COPY-TWO PIC X(10).
PROCEDURE DIVISION.
MOVE WS-SOURCE TO WS-COPY-ONE WS-COPY-TWO
DISPLAY WS-COPY-ONE
DISPLAY WS-COPY-TWO
STOP RUN. COBOL's MOVE can copy one value into multiple destinations in a single statement:
MOVE source TO dest1 dest2 dest3. The source is evaluated once and copied to each destination sequentially. This is unique to COBOL β most languages require a separate assignment for each variable. It is commonly used to initialize multiple fields to the same value or to copy a record into several buffers simultaneously.Figurative Constants (SPACES, ZEROS)
name = ""
counter = 0
puts "[#{name}]"
puts counter >>SOURCE FORMAT IS FREE
IDENTIFICATION DIVISION.
PROGRAM-ID. FIGURATIVE.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-NAME PIC X(20) VALUE "Alice Smith".
01 WS-COUNTER PIC 9(5) VALUE 42.
PROCEDURE DIVISION.
DISPLAY "[" WS-NAME "]"
DISPLAY WS-COUNTER
MOVE SPACES TO WS-NAME
MOVE ZEROS TO WS-COUNTER
DISPLAY "[" WS-NAME "]"
DISPLAY WS-COUNTER
STOP RUN. COBOL has "figurative constants" β built-in literals that represent commonly needed values. SPACES (or SPACE) fills an alphanumeric field with blanks; ZEROS (or ZERO, ZEROES) sets a numeric field to zero; HIGH-VALUES fills with the highest byte value (xFF); LOW-VALUES fills with x00. These are not functions but built-in keywords that COBOL applies to any compatible field type automatically.
INITIALIZE (Reset a Record)
person = { name: "Alice", count: 99, balance: 500.00 }
person = { name: "", count: 0, balance: 0.0 }
puts person.inspect >>SOURCE FORMAT IS FREE
IDENTIFICATION DIVISION.
PROGRAM-ID. INITIALIZE.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-PERSON.
05 WS-NAME PIC X(20) VALUE "Alice".
05 WS-COUNTER PIC 9(5) VALUE 99.
05 WS-BALANCE PIC S9(7)V99 VALUE 500.00.
PROCEDURE DIVISION.
DISPLAY WS-NAME " " WS-COUNTER " " WS-BALANCE
INITIALIZE WS-PERSON
DISPLAY WS-NAME " " WS-COUNTER " " WS-BALANCE
STOP RUN. INITIALIZE resets a data item (or an entire group item) to its default empty state: alphanumeric fields get SPACES, numeric fields get ZEROS. Applied to a group item, it initializes all elementary items within the group at once. INITIALIZE is more convenient than writing individual MOVE SPACES and MOVE ZEROS statements for every field in a large record β a common pattern after processing one record before reading the next.
Naming Conventions
# Ruby uses snake_case
account_number = "12345678"
customer_balance = 9999.99
puts "#{account_number}: #{customer_balance}" >>SOURCE FORMAT IS FREE
IDENTIFICATION DIVISION.
PROGRAM-ID. NAMING.
DATA DIVISION.
WORKING-STORAGE SECTION.
*> COBOL uses HYPHEN-CASE; WS- prefix = WORKING-STORAGE
01 WS-ACCOUNT-NUMBER PIC X(10) VALUE "12345678".
01 WS-CUSTOMER-BALANCE PIC S9(9)V99 VALUE 9999.99.
PROCEDURE DIVISION.
DISPLAY WS-ACCOUNT-NUMBER ": " WS-CUSTOMER-BALANCE
STOP RUN. COBOL uses hyphens in data names (
WS-ACCOUNT-NUMBER), not underscores. The WS- prefix for WORKING-STORAGE items is a widespread convention that makes the data origin visible in the code. COBOL is completely case-insensitive β WS-NAME, ws-name, and Ws-Name all refer to the same field. In contrast, Ruby is case-sensitive: name and Name are different identifiers (where Name would be a constant).Arithmetic
ADD
total = 100 + 50
puts total
amount = 100
amount += 50
puts amount >>SOURCE FORMAT IS FREE
IDENTIFICATION DIVISION.
PROGRAM-ID. ARITHMETIC-ADD.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-TOTAL PIC 9(7) VALUE 0.
01 WS-AMOUNT PIC 9(7) VALUE 100.
PROCEDURE DIVISION.
ADD 100 50 GIVING WS-TOTAL
DISPLAY WS-TOTAL
ADD 50 TO WS-AMOUNT
DISPLAY WS-AMOUNT
STOP RUN. COBOL's ADD verb has two forms.
ADD a b GIVING result computes a + b and stores it in result (like result = a + b). ADD value TO field adds directly to the field in place (like Ruby's +=). Multiple values can be summed in one statement: ADD a b c TO total. There is no + operator for addition in COBOL β every arithmetic operation uses a verb.SUBTRACT
remaining = 1000 - 350
puts remaining
balance = 1000
balance -= 350
puts balance >>SOURCE FORMAT IS FREE
IDENTIFICATION DIVISION.
PROGRAM-ID. ARITHMETIC-SUBTRACT.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-REMAINING PIC 9(7) VALUE 0.
01 WS-BALANCE PIC 9(7) VALUE 1000.
PROCEDURE DIVISION.
SUBTRACT 350 FROM 1000 GIVING WS-REMAINING
DISPLAY WS-REMAINING
SUBTRACT 350 FROM WS-BALANCE
DISPLAY WS-BALANCE
STOP RUN. SUBTRACT mirrors ADD in structure.
SUBTRACT value FROM total GIVING result computes total - value. SUBTRACT value FROM field modifies the field in place (equivalent to -=). Note the unusual syntax: SUBTRACT 350 FROM 1000 means 1000 β 350 = 650. The FROM operand is the minuend (the number being subtracted from), not the result β a common stumbling block for newcomers.MULTIPLY
product = 12 * 8
puts product
price = 25
price *= 3
puts price >>SOURCE FORMAT IS FREE
IDENTIFICATION DIVISION.
PROGRAM-ID. ARITHMETIC-MULTIPLY.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-PRODUCT PIC 9(7) VALUE 0.
01 WS-PRICE PIC 9(7) VALUE 25.
PROCEDURE DIVISION.
MULTIPLY 12 BY 8 GIVING WS-PRODUCT
DISPLAY WS-PRODUCT
MULTIPLY 3 BY WS-PRICE
DISPLAY WS-PRICE
STOP RUN. MULTIPLY a BY b GIVING result computes a Γ b. MULTIPLY value BY field multiplies the field in place. The multiplier comes first, then BY, then the multiplicand β reading like "multiply 12 by 8". MULTIPLY does not support exponentiation; use COMPUTE for that. The result field must be large enough to hold the product or high-order digits are silently truncated.DIVIDE with REMAINDER
quotient = 17 / 5
remainder = 17 % 5
puts quotient
puts remainder >>SOURCE FORMAT IS FREE
IDENTIFICATION DIVISION.
PROGRAM-ID. ARITHMETIC-DIVIDE.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-QUOTIENT PIC 9(5) VALUE 0.
01 WS-REMAINDER PIC 9(5) VALUE 0.
PROCEDURE DIVISION.
DIVIDE 17 BY 5 GIVING WS-QUOTIENT REMAINDER WS-REMAINDER
DISPLAY WS-QUOTIENT
DISPLAY WS-REMAINDER
STOP RUN. COBOL's DIVIDE verb computes both quotient and remainder in a single statement. There is no
% modulo operator β REMAINDER is a clause of the DIVIDE verb itself. The form DIVIDE a BY b GIVING quotient computes a Γ· b. The alternative DIVIDE b INTO a GIVING quotient is equivalent but has reversed operand order, which is a common source of confusion.COMPUTE (Expressions)
result = (15 + 3) * 2 - 1
puts result
square_root = Math.sqrt(144)
puts square_root >>SOURCE FORMAT IS FREE
IDENTIFICATION DIVISION.
PROGRAM-ID. COMPUTE.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-RESULT PIC 9(7) VALUE 0.
01 WS-SQUARE-ROOT PIC 9(5)V9 VALUE 0.
PROCEDURE DIVISION.
COMPUTE WS-RESULT = (15 + 3) * 2 - 1
DISPLAY WS-RESULT
COMPUTE WS-SQUARE-ROOT = FUNCTION SQRT(144)
DISPLAY WS-SQUARE-ROOT
STOP RUN. COMPUTE allows arithmetic expressions using standard mathematical notation β the only COBOL verb that uses
+, -, *, /, and ** (exponentiation). Intrinsic functions like FUNCTION SQRT can be used inside COMPUTE expressions. It replaces verbose chains of ADD/SUBTRACT/MULTIPLY/DIVIDE for complex calculations and reads much like Ruby's assignment with an expression.ON SIZE ERROR (Overflow)
# Ruby raises no error; integers grow as needed
result = 9999 * 9999
puts result >>SOURCE FORMAT IS FREE
IDENTIFICATION DIVISION.
PROGRAM-ID. SIZE-ERROR.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-RESULT PIC 9(5) VALUE 0.
PROCEDURE DIVISION.
MULTIPLY 9999 BY 9999 GIVING WS-RESULT
ON SIZE ERROR
DISPLAY "Overflow: result too large for field"
NOT ON SIZE ERROR
DISPLAY WS-RESULT
END-MULTIPLY
STOP RUN. Arithmetic verbs can include
ON SIZE ERROR to detect numeric overflow β when the result is too large for the destination field. Without this handler, COBOL silently truncates the high-order digits. ON SIZE ERROR makes overflow explicit: 9999 Γ 9999 = 99,980,001 cannot fit in a PIC 9(5) field, so the SIZE ERROR branch runs. NOT ON SIZE ERROR handles the success case. Ruby integers are arbitrary precision and never overflow.String Operations
String Assignment & Length
message = "Hello, World!"
puts message
puts message.length >>SOURCE FORMAT IS FREE
IDENTIFICATION DIVISION.
PROGRAM-ID. STRING-LENGTH.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-MESSAGE PIC X(30) VALUE SPACES.
01 WS-FIELD-SIZE PIC 9(3) VALUE 0.
PROCEDURE DIVISION.
MOVE "Hello, World!" TO WS-MESSAGE
DISPLAY WS-MESSAGE
MOVE FUNCTION LENGTH(WS-MESSAGE) TO WS-FIELD-SIZE
DISPLAY WS-FIELD-SIZE
STOP RUN. COBOL strings are fixed-width character arrays defined by their PIC X(n) clause.
FUNCTION LENGTH returns the declared field length β always 30 for a PIC X(30) β not the length of the current content. This is unlike Ruby's String#length which returns the actual string length. A PIC X(30) field is always 30 characters wide; shorter strings are padded with trailing spaces.STRING Verb (Concatenation)
first_name = "Alice"
last_name = "Smith"
full_name = first_name + " " + last_name
puts full_name >>SOURCE FORMAT IS FREE
IDENTIFICATION DIVISION.
PROGRAM-ID. STRING-CONCAT.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-FIRST-NAME PIC X(10) VALUE "Alice".
01 WS-LAST-NAME PIC X(10) VALUE "Smith".
01 WS-FULL-NAME PIC X(25) VALUE SPACES.
01 WS-POINTER PIC 9(3) VALUE 1.
PROCEDURE DIVISION.
STRING WS-FIRST-NAME DELIMITED BY SPACE
" " DELIMITED BY SIZE
WS-LAST-NAME DELIMITED BY SPACE
INTO WS-FULL-NAME
WITH POINTER WS-POINTER
DISPLAY WS-FULL-NAME
STOP RUN. COBOL's STRING verb concatenates multiple sources into a destination field.
DELIMITED BY SPACE stops copying at the first space (trimming trailing spaces from fixed-width fields). DELIMITED BY SIZE copies the full field length as-is. The WITH POINTER clause tracks the current write position. COBOL has no + operator or #{} interpolation for strings β STRING is the only concatenation verb.UNSTRING Verb (Split)
full_name = "Alice Smith"
parts = full_name.split(" ")
first_name = parts[0]
last_name = parts[1]
puts first_name
puts last_name >>SOURCE FORMAT IS FREE
IDENTIFICATION DIVISION.
PROGRAM-ID. UNSTRING-SPLIT.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-FULL-NAME PIC X(20) VALUE "Alice Smith".
01 WS-FIRST-NAME PIC X(10) VALUE SPACES.
01 WS-LAST-NAME PIC X(10) VALUE SPACES.
PROCEDURE DIVISION.
UNSTRING WS-FULL-NAME
DELIMITED BY SPACE
INTO WS-FIRST-NAME WS-LAST-NAME
DISPLAY WS-FIRST-NAME
DISPLAY WS-LAST-NAME
STOP RUN. UNSTRING is the inverse of STRING β it splits a source string on a delimiter and stores successive tokens into destination fields.
DELIMITED BY SPACE splits on any single space. Multiple destinations receive successive tokens. UNSTRING also supports multiple delimiters, a TALLYING count of fields filled, and a POINTER clause for partial processing. It is COBOL's equivalent of Ruby's String#split.INSPECT TALLYING (Count)
text = "Mississippi"
count = text.count("s")
puts count >>SOURCE FORMAT IS FREE
IDENTIFICATION DIVISION.
PROGRAM-ID. INSPECT-TALLY.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-TEXT PIC X(15) VALUE "Mississippi".
01 WS-S-COUNT PIC 9(3) VALUE 0.
PROCEDURE DIVISION.
INSPECT WS-TEXT
TALLYING WS-S-COUNT FOR ALL "s"
DISPLAY WS-S-COUNT
STOP RUN. INSPECT TALLYING counts occurrences of a character or substring within a field.
FOR ALL "s" counts every occurrence of lowercase "s" throughout the field. FOR LEADING "s" counts only leading occurrences. COBOL is case-sensitive inside string literals even though COBOL keywords themselves are case-insensitive β "s" and "S" are different characters here, just as in Ruby.INSPECT REPLACING (Replace)
text = "Hello World"
text = text.gsub("o", "0")
puts text >>SOURCE FORMAT IS FREE
IDENTIFICATION DIVISION.
PROGRAM-ID. INSPECT-REPLACE.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-TEXT PIC X(15) VALUE "Hello World".
PROCEDURE DIVISION.
DISPLAY WS-TEXT
INSPECT WS-TEXT REPLACING ALL "o" BY "0"
DISPLAY WS-TEXT
STOP RUN. INSPECT REPLACING modifies a field in place, replacing one character or substring with another.
REPLACING ALL "o" BY "0" replaces every "o" with "0". REPLACING LEADING "H" BY "h" replaces only leading characters. REPLACING FIRST "l" BY "L" replaces only the first occurrence. Unlike Ruby's String#gsub, INSPECT REPLACING always operates in-place and replacements must be the same length as the original.UPPER-CASE / LOWER-CASE
text = "Hello World"
upper = text.upcase
lower = text.downcase
puts upper
puts lower >>SOURCE FORMAT IS FREE
IDENTIFICATION DIVISION.
PROGRAM-ID. CASE-FUNCTIONS.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-TEXT PIC X(15) VALUE "Hello World".
01 WS-UPPER PIC X(15) VALUE SPACES.
01 WS-LOWER PIC X(15) VALUE SPACES.
PROCEDURE DIVISION.
MOVE FUNCTION UPPER-CASE(WS-TEXT) TO WS-UPPER
MOVE FUNCTION LOWER-CASE(WS-TEXT) TO WS-LOWER
DISPLAY WS-UPPER
DISPLAY WS-LOWER
STOP RUN. COBOL intrinsic functions like UPPER-CASE and LOWER-CASE are used with the
FUNCTION keyword and return a value that must be stored with MOVE. They are not methods on a string object but standalone functions. Intrinsic functions were added in COBOL 2002 β older programs use INSPECT REPLACING for case conversion. Other useful functions include FUNCTION TRIM, FUNCTION REVERSE, and FUNCTION NUMVAL (string-to-number conversion).Control Flow
IF / ELSE / END-IF
score = 85
if score >= 90
puts "A"
elsif score >= 80
puts "B"
else
puts "C"
end >>SOURCE FORMAT IS FREE
IDENTIFICATION DIVISION.
PROGRAM-ID. IF-ELSE.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-SCORE PIC 9(3) VALUE 85.
PROCEDURE DIVISION.
IF WS-SCORE >= 90
DISPLAY "A"
ELSE IF WS-SCORE >= 80
DISPLAY "B"
ELSE
DISPLAY "C"
END-IF
STOP RUN. COBOL's IF statement uses
END-IF to close the block (in modern COBOL). ELSE IF chains multiple conditions. The comparison operators are =, >, <, >=, <=, and NOT =. Alternatively, COBOL supports English-language comparisons: IF WS-SCORE IS GREATER THAN 90 is valid and equivalent to IF WS-SCORE > 90.EVALUATE (Switch/Case)
day_number = 3
case day_number
when 1 then puts "Monday"
when 2 then puts "Tuesday"
when 3 then puts "Wednesday"
when 4 then puts "Thursday"
when 5 then puts "Friday"
else puts "Weekend"
end >>SOURCE FORMAT IS FREE
IDENTIFICATION DIVISION.
PROGRAM-ID. EVALUATE.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-DAY-NUMBER PIC 9(1) VALUE 3.
PROCEDURE DIVISION.
EVALUATE WS-DAY-NUMBER
WHEN 1 DISPLAY "Monday"
WHEN 2 DISPLAY "Tuesday"
WHEN 3 DISPLAY "Wednesday"
WHEN 4 DISPLAY "Thursday"
WHEN 5 DISPLAY "Friday"
WHEN OTHER DISPLAY "Weekend"
END-EVALUATE
STOP RUN. EVALUATE is COBOL's switch/case statement.
WHEN OTHER matches everything not caught by earlier WHEN clauses β equivalent to Ruby's else in a case expression. Unlike C's switch, COBOL's EVALUATE does not fall through β each WHEN block is independent. EVALUATE also supports ranges (WHEN 1 THRU 5) and multiple values (WHEN 1, 2, 3).EVALUATE TRUE (Complex Conditions)
temperature = 22
case true
when temperature < 0 then puts "Freezing"
when temperature < 15 then puts "Cold"
when temperature < 25 then puts "Comfortable"
else puts "Hot"
end >>SOURCE FORMAT IS FREE
IDENTIFICATION DIVISION.
PROGRAM-ID. EVALUATE-TRUE.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-TEMPERATURE PIC S9(3) VALUE 22.
PROCEDURE DIVISION.
EVALUATE TRUE
WHEN WS-TEMPERATURE < 0 DISPLAY "Freezing"
WHEN WS-TEMPERATURE < 15 DISPLAY "Cold"
WHEN WS-TEMPERATURE < 25 DISPLAY "Comfortable"
WHEN OTHER DISPLAY "Hot"
END-EVALUATE
STOP RUN. EVALUATE TRUE evaluates each WHEN condition as a boolean expression, executing the first one that is TRUE. This is the recommended replacement for deeply nested IF/ELSE IF chains in COBOL. It is equivalent to Ruby's case true when condition... pattern. EVALUATE TRUE is more readable than nested IF statements and aligns with COBOL's design goal of business-readable code.88-Level Conditions in IF
status = "ACTIVE"
if status == "ACTIVE" || status == "PENDING"
puts "Account needs processing"
end >>SOURCE FORMAT IS FREE
IDENTIFICATION DIVISION.
PROGRAM-ID. LEVEL-88-IF.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-STATUS PIC X(8) VALUE "ACTIVE".
88 ACCOUNT-ACTIVE VALUE "ACTIVE".
88 ACCOUNT-PENDING VALUE "PENDING".
88 ACCOUNT-NEEDS-PROCESSING VALUE "ACTIVE" "PENDING".
PROCEDURE DIVISION.
IF ACCOUNT-NEEDS-PROCESSING
DISPLAY "Account needs processing"
END-IF
STOP RUN. A level-88 condition name can list multiple VALUES, making it TRUE when the parent field contains any of them. This replaces verbose
IF field = "A" OR field = "B" chains with a single readable name. Business logic in COBOL is meant to read like plain English: IF ACCOUNT-NEEDS-PROCESSING is self-documenting in a way that a multi-value comparison is not.SET for 88-Level Conditions
status = "ACTIVE"
puts status
status = "INACTIVE"
puts status >>SOURCE FORMAT IS FREE
IDENTIFICATION DIVISION.
PROGRAM-ID. SET-88.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-STATUS PIC X(8).
88 ACCOUNT-ACTIVE VALUE "ACTIVE".
88 ACCOUNT-INACTIVE VALUE "INACTIVE".
PROCEDURE DIVISION.
SET ACCOUNT-ACTIVE TO TRUE
DISPLAY WS-STATUS
SET ACCOUNT-INACTIVE TO TRUE
DISPLAY WS-STATUS
STOP RUN. SET condition-name TO TRUE assigns the first VALUE of the condition name's clause to the parent field. It is the canonical way to change a status field using its named condition: SET ACCOUNT-ACTIVE TO TRUE sets WS-STATUS to "ACTIVE". This is more maintainable than MOVE "ACTIVE" TO WS-STATUS because the string literal is defined in exactly one place β the 88-level clause β rather than scattered throughout the code.CONTINUE (No-Op)
value = 42
if value > 0
# nothing to do
elsif value == 0
puts "Zero"
else
puts "Negative"
end >>SOURCE FORMAT IS FREE
IDENTIFICATION DIVISION.
PROGRAM-ID. CONTINUE.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-VALUE PIC S9(5) VALUE 42.
PROCEDURE DIVISION.
IF WS-VALUE > 0
CONTINUE
ELSE IF WS-VALUE = 0
DISPLAY "Zero"
ELSE
DISPLAY "Negative"
END-IF
STOP RUN. CONTINUE is COBOL's no-op statement β it does nothing but is syntactically valid wherever a statement is required. It serves the same purpose as Python's
pass or Ruby's bare nil when an IF branch must exist but has no action. CONTINUE is also used as a placeholder when restructuring code β a COBOL idiom for marking "this case is handled by doing nothing".Loops
PERFORM n TIMES
3.times { puts "Hello" } >>SOURCE FORMAT IS FREE
IDENTIFICATION DIVISION.
PROGRAM-ID. PERFORM-TIMES.
PROCEDURE DIVISION.
PERFORM 3 TIMES
DISPLAY "Hello"
END-PERFORM
STOP RUN. PERFORM n TIMES repeats a block n times β COBOL's equivalent of Ruby's Integer#times. The block is delimited by END-PERFORM. COBOL has no blocks, closures, or anonymous functions β all repetition is expressed through PERFORM. The count can be a literal or a numeric data field.PERFORM UNTIL (While Loop)
counter = 1
while counter <= 5
puts counter
counter += 1
end >>SOURCE FORMAT IS FREE
IDENTIFICATION DIVISION.
PROGRAM-ID. PERFORM-UNTIL.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-COUNTER PIC 9(3) VALUE 1.
PROCEDURE DIVISION.
PERFORM UNTIL WS-COUNTER > 5
DISPLAY WS-COUNTER
ADD 1 TO WS-COUNTER
END-PERFORM
STOP RUN. PERFORM UNTIL continues while the condition is FALSE β it loops UNTIL the condition becomes TRUE. This is the inverse of Ruby's
while: COBOL has no while keyword. PERFORM UNTIL WS-COUNTER > 5 is equivalent to Ruby's while counter <= 5. The body must manually increment the counter β COBOL does not do this automatically.PERFORM VARYING (For Loop)
(1..5).each { |index| puts index } >>SOURCE FORMAT IS FREE
IDENTIFICATION DIVISION.
PROGRAM-ID. PERFORM-VARYING.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-INDEX PIC 9(3) VALUE 0.
PROCEDURE DIVISION.
PERFORM VARYING WS-INDEX FROM 1 BY 1 UNTIL WS-INDEX > 5
DISPLAY WS-INDEX
END-PERFORM
STOP RUN. PERFORM VARYING is COBOL's for-loop.
FROM start BY step UNTIL condition sets up the counter. The counter advances by step before each condition check. PERFORM VARYING can be nested using the AFTER clause: PERFORM VARYING row FROM 1 BY 1 UNTIL row > 3 AFTER column FROM 1 BY 1 UNTIL column > 4 creates a nested loop in a single statement β useful for iterating two-dimensional tables.PERFORM WITH TEST AFTER (Do-While)
counter = 0
loop do
counter += 1
puts counter
break if counter >= 3
end >>SOURCE FORMAT IS FREE
IDENTIFICATION DIVISION.
PROGRAM-ID. TEST-AFTER.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-COUNTER PIC 9(3) VALUE 0.
PROCEDURE DIVISION.
PERFORM WITH TEST AFTER UNTIL WS-COUNTER >= 3
ADD 1 TO WS-COUNTER
DISPLAY WS-COUNTER
END-PERFORM
STOP RUN. PERFORM WITH TEST AFTER UNTIL condition is COBOL's do-while loop β the body executes at least once, and the condition is checked at the end of each iteration. The default PERFORM UNTIL uses TEST BEFORE (checks condition first). This pattern is common in COBOL file-reading loops where you need to read at least one record before deciding whether to continue.EXIT PERFORM (Break)
(1..10).each do |index|
break if index == 5
puts index
end >>SOURCE FORMAT IS FREE
IDENTIFICATION DIVISION.
PROGRAM-ID. EXIT-PERFORM.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-INDEX PIC 9(3) VALUE 0.
PROCEDURE DIVISION.
PERFORM VARYING WS-INDEX FROM 1 BY 1 UNTIL WS-INDEX > 10
IF WS-INDEX = 5
EXIT PERFORM
END-IF
DISPLAY WS-INDEX
END-PERFORM
STOP RUN. EXIT PERFORM immediately terminates the current PERFORM loop β COBOL's equivalent of Ruby's
break. It can only appear inside a PERFORM block, not inside a called paragraph. EXIT PERFORM CYCLE skips to the next iteration (like Ruby's next). Both were introduced in COBOL 2002 and are not available in older COBOL dialects.Paragraphs & Sections
Defining and Calling a Paragraph
def print_greeting
puts "Good morning!"
end
print_greeting >>SOURCE FORMAT IS FREE
IDENTIFICATION DIVISION.
PROGRAM-ID. PARAGRAPH.
PROCEDURE DIVISION.
PERFORM PRINT-GREETING
STOP RUN.
PRINT-GREETING.
DISPLAY "Good morning!". A COBOL paragraph is defined by its name followed by a period on the same or next line. Paragraphs are COBOL's equivalent of Ruby methods β named blocks of code called with PERFORM. Unlike Ruby methods, paragraphs take no parameters and return no values; all data exchange happens through WORKING-STORAGE fields. Paragraphs must appear after STOP RUN to prevent the program from falling through into them after the main logic ends.
Paragraphs Share WORKING-STORAGE
def double(number)
number * 2
end
result = double(21)
puts result >>SOURCE FORMAT IS FREE
IDENTIFICATION DIVISION.
PROGRAM-ID. PARA-DATA.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-INPUT PIC 9(5) VALUE 0.
01 WS-OUTPUT PIC 9(5) VALUE 0.
PROCEDURE DIVISION.
MOVE 21 TO WS-INPUT
PERFORM DOUBLE-VALUE
DISPLAY WS-OUTPUT
STOP RUN.
DOUBLE-VALUE.
MULTIPLY 2 BY WS-INPUT GIVING WS-OUTPUT. COBOL paragraphs have no parameters or return values. The calling code sets WORKING-STORAGE fields before PERFORM, and the paragraph reads and writes those shared fields. This is COBOL's equivalent of passing arguments and returning a result β but entirely through shared global state. Compared to Ruby's clean method signatures, COBOL's paragraph communication requires careful documentation of what each paragraph reads and writes.
PERFORM THRU (Range of Paragraphs)
def step_one = puts "Step 1"
def step_two = puts "Step 2"
def step_three = puts "Step 3"
step_one
step_two
step_three >>SOURCE FORMAT IS FREE
IDENTIFICATION DIVISION.
PROGRAM-ID. PERFORM-THRU.
PROCEDURE DIVISION.
PERFORM STEP-ONE THRU STEP-THREE
STOP RUN.
STEP-ONE.
DISPLAY "Step 1".
STEP-TWO.
DISPLAY "Step 2".
STEP-THREE.
DISPLAY "Step 3". PERFORM paragraph THRU other-paragraph executes every paragraph from the first through the last in physical source order. It is a common COBOL pattern for grouping related steps: define INITIALIZE-SECTION, PROCESS-SECTION, and CLEANUP-SECTION as consecutive paragraphs and call all three with one PERFORM THRU. Ruby has no equivalent β you call each method individually or extract them into a single method.
Sections in PROCEDURE DIVISION
def setup = puts "Setting up..."
def process = puts "Processing..."
def teardown = puts "Cleaning up..."
setup
process
teardown >>SOURCE FORMAT IS FREE
IDENTIFICATION DIVISION.
PROGRAM-ID. SECTIONS.
PROCEDURE DIVISION.
PERFORM SETUP-SECTION
PERFORM PROCESS-SECTION
PERFORM TEARDOWN-SECTION
STOP RUN.
SETUP-SECTION SECTION.
SETUP-INIT.
DISPLAY "Setting up...".
PROCESS-SECTION SECTION.
PROCESS-MAIN.
DISPLAY "Processing...".
TEARDOWN-SECTION SECTION.
TEARDOWN-CLEANUP.
DISPLAY "Cleaning up...". The PROCEDURE DIVISION can be organized into named SECTIONS, each containing multiple paragraphs. A SECTION groups related paragraphs together β PERFORMing a SECTION executes all its paragraphs in sequence. Sections provide a higher level of organization than individual paragraphs, similar to how Ruby classes group related methods. Modern COBOL programs typically use either sections or flat paragraphs, not both.
PERFORM Paragraph n TIMES
def shout = puts "HELLO"
3.times { shout } >>SOURCE FORMAT IS FREE
IDENTIFICATION DIVISION.
PROGRAM-ID. PARA-TIMES.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-COUNTER PIC 9(3) VALUE 0.
PROCEDURE DIVISION.
PERFORM SHOUT 3 TIMES
STOP RUN.
SHOUT.
ADD 1 TO WS-COUNTER
DISPLAY "HELLO (call " WS-COUNTER ")". PERFORM paragraph-name n TIMES calls a paragraph n times β equivalent to Ruby's n.times { method_call }. If the paragraph needs different input each call, set the relevant WORKING-STORAGE fields before calling, or use PERFORM VARYING with a counter that the paragraph reads. COBOL paragraphs retain and modify WORKING-STORAGE between calls β the WS-COUNTER field above accumulates across all three calls.Tables (Arrays)
OCCURS (Table Declaration)
scores = [85, 92, 78]
puts scores[0]
puts scores[1]
puts scores[2] >>SOURCE FORMAT IS FREE
IDENTIFICATION DIVISION.
PROGRAM-ID. OCCURS.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-SCORES-TABLE.
05 WS-SCORE PIC 9(3) OCCURS 5 TIMES.
PROCEDURE DIVISION.
MOVE 85 TO WS-SCORE(1)
MOVE 92 TO WS-SCORE(2)
MOVE 78 TO WS-SCORE(3)
DISPLAY WS-SCORE(1)
DISPLAY WS-SCORE(2)
DISPLAY WS-SCORE(3)
STOP RUN. The OCCURS clause defines a table (COBOL's term for an array).
OCCURS 5 TIMES creates 5 elements, accessed with 1-based subscripts: WS-SCORE(1) through WS-SCORE(5). COBOL tables are always 1-indexed β unlike Ruby's 0-indexed arrays. The table is defined as a subordinate item within a group item, which can be referenced as a whole unit representing the entire table.PERFORM VARYING over a Table
scores = [85, 92, 78, 95, 88]
scores.each { |score| puts score } >>SOURCE FORMAT IS FREE
IDENTIFICATION DIVISION.
PROGRAM-ID. TABLE-ITERATE.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-SCORES-TABLE.
05 WS-SCORE PIC 9(3) OCCURS 5 TIMES.
01 WS-INDEX PIC 9(1) VALUE 1.
PROCEDURE DIVISION.
MOVE 85 TO WS-SCORE(1)
MOVE 92 TO WS-SCORE(2)
MOVE 78 TO WS-SCORE(3)
MOVE 95 TO WS-SCORE(4)
MOVE 88 TO WS-SCORE(5)
PERFORM VARYING WS-INDEX FROM 1 BY 1 UNTIL WS-INDEX > 5
DISPLAY WS-SCORE(WS-INDEX)
END-PERFORM
STOP RUN. Iterating over a COBOL table always uses PERFORM VARYING with a numeric subscript. There is no COBOL equivalent of Ruby's
Array#each or Array#map β the index must be manually managed. The subscript is used directly in parentheses: WS-SCORE(WS-INDEX). Table subscripts must be integer data items or integer literals; expressions inside subscripts are not supported in standard COBOL.Sum a Table
numbers = [10, 20, 30, 40, 50]
total = numbers.sum
puts total >>SOURCE FORMAT IS FREE
IDENTIFICATION DIVISION.
PROGRAM-ID. TABLE-SUM.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-NUMBERS-TABLE.
05 WS-NUMBER PIC 9(3) OCCURS 5 TIMES.
01 WS-TOTAL PIC 9(6) VALUE 0.
01 WS-INDEX PIC 9(1) VALUE 1.
PROCEDURE DIVISION.
MOVE 10 TO WS-NUMBER(1)
MOVE 20 TO WS-NUMBER(2)
MOVE 30 TO WS-NUMBER(3)
MOVE 40 TO WS-NUMBER(4)
MOVE 50 TO WS-NUMBER(5)
PERFORM VARYING WS-INDEX FROM 1 BY 1 UNTIL WS-INDEX > 5
ADD WS-NUMBER(WS-INDEX) TO WS-TOTAL
END-PERFORM
DISPLAY WS-TOTAL
STOP RUN. Summing a COBOL table requires a PERFORM VARYING loop that explicitly adds each element to an accumulator. There is no built-in equivalent of Ruby's
Array#sum or Enumerable#reduce. The accumulator pattern β initialize a sum variable to 0, then ADD each element β is pervasive in COBOL business logic. This verbosity is by design: every operation is stated explicitly, leaving nothing implicit.Two-Dimensional Table
matrix = Array.new(3) { Array.new(3, 0) }
matrix[0][0] = 1
matrix[1][1] = 5
matrix[2][2] = 9
puts matrix[1][1] >>SOURCE FORMAT IS FREE
IDENTIFICATION DIVISION.
PROGRAM-ID. TABLE-2D.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-MATRIX.
05 WS-ROW OCCURS 3 TIMES.
10 WS-CELL PIC 9(3) OCCURS 3 TIMES.
PROCEDURE DIVISION.
MOVE 1 TO WS-CELL(1, 1)
MOVE 5 TO WS-CELL(2, 2)
MOVE 9 TO WS-CELL(3, 3)
DISPLAY WS-CELL(1, 1)
DISPLAY WS-CELL(2, 2)
DISPLAY WS-CELL(3, 3)
STOP RUN. A two-dimensional COBOL table is created by nesting OCCURS clauses. The outer OCCURS defines rows; the inner OCCURS defines columns within each row. Access uses two subscripts:
WS-CELL(row, column). COBOL supports up to seven dimensions of subscripting. This nesting pattern maps directly to Ruby's array-of-arrays, but COBOL requires all dimensions to be declared upfront with their maximum sizes.OCCURS DEPENDING ON (Variable Length)
item_count = 3
items = Array.new(item_count, 0)
items[0] = 100
items[1] = 200
items[2] = 300
items.each { |item| puts item } >>SOURCE FORMAT IS FREE
IDENTIFICATION DIVISION.
PROGRAM-ID. OCCURS-DEPENDING.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-ITEM-COUNT PIC 9(3) VALUE 3.
01 WS-ITEMS-TABLE.
05 WS-ITEM PIC 9(5) OCCURS 1 TO 10 TIMES
DEPENDING ON WS-ITEM-COUNT.
01 WS-INDEX PIC 9(3) VALUE 0.
PROCEDURE DIVISION.
MOVE 100 TO WS-ITEM(1)
MOVE 200 TO WS-ITEM(2)
MOVE 300 TO WS-ITEM(3)
PERFORM VARYING WS-INDEX FROM 1 BY 1 UNTIL WS-INDEX > WS-ITEM-COUNT
DISPLAY WS-ITEM(WS-INDEX)
END-PERFORM
STOP RUN. OCCURS n TO m TIMES DEPENDING ON field declares a variable-length table where the active element count is stored in a separate numeric field. The table is allocated for the maximum size (m) but only WS-ITEM-COUNT elements are considered valid at runtime. This is COBOL's approach to dynamic arrays β the maximum size must be known at compile time, but the active size can vary during execution.Output & Formatting
DISPLAY (Print Output)
puts "Hello, World!"
print "No newline "
puts "but this has one" >>SOURCE FORMAT IS FREE
IDENTIFICATION DIVISION.
PROGRAM-ID. DISPLAY-BASIC.
PROCEDURE DIVISION.
DISPLAY "Hello, World!"
DISPLAY "No newline " WITH NO ADVANCING
DISPLAY "but this has one"
STOP RUN. DISPLAY is COBOL's primary output verb β equivalent to Ruby's
puts. It writes to standard output followed by a newline. WITH NO ADVANCING suppresses the newline, like Ruby's print. DISPLAY can output literals, data fields, or combinations: DISPLAY "Name: " WS-NAME. Unlike puts, a single DISPLAY statement can output multiple items consecutively with no separator between them.DISPLAY Multiple Items
first_name = "Alice"
age = 30
puts "Name: #{first_name}, Age: #{age}" >>SOURCE FORMAT IS FREE
IDENTIFICATION DIVISION.
PROGRAM-ID. DISPLAY-MULTIPLE.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-FIRST-NAME PIC X(10) VALUE "Alice".
01 WS-AGE PIC 9(3) VALUE 30.
PROCEDURE DIVISION.
DISPLAY "Name: " WS-FIRST-NAME ", Age: " WS-AGE
STOP RUN. DISPLAY outputs all its operands in sequence with no separator between them. COBOL has no string interpolation syntax like Ruby's
#{}. To combine literal text with variable values, list them all as DISPLAY operands. Understanding field widths matters here: WS-FIRST-NAME is PIC X(10) so "Alice" will display as "Alice " (with 5 trailing spaces) before the ", Age:" label.Numeric Editing (Formatted Numbers)
amount = 12345.67
integer_part, decimal_part = ("%.2f" % amount).split(".")
integer_with_commas = integer_part.reverse.scan(/d{1,3}/).join(",").reverse
puts "$#{integer_with_commas}.#{decimal_part}" >>SOURCE FORMAT IS FREE
IDENTIFICATION DIVISION.
PROGRAM-ID. NUMERIC-EDITING.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-AMOUNT PIC 9(5)V99 VALUE 12345.67.
01 WS-EDITED-AMOUNT PIC ZZ,ZZ9.99.
01 WS-DOLLAR-AMOUNT PIC $ZZ,ZZ9.99.
PROCEDURE DIVISION.
MOVE WS-AMOUNT TO WS-EDITED-AMOUNT
DISPLAY WS-EDITED-AMOUNT
MOVE WS-AMOUNT TO WS-DOLLAR-AMOUNT
DISPLAY WS-DOLLAR-AMOUNT
STOP RUN. COBOL "edited" PICTURE clauses format numbers for display.
Z suppresses leading zeros (replaces them with spaces), , inserts a comma, . inserts a decimal point, and $ inserts a dollar sign. You always MOVE from a numeric field (PIC 9) into an edited field just before display β edited fields cannot be used in arithmetic. This declarative formatting is one of COBOL's most powerful features for generating financial reports without any format string syntax.Intrinsic Functions in Output
numbers = [3, 7, 2, 9, 1]
puts numbers.max
puts numbers.min >>SOURCE FORMAT IS FREE
IDENTIFICATION DIVISION.
PROGRAM-ID. INTRINSIC-FUNCS.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-MAX-VALUE PIC 9(5) VALUE 0.
01 WS-MIN-VALUE PIC 9(5) VALUE 0.
PROCEDURE DIVISION.
COMPUTE WS-MAX-VALUE = FUNCTION MAX(3, 7, 2, 9, 1)
COMPUTE WS-MIN-VALUE = FUNCTION MIN(3, 7, 2, 9, 1)
DISPLAY WS-MAX-VALUE
DISPLAY WS-MIN-VALUE
STOP RUN. COBOL intrinsic functions are called with the
FUNCTION keyword and their result must be captured with MOVE or COMPUTE. Useful numeric functions include MAX, MIN, ABS, MOD, INTEGER, and SQRT. Date functions include FUNCTION CURRENT-DATE (returns a 21-character field with date, time, and UTC offset). Intrinsic functions were added in COBOL 2002; older COBOL code often implemented these calculations manually.File I/O
SELECT, ASSIGN, and FD
# Ruby: one call opens and describes the file
File.open("customers.txt", "w") do |file|
file.puts "Alice,30"
end >>SOURCE FORMAT IS FREE
IDENTIFICATION DIVISION.
PROGRAM-ID. FILE-SELECT.
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT CUSTOMER-FILE
ASSIGN TO "customers.txt"
ORGANIZATION IS LINE SEQUENTIAL.
DATA DIVISION.
FILE SECTION.
FD CUSTOMER-FILE.
01 WS-CUSTOMER-RECORD PIC X(80).
WORKING-STORAGE SECTION.
01 WS-END-OF-FILE PIC X(1) VALUE "N".
88 END-OF-FILE VALUE "Y".
PROCEDURE DIVISION.
*> File I/O requires a real filesystem
*> This example shows the declaration structure only
STOP RUN. COBOL file handling is declared across three DIVISIONS. The ENVIRONMENT DIVISION's FILE-CONTROL assigns a logical file name (CUSTOMER-FILE) to a physical file path. The DATA DIVISION's FILE SECTION declares the record layout with an FD (File Description) entry. Only then does the PROCEDURE DIVISION use OPEN/READ/WRITE/CLOSE. This multi-division declaration is COBOL's way of separating the "what" (record layout) from the "how" (access pattern) and the "where" (file path). File I/O requires a real filesystem and cannot run in the browser.
OPEN, READ, WRITE, CLOSE
File.open("names.txt", "w") { |file| file.puts "Alice" }
File.open("names.txt", "r") do |file|
file.each_line { |line| puts line.chomp }
end >>SOURCE FORMAT IS FREE
IDENTIFICATION DIVISION.
PROGRAM-ID. FILE-READ.
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT INPUT-FILE
ASSIGN TO "names.txt"
ORGANIZATION IS LINE SEQUENTIAL.
DATA DIVISION.
FILE SECTION.
FD INPUT-FILE.
01 WS-RECORD PIC X(80).
WORKING-STORAGE SECTION.
01 WS-END-OF-FILE PIC X(1) VALUE "N".
88 END-OF-FILE VALUE "Y".
PROCEDURE DIVISION.
OPEN INPUT INPUT-FILE
READ INPUT-FILE
AT END SET END-OF-FILE TO TRUE
END-READ
PERFORM UNTIL END-OF-FILE
DISPLAY WS-RECORD
READ INPUT-FILE
AT END SET END-OF-FILE TO TRUE
END-READ
END-PERFORM
CLOSE INPUT-FILE
STOP RUN. File access in COBOL always follows the same verb sequence: OPEN β READ/WRITE β CLOSE.
AT END in the READ statement sets a condition when there are no more records β the standard end-of-file detection pattern. The READ-UNTIL-END-OF-FILE loop reads one extra record at the end (the "priming read" pattern) to trigger AT END. COBOL has no equivalent of Ruby's each_line iterator; the loop must be written explicitly. File I/O requires a real filesystem.WRITE (Writing Records)
File.open("output.txt", "w") do |file|
file.puts "Alice,30"
file.puts "Bob,25"
end >>SOURCE FORMAT IS FREE
IDENTIFICATION DIVISION.
PROGRAM-ID. FILE-WRITE.
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT OUTPUT-FILE
ASSIGN TO "output.txt"
ORGANIZATION IS LINE SEQUENTIAL.
DATA DIVISION.
FILE SECTION.
FD OUTPUT-FILE.
01 WS-OUTPUT-RECORD PIC X(80).
PROCEDURE DIVISION.
OPEN OUTPUT OUTPUT-FILE
MOVE "Alice,30" TO WS-OUTPUT-RECORD
WRITE WS-OUTPUT-RECORD
MOVE "Bob,25" TO WS-OUTPUT-RECORD
WRITE WS-OUTPUT-RECORD
CLOSE OUTPUT-FILE
STOP RUN. COBOL writes files by moving data into the record buffer (the 01-level item under FD) and then calling WRITE on that record. You never write "to the file" directly β you write the record buffer.
OPEN OUTPUT creates the file (or truncates it). WRITE AFTER ADVANCING 2 LINES is the COBOL way to add blank lines in a report. File I/O requires a real filesystem and cannot run in the browser.β Gotchas for Rubyists
β COBOL Is Case-Insensitive
# Ruby is case-sensitive: name β Name β NAME
name = "Alice"
puts name >>SOURCE FORMAT IS FREE
IDENTIFICATION DIVISION.
PROGRAM-ID. CASE-INSENSITIVE.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-NAME PIC X(10) VALUE "Alice".
PROCEDURE DIVISION.
*> All three are the SAME field:
DISPLAY WS-NAME
display ws-name
Display Ws-Name
STOP RUN. COBOL keywords and data names are completely case-insensitive β
DISPLAY, display, and Display are identical. This includes field names: WS-NAME and ws-name refer to the same field. In contrast, Ruby is case-sensitive: name, Name, and NAME are three different identifiers (where Name would be a constant). Traditional COBOL was written entirely in uppercase; modern COBOL allows mixed case, but production code remains uppercase by convention.β Trailing Spaces in PIC X Fields
name = "Alice"
puts name.length # 5 β the actual length
puts "[#{name}]" # [Alice] β no padding >>SOURCE FORMAT IS FREE
IDENTIFICATION DIVISION.
PROGRAM-ID. TRAILING-SPACES.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-NAME PIC X(10) VALUE "Alice".
PROCEDURE DIVISION.
*> PIC X(10) always occupies 10 chars
DISPLAY "[" WS-NAME "]"
*> FUNCTION LENGTH returns the declared field size (always 10)
DISPLAY FUNCTION LENGTH(WS-NAME)
*> FUNCTION TRIM removes trailing spaces
DISPLAY "[" FUNCTION TRIM(WS-NAME TRAILING) "]"
STOP RUN. A PIC X(n) field is always exactly n characters wide. Storing "Alice" in a PIC X(10) field pads it with 5 trailing spaces: "Alice ". This causes subtle bugs:
WS-NAME = "Alice" is FALSE because COBOL compares the full 10-character field "Alice " against "Alice" (padded to match). Use FUNCTION TRIM(field TRAILING) to remove trailing spaces before comparison or display. Ruby strings are dynamic and never have this problem.β MOVE Silently Truncates
box = "Al"
puts box # "Al" β stored exactly
box = "Alexander the Great"
puts box # "Alexander the Great" β Ruby grows to fit >>SOURCE FORMAT IS FREE
IDENTIFICATION DIVISION.
PROGRAM-ID. MOVE-TRUNCATION.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-BOX PIC X(10).
PROCEDURE DIVISION.
*> Short: padded with trailing spaces
MOVE "Alice" TO WS-BOX
DISPLAY "[" WS-BOX "]"
*> Long: silently truncated on the right β no error!
MOVE "Alexander the Great" TO WS-BOX
DISPLAY "[" WS-BOX "]"
STOP RUN. COBOL MOVE silently truncates when the source is longer than the destination: "Alexander the Great" MOVEd into a PIC X(10) field produces "Alexander ". There is no overflow error, warning, or exception. This silent truncation is a major source of data integrity bugs in COBOL programs, especially when field sizes change during maintenance. Ruby strings grow dynamically to hold any content β there is no equivalent silent truncation.
β The Period as Scope Terminator
# Ruby uses end keywords β no ambiguity
if true
puts "True"
puts "Still inside if"
end
puts "Outside if β always runs" >>SOURCE FORMAT IS FREE
IDENTIFICATION DIVISION.
PROGRAM-ID. PERIOD-GOTCHA.
PROCEDURE DIVISION.
*> Modern COBOL: END-IF is unambiguous
IF 1 = 1
DISPLAY "True"
DISPLAY "Still inside IF"
END-IF
DISPLAY "Outside IF β always runs"
*> DANGER in fixed format (legacy):
*> IF 1 = 1
*> DISPLAY "True"
*> DISPLAY "Still inside IF". <- PERIOD ends the entire IF!
*> DISPLAY "Outside IF β always runs (even if condition was false)"
STOP RUN. In traditional fixed-format COBOL, a period (.) ends the scope of any open IF, PERFORM, or EVALUATE β regardless of indentation. A misplaced period after an IF body makes subsequent statements run unconditionally, even when the condition was false. This was a notorious source of COBOL bugs for decades. Modern COBOL uses explicit scope terminators (END-IF, END-PERFORM, END-EVALUATE) to eliminate the ambiguity. This page uses free-format COBOL throughout, which strongly encourages explicit scope terminators.
β No Functions β Only Paragraphs
def calculate_tax(income)
income * 0.25
end
tax = calculate_tax(50000)
puts tax >>SOURCE FORMAT IS FREE
IDENTIFICATION DIVISION.
PROGRAM-ID. NO-FUNCTIONS.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-INCOME PIC 9(9)V99 VALUE 50000.00.
01 WS-TAX PIC 9(9)V99 VALUE 0.
PROCEDURE DIVISION.
MOVE 50000.00 TO WS-INCOME
PERFORM CALCULATE-TAX
DISPLAY WS-TAX
STOP RUN.
CALCULATE-TAX.
COMPUTE WS-TAX = WS-INCOME * 0.25. COBOL has no functions, methods, or closures. Logic is organized into paragraphs (and sections), called with PERFORM. Parameters and return values are communicated through WORKING-STORAGE fields β there is no way to pass arguments directly to a paragraph. COBOL 2002 introduced user-defined functions (
FUNCTION MY-FUNC(args)) compiled separately, but they are rarely used in practice. This is COBOL's most significant structural limitation compared to modern languages like Ruby.