Ruby.CodeCompared.To/COBOL

An interactive executable cheatsheet for Rubyists learning COBOL

Ruby 4.0 GnuCOBOL 2.2
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.