2026/04/27

COBOL: esqlOC

嵌入式 SQL(Embedded SQL)是一種將 SQL 語句直接寫入 C 語言、COBOL、FORTRAN 及 Ada 等程式語言原始碼中的方法。 藉此方法,可使得應用程式能夠存取以及處理資料。 esqlOC 是由 Sergey Kashyrin 開發 的 GnuCOBOL Embedded SQL (ESQL) 預編譯器與執行期程式庫,它依賴 ODBC 來連接多種 RDBMS。 Open Cobol ESQL (ocesql) 則是 另外一套開放原始碼嵌入式 SQL 預編譯器與執行期程式庫。 因為嵌入式 SQL 是 SQL 標準的一部份,因此理論上可以替換不同的預編譯器來轉譯原始碼。

在使用時首先要使用 esqlOC 程式將包含嵌入式 SQL 的程式碼轉譯為 COBOL 原始碼, 再使用 GnuCOBOL 編譯,並且需要連結 esqlOC 的 ocsql 函式庫。

Embedded SQL statements work like normal SQL statements with some minor changes. For example, the output of a query is directed to a predefined set of variables which are referred as Host Variables. An additional INTO clause is placed in the SELECT statement.

version.sqb

       IDENTIFICATION DIVISION.
       PROGRAM-ID. 01_select.

       DATA DIVISION.
       WORKING-STORAGE SECTION.
       EXEC SQL BEGIN DECLARE SECTION END-EXEC.
       01  BUFFER       PIC X(1024).
       01  HVERSION     PIC X(160).
       EXEC SQL END DECLARE SECTION END-EXEC.

       PROCEDURE DIVISION.
           STRING 'DRIVER=PSQL;'
                  'SERVER=localhost;'
                  'PORT=5432;'
                  'DATABASE=danilo;'
                  'USER=danilo;'
                  'PASSWORD=danilo;'
           INTO BUFFER.

           EXEC SQL 
               CONNECT TO :BUFFER
           END-EXEC.

           IF SQLCODE NOT = ZERO
               DISPLAY "Connection Failed"
               STOP RUN
           END-IF.

           EXEC SQL 
               SELECT version() INTO :HVERSION
           END-EXEC.

           IF SQLCODE = 0
               DISPLAY "Version: " HVERSION
           END-IF.

           EXEC SQL DISCONNECT ALL END-EXEC.
           STOP RUN.

如果要使用 ODBC DSN,可以改寫如下:

       IDENTIFICATION DIVISION.
       PROGRAM-ID. 01_select.

       DATA DIVISION.
       WORKING-STORAGE SECTION.
       EXEC SQL BEGIN DECLARE SECTION END-EXEC.
       01  BUFFER       PIC X(1024).
       01  HVERSION     PIC X(160).
       EXEC SQL END DECLARE SECTION END-EXEC.


       PROCEDURE DIVISION.
      * with DSN: 'youruser/yourpasswd@yourODBC_DSN'
           STRING 'danilo/danilo@PostgreSQL'
           INTO BUFFER.

           EXEC SQL 
               CONNECT TO :BUFFER
           END-EXEC.

           IF SQLCODE NOT = ZERO
               DISPLAY "Connection Failed"
               STOP RUN
           END-IF.

           EXEC SQL 
               SELECT version() INTO :HVERSION
           END-EXEC.

           IF SQLCODE = 0
               DISPLAY "Version: " HVERSION
           END-IF.

           EXEC SQL DISCONNECT ALL END-EXEC.
           STOP RUN.

使用 esqlOC 程式將包含嵌入式 SQL 的程式碼轉譯為 COBOL 原始碼:

esqlOC version.sqb

而後再使用 GnuCOBOL 編譯。

cobc -x version.cob -locsql

參考資料

COBOL 學習筆記

COBOL(Common Business-Oriented Language)是 1959 年問世的編譯程式語言, 最早是以 Grace Hopper 開發的 FLOW-MATIC 語言為範本, 是最早實施標準化的計算機語言之一,專為商業資料處理設計,語法接近英文並且結構冗長,代碼類似英語句子這使得即使非工程師也比較容易閱讀其邏輯, 廣泛應用於大型主機(Mainframe)金融交易、企業薪資計算、庫存管理等需要高處理量與穩定的領域, 目前最新的語言標準版本為 ISO/IEC 1989:2023。

商業軟體方面,比較有名的編譯器實作為 IBM Enterprise COBOL, Micro Focus Visual COBOL, Fujitsu NetCOBOL 以及 Veryant isCOBOL 等軟體。而開放原始碼的 COBOL 編譯器主要有二套, 一個是將 COBOL 原始碼轉譯為 C 語言以後編譯的 GnuCOBOL, 一個是自 GCC 15.1 開始加入的 COBOL 編譯器 GCC COBOL

在 openSUSE 安裝 GnuCOBOL:

sudo zypper in gnucobol

在 openSUSE 安裝 GCC COBOL:

sudo zypper in gcc-cobol

COBOL 在語法上不區分大小寫(Case-insensitive),因此保留字、變數名稱、段落名稱等,使用大寫、小寫或混合大小寫書寫, 對編譯器而言都是相同的。在 COBOL-85 標準之前,COBOL 程式通常要求全部使用大寫字母編寫。現代 COBOL 雖然允許使用小寫, 但為了維持舊代碼的兼容性和傳統習慣,許多開發者仍習慣使用全大寫。 程式碼分為四大部:IDENTIFICATION DIVISION(識別部)、ENVIRONMENT DIVISION(設備部)、 DATA DIVISION(資料部)和PROCEDURE DIVISION(程序部)。

All COBOL programs are organized in a structure that consists of divisions, sections, paragraphs, sentences, statements, clauses, and phrases.

This structure is hierarchical--that is, as a general rule,

  • a COBOL program is made up of divisions;
  • a division is made up of sections;
  • a section is made up of paragraphs;
  • a paragraph is made up of either sentences or clauses (depending upon the division); a sentence can contain one or more statements;
  • a statement or clause can contain one or more phrases.

COBOL can be written in two formats: fixed (the default) or free. In fixed-format, code must be aligned to fit in certain areas (a holdover from using punched cards). Until COBOL 2002, these were:

Name Column(s) Usage
Sequence number area 1–6 Originally used for card/line numbers (facilitating mechanical punched card sorting to assure intended program code sequence after manual editing/handling), this area is ignored by the compiler
Indicator area 7 The following characters are allowed here:
  • * – Comment line
  • / – Comment line that will be printed on a new page of a source listing
  • - – Continuation line, where words or literals from the previous line are continued
  • D – Line enabled in debugging mode, which is otherwise ignored
Area A 8–11 This contains: DIVISION, SECTION and procedure headers; 01 and 77 level numbers and file/report descriptors
Area B 12–72 Any other code not allowed in Area A
Program name area 73– Historically up to column 80 for punched cards, it is used to identify the program or sequence the card belongs to

In COBOL 2002, Areas A and B were merged to form the program-text area, which now ends at an implementor-defined column.

COBOL 2002 also introduced free-format code. Free-format code can be placed in any column of the file, as in newer programming languages. Comments are specified using *>, which can be placed anywhere and can also be used in fixed-format source code. Continuation lines are not present, and the >>PAGE directive replaces the / indicator.

下面是一個 Hello World 的例子 (fixed format):

       IDENTIFICATION DIVISION.
       PROGRAM-ID. HELLO-WORLD.
       
       PROCEDURE DIVISION.
           DISPLAY 'Hello, World!'.
           STOP RUN.

下面是使用 GOBACK 的寫法:

       IDENTIFICATION DIVISION.
       PROGRAM-ID. hello.

       PROCEDURE DIVISION.
           DISPLAY "Hello, world!"
           GOBACK.

       END PROGRAM hello.

使用 GnuCOBOL 編譯:

cobc -x hello.cob

使用 GCC COBOL 編譯:

gcobol -main hello.cob -o hello

下面是一個 Hello World 的例子 (free format):

*> COBOL Hello World program
identification division.
program-id. hello.
procedure division.
   display "Hello World! ".
   goback.

使用 GnuCOBOL 編譯:

cobc -x -free hello.cob

使用 GCC COBOL 編譯:

gcobol -ffree-form -main hello.cob -o hello

Data Division

Data Division is used to define the variables used in a program. To describe data in COBOL, one must understand the following terms −

  • Level Number
  • Data Name
  • Picture Clause
  • Value Clause

Standard COBOL provides the following data types:

Data type Sample declaration Notes
Alphabetic PIC A(30) May contain only letters or spaces.
Alphanumeric PIC X(30) May contain any characters.
Boolean PIC 1 USAGE BIT Data stored in the form of 0s and 1s, as a binary number.
Index USAGE INDEX Used to reference table elements.
National PIC N(30) Similar to alphanumeric, but using an extended character set, e.g. UTF-8.
Numeric PIC 9(5)V9(2) Contains exactly 7 digits (7=5+2). 'V' locates the implicit decimal in a fixed point number.
Object USAGE OBJECT REFERENCE May reference either an object or NULL.
Pointer USAGE POINTER

Data items in COBOL are declared hierarchically through the use of level-numbers which indicate if a data item is part of another. An item with a higher level-number is subordinate to an item with a lower one. Top-level data items, with a level-number of 1, are called records. Items that have subordinate aggregate data are called group items; those that do not are called elementary items. Level-numbers used to describe standard data items are between 1 and 49.

       01  some-record.                   *> Aggregate group record item
           05  num            PIC 9(10).  *> Elementary item
           05  the-date.                  *> Aggregate (sub)group record item
               10  the-year   PIC 9(4).   *> Elementary item
               10  the-month  PIC 99.     *> Elementary item
               10  the-day    PIC 99.     *> Elementary item

A level-number of 66 is used to declare a re-grouping of previously defined items, irrespective of how those items are structured. This data level, also referred to by the associated RENAMES clause.

       01  customer-record.
           05  cust-key            PIC X(10).
           05  cust-name.
               10  cust-first-name PIC X(30).
               10  cust-last-name  PIC X(30).
           05  cust-dob            PIC 9(8).
           05  cust-balance        PIC 9(7)V99.
           
       66  cust-personal-details   RENAMES cust-name THRU cust-dob.
       66  cust-all-details        RENAMES cust-name THRU cust-balance.

A 77 level-number indicates the item is stand-alone, and in such situations is equivalent to the level-number 01.

An 88 level-number declares a condition name (a so-called 88-level) which is true when its parent data item contains one of the values specified in its VALUE clause.

       01  wage-type          PIC X.
           88  wage-is-hourly VALUE "H".
           88  wage-is-yearly VALUE "S", "Y".

A PICTURE (or PIC) clause is a string of characters, each of which represents a portion of the data item and what it may contain. Some picture characters specify the type of the item and how many characters or digits it occupies in memory. For example, a 9 indicates a decimal digit, and an S indicates that the item is signed. Other picture characters (called insertion and editing characters) specify how an item should be formatted.

The USAGE clause declares the format in which data is stored. Depending on the data type, it can either complement or be used instead of a PICTURE clause. While it can be used to declare pointers and object references, it is mostly geared towards specifying numeric types.

  • BINARY, where a minimum size is either specified by the PICTURE clause or by a USAGE clause such as BINARY-LONG.
  • USAGE COMPUTATIONAL, where data may be stored in whatever format the implementation provides; often equivalent to USAGE BINARY. A computational item is a value used in arithmetic operations. It must be numeric. If a group item is described with a computational usage, the elementary items within the group have that usage.
  • USAGE DISPLAY, the default format, where data is stored as a string
  • COMPUTATIONAL-1 or COMP-1 specified for internal floating-point items (single precision). COMP-1 items are 4 bytes long.
  • COMPUTATIONAL-2 or COMP-2 (long floating-point) specified for internal floating-point items (double precision). COMP-2 items are 8 bytes long.
  • COMPUTATIONAL-3 or COMP-3 (internal decimal) is the equivalent of PACKED-DECIMAL. PACKED-DECIMAL specified for internal decimal items.
  • USAGE NATIONAL, where data is stored as a string using an extended character set
  • USAGE PACKED-DECIMAL, where data is stored in the smallest possible decimal format (typically packed binary-coded decimal)

Value clause is an optional clause which is used to initialize the data items. The values can be numeric literal, alphanumeric literal, or figurative constant. It can be used with both group and elementary items.

下面是一個宣告變數的例子:

       IDENTIFICATION DIVISION.
       PROGRAM-ID. EXAMPLE.

       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01 WS-NAME PIC A(6) VALUE 'ABCDEF'.
       
       PROCEDURE DIVISION.
           DISPLAY "WS-NAME : "WS-NAME.
           STOP RUN.

In COBOL, the WORKING-STORAGE SECTION and LOCAL-STORAGE SECTION are both part of the Data Division used to define internal variables, but they differ fundamentally in how they manage memory and handle program calls. Use WORKING-STORAGE SECTION for standard variables, counters, or flags that need to retain their values if a subprogram is called multiple times without being cancelled. Use LOCAL-STORAGE SECTION when writing recursive programs (where a program calls itself) to ensure each "level" of recursion has its own set of variables. It is also preferred in multi-threaded applications to avoid data corruption between threads.

COBOL Copybook 是一種包含 COBOL 程式碼的外部檔案,主要定義資料結構(如 01 階層式記錄), 可被多個程式透過 COPY 語句引入。它能實現結構定義的統一管理,確保資料一致性,通常使用 .cpy 副檔名儲存。

STUDENT.cpy

       01 STUDENT-RECORD.
          05 STUDENT-ID        PIC 9(05).
          05 STUDENT-NAME.
             10 FIRST-NAME     PIC X(20).
             10 LAST-NAME      PIC X(20).
          05 DATE-OF-BIRTH     PIC 9(08).
          05 ENROLLMENT-STATUS PIC X(01).
             88 ACTIVE-STUDENT VALUE 'A'.
             88 INACTIVE-STUDENT VALUE 'I'.

STU-INFO.cob

       IDENTIFICATION DIVISION.
       PROGRAM-ID. STU-INFO.

       DATA DIVISION.
       WORKING-STORAGE SECTION.
      * The COPY statement pulls in the code from STUDENT.cpy
       COPY STUDENT.

       PROCEDURE DIVISION.
           MOVE 12345 TO STUDENT-ID.
           MOVE "JOHN" TO FIRST-NAME.
           SET ACTIVE-STUDENT TO TRUE.
           
           DISPLAY "STUDENT ID: " STUDENT-ID.
           DISPLAY "STATUS: " ENROLLMENT-STATUS.
           STOP RUN.

Initialize 用來初始化變數。MOVE 可以用來設值。
而 ADD, SUBTRACT, MULTIPLY, DIVIDE 用來作為數值加滅乘除之用, Compute 則是可以用來編寫算術表達式,可以使用 Compute 取代 ADD, SUBTRACT, MULTIPLY, DIVIDE。

       IDENTIFICATION DIVISION.
       PROGRAM-ID. EXAMPLE.
       
       DATA DIVISION.
          WORKING-STORAGE SECTION.
          01 WS-VAR1 PIC 9(3).
          01 WS-VAR2 PIC 9(3).
       
       PROCEDURE DIVISION.
          MAIN-PARA.
          INITIALIZE WS-VAR1 REPLACING NUMERIC DATA BY 010.
          MOVE 010 TO WS-VAR2.

          DIVIDE 10 INTO WS-VAR1.
          MULTIPLY WS-VAR1 BY 2 GIVING WS-VAR1.
          ADD 4 TO WS-VAR1.
          SUBTRACT 3 FROM WS-VAR1.
          COMPUTE WS-VAR2 = (((WS-VAR2 / 10) * 2) + 4) - 3
          DISPLAY "WS-VAR1 is     : " WS-VAR1.
          DISPLAY "WS-VAR2 is     : " WS-VAR2.
       
          STOP RUN.

ACCEPT 可以用來自標準輸入 (STDIN) 取得輸入資料。

       IDENTIFICATION DIVISION.
       PROGRAM-ID. EXAMPLE.

       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01 WS-STUDENT-NAME PIC X(25).
       
       PROCEDURE DIVISION.
           ACCEPT WS-STUDENT-NAME.
           DISPLAY "Name :  " WS-STUDENT-NAME.
           STOP RUN.

Conditional Statements

COBOL 使用 IF 進行條件判斷。要注意的是,COBOL 只有 IF ... ELSE 結構,並沒有直接支援 ELSE IF, 但是可以在 ELSE 下使用 IF 條件句進行更多的條件判斷。

       IDENTIFICATION DIVISION.
       PROGRAM-ID. HELLO.
        
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01 WS-NUM1 PIC 9(9).
       01 WS-NUM2 PIC 9(9).
       01 WS-NUM3 PIC 9(5).
       01 WS-NUM4 PIC 9(6).
        
       PROCEDURE DIVISION.
           MOVE 25 TO WS-NUM1 WS-NUM3.
           MOVE 15 TO WS-NUM2 WS-NUM4.
           
           IF WS-NUM1 > WS-NUM2 THEN
              DISPLAY 'IN LOOP 1 - IF BLOCK'
              
              IF WS-NUM3 = WS-NUM4 THEN
                 DISPLAY 'IN LOOP 2 - IF BLOCK'
              ELSE
                 DISPLAY 'IN LOOP 2 - ELSE BLOCK'
              END-IF
              
           ELSE
             DISPLAY 'IN LOOP 1 - ELSE BLOCK'
           END-IF.
           
           STOP RUN.

如果需要多於一個以上的比較,可以使用 EVALUATE

       IDENTIFICATION DIVISION.
       PROGRAM-ID. HELLO.
        
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01 WS-A PIC 9 VALUE 0.
           
       PROCEDURE DIVISION.
           MOVE 3 TO WS-A.
           
           EVALUATE TRUE
              WHEN WS-A > 2
                 DISPLAY 'WS-A GREATER THAN 2'
        
              WHEN WS-A < 0
                 DISPLAY 'WS-A LESS THAN 0'
        
              WHEN OTHER
                 DISPLAY 'INVALID VALUE OF WS-A'
           END-EVALUATE.
           
           STOP RUN.

Loop Statements

GO TO 陳述式是 COBOL 無條件轉移控制權的機制。 GO TO 會直接跳轉到程式中指定的段落(Paragraph)或節(Section)繼續執行。 在軟體開發引入結構化程式設計以後,COBOL 使用 PERFORM 進行迴圈控制流程, 一般而言建議盡量使用 PERFORM 進行結構化的程式設計。

PERFORM 用來呼叫其它的 paragraph。

       IDENTIFICATION DIVISION.
       PROGRAM-ID. COUNT.
        
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01 WS-I PIC 9 VALUE 1.
          
       PROCEDURE DIVISION.
       MAIN-PARA.
           DISPLAY 'Starting Program'
           PERFORM 1000-PROCESS-DATA
               DISPLAY 'Finished Program'
           STOP RUN.

       1000-PROCESS-DATA.
           DISPLAY 'Inside the paragraph'.

Perform Thru 用來執行一系列的 paragraph。

       IDENTIFICATION DIVISION.
       PROGRAM-ID. EXAMPLE.
       
       PROCEDURE DIVISION.
          A-PARA.
          PERFORM DISPLAY 'IN A-PARA'
          END-PERFORM.
          PERFORM C-PARA THRU E-PARA.
          
          B-PARA.
          DISPLAY 'IN B-PARA'.
          STOP RUN.
          
          C-PARA.
          DISPLAY 'IN C-PARA'.
          
          D-PARA.
          DISPLAY 'IN D-PARA'.
          
          E-PARA.
          DISPLAY 'IN E-PARA'.

用來執行重覆多少次的迴圈。

       IDENTIFICATION DIVISION.
       PROGRAM-ID. COUNT.
        
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01 WS-I PIC 9 VALUE 1.
           
       PROCEDURE DIVISION.
           PERFORM 3 TIMES
               DISPLAY "It is " WS-I
               ADD 1 TO WS-I
           END-PERFORM.
 
           STOP RUN.

PERFORM UNTIL 用來執行類似其它語言 WHILE 語句的迴圈。

       IDENTIFICATION DIVISION.
       PROGRAM-ID. COUNT.
        
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01 WS-COUNTER PIC 9 VALUE 1.
          
       PROCEDURE DIVISION.
           PERFORM UNTIL WS-COUNTER > 5
               DISPLAY "Counter is: " WS-COUNTER
               ADD 1 TO WS-COUNTER
           END-PERFORM.
           STOP RUN.

PERFORM VARYING 用來執行類似其它語言 FOR 語句的迴圈。下面實作了九九乘法表:

       IDENTIFICATION DIVISION.
       PROGRAM-ID. MULT-TABLE.

       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01 I PIC 99 VALUE 1.
       01 J PIC 99 VALUE 1.
       01 RESULT PIC 99.

       PROCEDURE DIVISION.
       MAIN-PROCEDURE.
           PERFORM VARYING I FROM 1 BY 1 UNTIL I > 9
               PERFORM VARYING J FROM 1 BY 1 UNTIL J > 9
                   COMPUTE RESULT = I * J
                   DISPLAY I " x " J " = " RESULT
               END-PERFORM
           END-PERFORM.
           STOP RUN.

(注意:經過實測,如果 I 或者是 J 宣告為 PIC 9,因為需要其值為 10 才能夠達成停止條件, 這樣迴圈並無法停止,也就是形成了無窮迴圈)

在 COBOL 2014 標準提出了 EXIT PERFORM CYCLE 中斷迴圈此次循環以及 EXIT PERFORM 中斷迴圈的語句, 但是不是所有的 COBOL 編譯器都有支援。


Write a program that displays the digits from 1 to n then back down to 1; for instance, if n = 5, the program should display 123454321. You are permitted to use only a single for loop. The range is 0 < n < 10.

       IDENTIFICATION DIVISION.
       PROGRAM-ID. NUMBER-COUNT.

       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01 WS-NUMBER          PIC 9.

       PROCEDURE DIVISION.
           ACCEPT WS-NUMBER FROM COMMAND-LINE.

           IF WS-NUMBER < 1 OR WS-NUMBER > 9 THEN
               DISPLAY 'INVALID VALUE'
               STOP RUN
           END-IF.

           EVALUATE TRUE
              WHEN WS-NUMBER = 1
                 DISPLAY '1'

              WHEN WS-NUMBER = 2
                 DISPLAY '121'

              WHEN WS-NUMBER = 3
                 DISPLAY '12321'

              WHEN WS-NUMBER = 4
                 DISPLAY '1234321'

              WHEN WS-NUMBER = 5
                 DISPLAY '123454321'

              WHEN WS-NUMBER = 6
                 DISPLAY '12345654321'

              WHEN WS-NUMBER = 7
                 DISPLAY '1234567654321'

              WHEN WS-NUMBER = 8
                 DISPLAY '123456787654321'

              WHEN WS-NUMBER = 9
                 DISPLAY '12345678987654321'

              WHEN OTHER
                 DISPLAY 'INVALID VALUE'
           END-EVALUATE.

           STOP RUN.

要注意的是,命令列通常以空白作為區隔參數的符號, 而這裡使用的 COMMAND-LINE 會拿到一個將全部的命令列參數視為整體的字串(參數之間以空白區隔), 這也是大多數的 COBOL 編譯器都支援的方式。至於命令列參數的總數以及存取個別參數的方式, 因為 COBOL 標準沒有定義,所以不同的 COBOL 編譯器有其自己的實作。

改寫為使用迴圈的寫法:

       IDENTIFICATION DIVISION.
       PROGRAM-ID. NUMBER-COUNT.

       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01 WS-NUMBER      PIC 9.
       01 WS-POSITIVE    PIC 9 VALUE 1.
       01 WS-COUNT       PIC 9 VALUE 0.

       PROCEDURE DIVISION.
           ACCEPT WS-NUMBER FROM COMMAND-LINE.

           IF WS-NUMBER < 1 OR WS-NUMBER > 9 THEN
               DISPLAY 'INVALID VALUE'
               STOP RUN
           END-IF.

           PERFORM UNTIL 1 < 0
               IF WS-POSITIVE IS EQUAL TO 1 THEN
                   ADD 1 TO WS-COUNT
                   DISPLAY WS-COUNT WITH NO ADVANCING
                   IF WS-COUNT IS EQUAL TO WS-NUMBER THEN
                       MOVE 0 TO WS-POSITIVE
                   END-IF
               ELSE
                   SUBTRACT 1 FROM WS-COUNT
                   IF WS-COUNT IS GREATER THAN 0 THEN
                       DISPLAY WS-COUNT WITH NO ADVANCING
                   ELSE
                       EXIT PERFORM
                   END-IF
               END-IF
           END-PERFORM.
           DISPLAY " "
           STOP RUN.

下面是一個讓使用者輸入一個數字以後,猜測與隨機製造的數字是否相同的遊戲:

       IDENTIFICATION DIVISION.
       PROGRAM-ID. RANDOM-EXAMPLE.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01 WS-RESULT   PIC 9(4).
       01 WS-ANSWER   PIC 9(4).

       PROCEDURE DIVISION.
           COMPUTE WS-RESULT = (FUNCTION RANDOM * 999) + 1
           
           PERFORM UNTIL 1 < 0
               DISPLAY "Please give a number (1-1000): "
                       WITH NO ADVANCING
               ACCEPT WS-ANSWER

               IF WS-ANSWER IS EQUAL TO WS-RESULT THEN
                   EXIT PERFORM
               ELSE
                   IF WS-ANSWER IS GREATER THAN WS-RESULT THEN
                       DISPLAY "Please guess more lower"
                   ELSE
                       DISPLAY "Please guess more higher"
                   END-IF
               END-IF
           END-PERFORM.
           STOP RUN.

String Handling

Inspect verb is used to count or replace the characters in a string. String operations can be performed on alphanumeric, numeric, or alphabetic values. Inspect operations are performed from left to right.

       IDENTIFICATION DIVISION.
       PROGRAM-ID. HELLO.
       
       DATA DIVISION.
          WORKING-STORAGE SECTION.
          01 WS-CNT1 PIC 9(2) VALUE 0.
          01 WS-CNT2 PIC 9(2) VALUE 0.
          01 WS-STRING PIC X(18) VALUE 'ABCDACDADEAAAFFABC'.
          
       PROCEDURE DIVISION.
          INSPECT WS-STRING TALLYING WS-CNT1 FOR ALL 'A'.
          DISPLAY "WS-CNT1 : "WS-CNT1
          INSPECT WS-STRING TALLYING WS-CNT2 FOR CHARACTERS.
          DISPLAY "WS-CNT2 : "WS-CNT2
          
          STOP RUN.

下面是用來取代某個字元的範例。

Write a program to replace the character ’e’ with ‘E’ in the string ‘Weekly Challenge’. Also print the number of times the character ’e’ is found in the string.

       IDENTIFICATION DIVISION.
       PROGRAM-ID. REPLACE.
       
       DATA DIVISION.
          WORKING-STORAGE SECTION.
          01 WS-COUNT  PIC 9(2) VALUE 0.
          01 WS-STRING PIC X(16) VALUE 'Weekly Challenge'.
       
       PROCEDURE DIVISION.
          INSPECT WS-STRING TALLYING WS-COUNT FOR ALL 'e'.
          DISPLAY "Find e " WS-COUNT " times.".
          INSPECT WS-STRING REPLACING ALL "e" BY "E".
          DISPLAY "NEW STRING: "WS-STRING.
          
          STOP RUN.

下面是 1-9 位數不重複印出來的練習問題。
這裡使用了 PIC Z 的定義,它的主要作用是抑制前導零(Zero Suppression)。 當對應的數值為零時,Z 字元會將該數字位置顯示為空白,讓輸出結果更易讀。

       IDENTIFICATION DIVISION.
       PROGRAM-ID. LIST-NUMBER.

       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01 WS-CNT1    PIC 9.
       01 WS-FLAG    PIC 9.
       01 WS-NUMBER  PIC Z(8)9.

       LOCAL-STORAGE SECTION.
       01 LS-NUM     PIC 9     USAGE BINARY.
       01 LS-MAX     PIC 9(9)  USAGE BINARY.
       01 LS-INDEX   PIC 9(10) USAGE BINARY.

       PROCEDURE DIVISION.
       MAIN-PARA.
           DISPLAY "Please give a number: " WITH NO ADVANCING
           ACCEPT LS-NUM
           IF LS-NUM < 1 OR LS-NUM > 9 THEN
               DISPLAY "INVALID DATA."
               STOP RUN
           END-IF.

           COMPUTE LS-MAX = 10 ** LS-NUM - 1
           PERFORM VARYING LS-INDEX FROM 1 BY 1 UNTIL LS-INDEX > LS-MAX
               MOVE 0 TO WS-FLAG
               MOVE LS-INDEX TO WS-NUMBER
               PERFORM CHECK-PARA

               IF WS-FLAG IS EQUAL TO 0 THEN
                   DISPLAY FUNCTION TRIM(WS-NUMBER)
               END-IF
           END-PERFORM.

           STOP RUN.


       CHECK-PARA.
           MOVE 0 TO WS-CNT1
           INSPECT WS-NUMBER TALLYING WS-CNT1 FOR ALL '1'.
           IF WS-CNT1 IS GREATER THAN 1 THEN
               MOVE 1 TO WS-FLAG
               EXIT PARAGRAPH
           END-IF.

           MOVE 0 TO WS-CNT1
           INSPECT WS-NUMBER TALLYING WS-CNT1 FOR ALL '2'.
           IF WS-CNT1 IS GREATER THAN 1 THEN
               MOVE 1 TO WS-FLAG
               EXIT PARAGRAPH
           END-IF.

           MOVE 0 TO WS-CNT1
           INSPECT WS-NUMBER TALLYING WS-CNT1 FOR ALL '3'.
           IF WS-CNT1 IS GREATER THAN 1 THEN
               MOVE 1 TO WS-FLAG
               EXIT PARAGRAPH
           END-IF.

           MOVE 0 TO WS-CNT1
           INSPECT WS-NUMBER TALLYING WS-CNT1 FOR ALL '4'.
           IF WS-CNT1 IS GREATER THAN 1 THEN
               MOVE 1 TO WS-FLAG
               EXIT PARAGRAPH
           END-IF.

           MOVE 0 TO WS-CNT1
           INSPECT WS-NUMBER TALLYING WS-CNT1 FOR ALL '5'.
           IF WS-CNT1 IS GREATER THAN 1 THEN
               MOVE 1 TO WS-FLAG
               EXIT PARAGRAPH
           END-IF.

           MOVE 0 TO WS-CNT1
           INSPECT WS-NUMBER TALLYING WS-CNT1 FOR ALL '6'.
           IF WS-CNT1 IS GREATER THAN 1 THEN
               MOVE 1 TO WS-FLAG
               EXIT PARAGRAPH
           END-IF.

           MOVE 0 TO WS-CNT1
           INSPECT WS-NUMBER TALLYING WS-CNT1 FOR ALL '7'.
           IF WS-CNT1 IS GREATER THAN 1 THEN
               MOVE 1 TO WS-FLAG
               EXIT PARAGRAPH
           END-IF.

           MOVE 0 TO WS-CNT1
           INSPECT WS-NUMBER TALLYING WS-CNT1 FOR ALL '8'.
           IF WS-CNT1 IS GREATER THAN 1 THEN
               MOVE 1 TO WS-FLAG
               EXIT PARAGRAPH
           END-IF.

           MOVE 0 TO WS-CNT1
           INSPECT WS-NUMBER TALLYING WS-CNT1 FOR ALL '9'.
           IF WS-CNT1 IS GREATER THAN 1 THEN
               MOVE 1 TO WS-FLAG
               EXIT PARAGRAPH
           END-IF.

           MOVE 0 TO WS-CNT1
           INSPECT WS-NUMBER TALLYING WS-CNT1 FOR ALL '0'.
           IF WS-CNT1 IS GREATER THAN 1 THEN
               MOVE 1 TO WS-FLAG
               EXIT PARAGRAPH
           END-IF.

String verb is used to concatenate the strings. Using STRING statement, two or more strings of characters can be combined to form a longer string. Delimited By clause is compulsory.

DELIMITED BY SIZE moves the entire source field regardless of its content. DELIMITED BY SPACE clause indicates that the transfer of characters from a source field stops as soon as the first space is encountered.

       IDENTIFICATION DIVISION.
       PROGRAM-ID. EXAMPLE.
       
       DATA DIVISION.
          WORKING-STORAGE SECTION.
          01 WS-STRING PIC A(30).
          01 WS-STR1 PIC A(15) VALUE 'Tutorialspoint'.
          01 WS-STR2 PIC A(7) VALUE 'Welcome'.
          01 WS-STR3 PIC A(7) VALUE 'To AND'.
          01 WS-COUNT PIC 99 VALUE 1.
       
       PROCEDURE DIVISION.
          STRING WS-STR2 DELIMITED BY SIZE
             WS-STR3 DELIMITED BY SPACE
             WS-STR1 DELIMITED BY SIZE
             INTO WS-STRING 
             WITH POINTER WS-COUNT
             ON OVERFLOW DISPLAY 'OVERFLOW!' 
          END-STRING.
          
          DISPLAY 'WS-STRING : 'WS-STRING.
          DISPLAY 'WS-COUNT : 'WS-COUNT.
       
          STOP RUN.

Unstring verb is used to split one string into multiple sub-strings. Delimited By clause is compulsory.

       IDENTIFICATION DIVISION.
       PROGRAM-ID. EXAMPLE.
       
       DATA DIVISION.
          WORKING-STORAGE SECTION.
          01 WS-STRING PIC A(30) VALUE 'WELCOME TO TUTORIALSPOINT'.
          01 WS-STR1 PIC A(7).
          01 WS-STR2 PIC A(2).
          01 WS-STR3 PIC A(15).
       
       PROCEDURE DIVISION.
          UNSTRING WS-STRING DELIMITED BY SPACE
             INTO WS-STR1, WS-STR2, WS-STR3
          END-UNSTRING.
          
          DISPLAY 'WS-STR1 : 'WS-STR1.
          DISPLAY 'WS-STR2 : 'WS-STR2.
          DISPLAY 'WS-STR3 : 'WS-STR3.
          
          STOP RUN.

Table Processing

Arrays in COBOL are known as tables. Table is declared in Data Division. Occurs clause is used to define a table. Occurs clause indicates the repetition of data name definition. It can be used only with level numbers starting from 02 to 49. Do not use occurs clause with Redefines.

       IDENTIFICATION DIVISION.
       PROGRAM-ID. HELLO.
        
       DATA DIVISION.
           WORKING-STORAGE SECTION.
           01 WS-TABLE.
              05 WS-A PIC A(10) VALUE 'TUTORIALS' OCCURS 5 TIMES.     
        
       PROCEDURE DIVISION.
           DISPLAY "ONE-D TABLE : "WS-TABLE.
           STOP RUN.

下面是另外一個例子。

       IDENTIFICATION DIVISION.
       PROGRAM-ID. HELLO.
       
       DATA DIVISION.
          WORKING-STORAGE SECTION.
          01 WS-TABLE.
             05 WS-A OCCURS 2 TIMES.
                10 WS-B PIC A(10) VALUE ' TUTORIALS'.
                10 WS-C OCCURS 2 TIMES.
                   15 WS-D PIC X(6) VALUE ' POINT'.
       
       PROCEDURE DIVISION.
          DISPLAY "TWO-D TABLE : "WS-TABLE.
       
          STOP RUN.

Table individual elements can be accessed by using subscript. Subscript values can range from 1 to the number of times the table occurs. A subscript can be any positive number. It does not require any declaration in data division. It is automatically created with occurs clause.

       IDENTIFICATION DIVISION.
       PROGRAM-ID. HELLO.
       
       DATA DIVISION.
          WORKING-STORAGE SECTION.
          01 WS-TABLE.
             05 WS-A OCCURS 3 TIMES.
                10 WS-B PIC A(2).
                10 WS-C OCCURS 2 TIMES.
                   15 WS-D PIC X(3).
       
       PROCEDURE DIVISION.
          MOVE '12ABCDEF34GHIJKL56MNOPQR' TO WS-TABLE.
          DISPLAY 'WS-TABLE  : ' WS-TABLE.
          DISPLAY 'WS-A(1)   : ' WS-A(1).
          DISPLAY 'WS-C(1 1) : ' WS-C(1 1).
          DISPLAY 'WS-C(1 2) : ' WS-C(1 2).
          DISPLAY 'WS-A(2)   : ' WS-A(2).
          DISPLAY 'WS-C(2 1) : ' WS-C(2 1).
          DISPLAY 'WS-C(2 2) : ' WS-C(2 2).
          DISPLAY 'WS-A(3)   : ' WS-A(3).
          DISPLAY 'WS-C(3 1) : ' WS-C(3 1).
          DISPLAY 'WS-C(3 2) : ' WS-C(3 2).
          
          STOP RUN.

Table elements can also be accessed using index. An index is a displacement of element from the start of the table. An index is declared with Occurs clause using INDEXED BY clause. The value of index can be changed using SET statement and PERFORM Varying option.

       IDENTIFICATION DIVISION.
       PROGRAM-ID. HELLO.
       
       DATA DIVISION.
          WORKING-STORAGE SECTION.
          01 WS-TABLE.
             05 WS-A OCCURS 3 TIMES INDEXED BY I.
                10 WS-B PIC A(2).
                10 WS-C OCCURS 2 TIMES INDEXED BY J.
                   15 WS-D PIC X(3).
       
       PROCEDURE DIVISION.
          MOVE '12ABCDEF34GHIJKL56MNOPQR' TO WS-TABLE.
          PERFORM A-PARA VARYING I FROM 1 BY 1 UNTIL I > 3 
          STOP RUN.
          
          A-PARA.
          PERFORM C-PARA VARYING J FROM 1 BY 1 UNTIL J > 2.
          
          C-PARA.
          DISPLAY WS-C(I,J).

File Handling

The concept of files in COBOL is different from that in C/C++. COBOL file handling is a core feature designed to process large volumes of structured data efficiently. It operates on logical records rather than raw text files, typically interacting with Physical Sequential (PS) or VSAM files in mainframe environments.

COBOL supports three primary file types defined in the ENVIRONMENT DIVISION:

  • Sequential: Records are stored and accessed in the order they were written.
  • Indexed: Records are accessed using a unique primary key, allowing both sequential and random access.
  • Relative: Records are stored in fixed slots and accessed via a Relative Record Number (RRN).

File handling requires entries across three main divisions:

  • ENVIRONMENT DIVISION: Use the FILE-CONTROL Paragraph and SELECT clause to map a program's internal logical file name to an external physical file (often via JCL).
  • DATA DIVISION: The FILE SECTION contains the File Description (FD) entry, which defines record length and layout.
  • PROCEDURE DIVISION: Contains the logic to manipulate data using file handling verbs.

我們將文字檔視為將一行作為一個 record 儲存並且以換行字元區隔的檔案, 因此可以這樣讀取 Linux 下的 /etc/os-release 並且取得 Linux Distribution Name:

       IDENTIFICATION DIVISION.
       PROGRAM-ID. READ-NAME.

       ENVIRONMENT DIVISION.
       INPUT-OUTPUT SECTION.
       FILE-CONTROL.
           SELECT MY-FILE ASSIGN TO '/etc/os-release'
           ORGANIZATION IS LINE SEQUENTIAL.

       DATA DIVISION.
       FILE SECTION.
       FD MY-FILE.
       01 MY-RECORD      PIC X(80).

       WORKING-STORAGE SECTION.
       01 WS-EOF              PIC X VALUE 'N'.
           88 EOF-REACHED     VALUE 'Y'.
       77 WS-KEY       PIC X(20).
       77 WS-VAL       PIC X(60).

       PROCEDURE DIVISION.
       MAIN-LOGIC.
           OPEN INPUT MY-FILE
           
           PERFORM UNTIL EOF-REACHED
               READ MY-FILE
                   AT END
                       MOVE 'Y' TO WS-EOF
                   NOT AT END
                       PERFORM PARSE-STRING
                       IF WS-KEY = "NAME" THEN
                           DISPLAY WS-VAL
                           EXIT PERFORM
                       END-IF
               END-READ
           END-PERFORM

           CLOSE MY-FILE
           STOP RUN.

       PARSE-STRING.
           UNSTRING MY-RECORD
               DELIMITED BY "="
               INTO WS-KEY, WS-VAL
           END-UNSTRING.

Subroutines

Cobol subroutine is a program that can be compiled independently but cannot be executed independently. There are two types of subroutines: internal subroutines like Perform statements and external subroutines like CALL verb.

If the values of variables in the called program are modified, then their new values will reflect in the calling program. If BY clause is not specified, then variables are always passed by reference.

The LINKAGE SECTION in COBOL is a part of the DATA DIVISION used to describe data that is available to a program but is not stored within that program's own memory. You cannot use the VALUE clause for items in LINKAGE SECTION, except for level-88 condition names.

main program

       IDENTIFICATION DIVISION.
       PROGRAM-ID. MAIN.
       
       DATA DIVISION.
          WORKING-STORAGE SECTION.
          01 WS-STUDENT-ID PIC 9(4) VALUE 1000.
          01 WS-STUDENT-NAME PIC A(15) VALUE 'Tim'.
       
       PROCEDURE DIVISION.
          CALL 'UTIL' USING WS-STUDENT-ID, WS-STUDENT-NAME.
          DISPLAY 'Student Id : ' WS-STUDENT-ID
          DISPLAY 'Student Name : ' WS-STUDENT-NAME
          STOP RUN.

Called Program

       IDENTIFICATION DIVISION.
       PROGRAM-ID. UTIL.
       
       DATA DIVISION.
          LINKAGE SECTION.
          01 LS-STUDENT-ID PIC 9(4).
          01 LS-STUDENT-NAME PIC A(15).
       
       PROCEDURE DIVISION USING LS-STUDENT-ID, LS-STUDENT-NAME.
          DISPLAY 'In Called Program'.
          MOVE 1111 TO LS-STUDENT-ID.
          EXIT PROGRAM.

BY CONTENT on a CALL will copy the content of the identifier to a compiler-managed area of storage. If the values of variables in the called program are modified, then their new values will not reflect in the calling program.

       IDENTIFICATION DIVISION.
       PROGRAM-ID. MAIN.
       
       DATA DIVISION.
          WORKING-STORAGE SECTION.
          01 WS-STUDENT-ID PIC 9(4) VALUE 1000.
          01 WS-STUDENT-NAME PIC A(15) VALUE 'Tim'.
       
       PROCEDURE DIVISION.
          CALL 'UTIL' USING BY CONTENT WS-STUDENT-ID,
                            BY CONTENT WS-STUDENT-NAME.
          DISPLAY 'Student Id : ' WS-STUDENT-ID
          DISPLAY 'Student Name : ' WS-STUDENT-NAME
          STOP RUN.

CALL 除了用來呼叫外部的函數,也可以呼叫 SYSTEM 執行外部的程式。

       IDENTIFICATION DIVISION.
       PROGRAM-ID. CALL-PROGRAM.

       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  COMMAND-STRING PIC X(20) VALUE "ls -al".
       01  RETURN-CODE-WS PIC 9(4).
       
       PROCEDURE DIVISION.
           CALL "SYSTEM" USING COMMAND-STRING RETURNING RETURN-CODE-WS.
           GOBACK.

下面是人類猜數字的小遊戲:

GETA.cbl

       IDENTIFICATION DIVISION.
       PROGRAM-ID. 'GETA'.
       
       DATA DIVISION.
          LOCAL-STORAGE SECTION.
          01 LS-INDEX  PIC 9.

          LINKAGE SECTION.
          01 LS-ANSWER PIC 9(4).
          01 LS-GUESS  PIC 9(4).
          01 LS-COUNT  PIC 9.
       
       PROCEDURE DIVISION USING LS-ANSWER, LS-GUESS, LS-COUNT.
           PERFORM VARYING LS-INDEX FROM 1 BY 1 UNTIL LS-INDEX > 4
               IF LS-ANSWER(LS-INDEX:1) = LS-GUESS(LS-INDEX:1) THEN
                   ADD 1 TO LS-COUNT
               END-IF
           END-PERFORM.
 
           GOBACK.
       END PROGRAM 'GETA'.

GETB.cbl

       IDENTIFICATION DIVISION.
       PROGRAM-ID. 'GETB'.
       
       DATA DIVISION.
          LOCAL-STORAGE SECTION.
          01 LS-INDEX1  PIC 9.
          01 LS-INDEX2  PIC 9.

          LINKAGE SECTION.
          01 LS-ANSWER PIC 9(4).
          01 LS-GUESS  PIC 9(4).
          01 LS-COUNT  PIC 9.
       
       PROCEDURE DIVISION USING LS-ANSWER, LS-GUESS, LS-COUNT.
           PERFORM VARYING LS-INDEX1 FROM 1 BY 1 UNTIL LS-INDEX1 > 4
             PERFORM VARYING LS-INDEX2 FROM 1 BY 1 UNTIL LS-INDEX2 > 4
                 IF LS-INDEX1 IS NOT EQUAL TO LS-INDEX2 THEN
                     IF LS-ANSWER(LS-INDEX1:1) = LS-GUESS(LS-INDEX2:1)
                         THEN
                         ADD 1 TO LS-COUNT
                     END-IF
                 END-IF
             END-PERFORM
           END-PERFORM.
 
           GOBACK.
       END PROGRAM 'GETB'.

guess.cbl

       IDENTIFICATION DIVISION.
       PROGRAM-ID. GUESS-GAME.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01 WS-ANSWER   PIC 9(4).
       01 WS-GUESS    PIC 9(4).
       01 WS-AVALUE   PIC 9.
       01 WS-BVALUE   PIC 9.

       PROCEDURE DIVISION.
           PERFORM UNTIL 1 < 0
               COMPUTE WS-ANSWER = (FUNCTION RANDOM * 9999) + 1

               IF WS-ANSWER(1:1) <> WS-ANSWER(2:1) AND
                  WS-ANSWER(1:1) <> WS-ANSWER(3:1) AND
                  WS-ANSWER(1:1) <> WS-ANSWER(4:1) AND
                  WS-ANSWER(2:1) <> WS-ANSWER(3:1) AND
                  WS-ANSWER(2:1) <> WS-ANSWER(4:1) AND
                  WS-ANSWER(3:1) <> WS-ANSWER(4:1) THEN
                       EXIT PERFORM
               END-IF
           END-PERFORM.

           PERFORM UNTIL 1 < 0
               DISPLAY "Please input your guess: " WITH NO ADVANCING
               ACCEPT WS-GUESS
        
               MOVE 0 TO WS-AVALUE
               MOVE 0 TO WS-BVALUE
               CALL 'GETA' USING BY CONTENT WS-ANSWER, WS-GUESS,
                                 BY REFERENCE WS-AVALUE
               CALL 'GETB' USING BY CONTENT WS-ANSWER, WS-GUESS,
                                 BY REFERENCE WS-BVALUE
               DISPLAY "Result: A = " WS-AVALUE " B = " WS-BVALUE
               DISPLAY " "

               IF WS-AVALUE = 4 AND WS-BVALUE = 0 THEN
                   DISPLAY "Game is completed."
                   EXIT PERFORM
               END-IF
           END-PERFORM.
           
           STOP RUN.

下面是電腦猜數字的小遊戲:

       IDENTIFICATION DIVISION.
       PROGRAM-ID. RANDOM-EXAMPLE.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01 WS-SOLUTIONS.
           05 WS-NUMBER PIC 9(4) VALUE 0000 OCCURS 5040 TIMES.
       01 WS-NEW-NUMBERS.
           05 WS-NEW-NUMBER PIC 9(4) VALUE 0000 OCCURS 5040 TIMES.
       01 WS-ANSWER   PIC 9(4).
       01 WS-GUESS    PIC 9(4).
       77 WS-AVALUE   PIC 9.
       77 WS-BVALUE   PIC 9.
       77 WS-AGUESS   PIC 9.
       77 WS-BGUESS   PIC 9.
       01 WS-INDEX1   PIC 99.
       01 WS-INDEX2   PIC 99.
       01 WS-INDEX3   PIC 99.
       01 WS-INDEX4   PIC 99.
       01 WS-COUNT1   PIC 9(4).
       01 WS-COUNT2   PIC 9(4).
       01 WS-LOOP1    PIC 9(4).       
       
       PROCEDURE DIVISION.
           INITIALIZE WS-COUNT1 REPLACING NUMERIC DATA BY 0000.

           PERFORM VARYING WS-INDEX1 FROM 0 BY 1 UNTIL WS-INDEX1 > 9
             PERFORM VARYING WS-INDEX2 FROM 0 BY 1 UNTIL WS-INDEX2 > 9
               PERFORM VARYING WS-INDEX3 FROM 0 BY 1
                       UNTIL WS-INDEX3 > 9
                 PERFORM VARYING WS-INDEX4 FROM 0 BY 1
                         UNTIL WS-INDEX4 > 9
                   IF WS-INDEX1 <> WS-INDEX2 AND
                      WS-INDEX1 <> WS-INDEX3 AND
                      WS-INDEX1 <> WS-INDEX4 AND
                      WS-INDEX2 <> WS-INDEX3 AND
                      WS-INDEX2 <> WS-INDEX4 AND
                      WS-INDEX3 <> WS-INDEX4 THEN
                       ADD 1 TO WS-COUNT1
                       STRING WS-INDEX1(2:1) WS-INDEX2(2:1)
                              WS-INDEX3(2:1) WS-INDEX4(2:1)
                              INTO WS-NUMBER(WS-COUNT1)
                       END-STRING
                   END-IF
                 END-PERFORM
               END-PERFORM
             END-PERFORM
           END-PERFORM.

           PERFORM UNTIL 1 < 0
             IF WS-COUNT1 IS EQUAL TO 0 THEN
                 DISPLAY "Something is wrong."
                 EXIT PERFORM
             END-IF

             MOVE WS-NUMBER(1) TO WS-ANSWER
             DISPLAY "My answer is " WS-ANSWER
             DISPLAY "The a value is: " WITH NO ADVANCING
             ACCEPT WS-AVALUE
             DISPLAY "The b value is: " WITH NO ADVANCING
             ACCEPT WS-BVALUE

             IF WS-AVALUE = 4 AND WS-BVALUE = 0 THEN
                 DISPLAY "Game is completed."
                 EXIT PERFORM
             END-IF

             MOVE 0000 TO WS-COUNT2
             PERFORM VARYING WS-LOOP1 FROM 1 BY 1 UNTIL WS-LOOP1
                     IS GREATER THAN WS-COUNT1
                 MOVE 0000 TO WS-NEW-NUMBER(WS-LOOP1)
             END-PERFORM

             PERFORM VARYING WS-LOOP1 FROM 1 BY 1 UNTIL WS-LOOP1
                     IS GREATER THAN WS-COUNT1
                 MOVE WS-NUMBER(WS-LOOP1) TO WS-GUESS
                 MOVE 0 TO WS-AGUESS
                 MOVE 0 TO WS-BGUESS
                 CALL 'GETA' USING BY CONTENT WS-ANSWER, WS-GUESS,
                                   BY REFERENCE WS-AGUESS
                 CALL 'GETB' USING BY CONTENT WS-ANSWER, WS-GUESS,
                                   BY REFERENCE WS-BGUESS

                 IF WS-AVALUE = WS-AGUESS AND WS-BVALUE = WS-BGUESS THEN
                     ADD 1 TO WS-COUNT2
                     MOVE WS-GUESS TO WS-NEW-NUMBER(WS-COUNT2)
                 END-IF
             END-PERFORM

             MOVE WS-COUNT2 TO WS-COUNT1

             PERFORM VARYING WS-LOOP1 FROM 1 BY 1 UNTIL WS-LOOP1
                     IS GREATER THAN WS-COUNT2
                 MOVE WS-NEW-NUMBER(WS-LOOP1) TO WS-NUMBER(WS-LOOP1)
             END-PERFORM
             DISPLAY " "
           END-PERFORM.

           STOP RUN.

參考資料

2026/04/25

MariaDB

MariaDB 出垷的原因是因為 MySQL 被賣給了 SUN, 而後 SUN 被 Oracle 併購,所以 MySQL 被 Oracle 所擁有, 這導致一些 MySQL 原本的開發團隊人員對於 MySQL 是否能夠維持開放原始碼產生質疑, 所以複製 MySQL 5.5 原始碼出來維護的分支,並且成為大多數 Linux distribution 的內建選擇。 一開始 Oracle 其實對 MySQL 的管理還不錯,事情變化的源頭為 Oracle 推出 MySQL Heatwave 服務, 並且一些功能只在 MySQL Heatwave 增加,MySQL 社群版或者是企業版都不具備這些在 MySQL Heatwave 增加的功能, 同時 Oracle 也降低了對 MySQL 的開發重視度,這導致了 MySQL 社群版或者是企業版在 MySQL Heatwave 服務推出後其發展開始停滯, 並且遂漸跟不上使用者的需求。

MariaDB 使用與 MySQL 相同的通訊協定,一般而言可以作為 MySQL 的替代,其目標之一就是儘量維持與 MySQL 的相容, 雖然 MariaDB 與 MySQL 隨著二個開發團隊的各自開發而會有小地方的不同,但是目前仍然大多數的 MySQL 工具都可以直接使用。

在 openSUSE 安裝的指令:

sudo zypper in mariadb

使用下列的指令啟動 MariaDB server:

sudo systemctl start mariadb

使用下列的指令查詢 MariaDB server 的狀態:

sudo systemctl status mariadb

使用下列的指令停止 MariaDB server:

sudo systemctl stop mariadb

在安裝後啟動 MariaDB server,再使用下列的指令進行安全性設定:

sudo mysql_secure_installation

使用下列的指令驗證是否安裝正確(如果有設定密碼 password,輸入密碼):

mariadb -u root -p

在 MariaDB 中,若要完整支援 Unicode(包含 Emoji、特殊中日韓字元),應使用 utf8mb4 編碼, 而非舊的 utf8 (即 utf8mb3),因為舊版只支援 3 位元組,而 utf8mb4 支援 4 位元組。

修改 MariaDB 設定檔 /etc/my.cnf

[client]
default-character-set = utf8mb4

[mysql]
default-character-set = utf8mb4

[mysqld]
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci
character-set-client-handshake=FALSE

使用下列的指令重新啟動 MariaDB server:

sudo systemctl restart mariadb

重啟 MariaDB 服務後,登入並執行以下指令檢查:

SHOW VARIABLES LIKE 'character_set%';
SHOW VARIABLES LIKE 'collation%';

接下來建立一個新的資料庫以及新的使用者。

CREATE DATABASE `danilo`;

新增一個 MariaDB 資料庫使用者 danilo(Local access only),並將密碼設定為 danilo:

CREATE USER 'danilo'@'localhost' IDENTIFIED BY 'danilo';

給予 danilo 帳號在 danilo 資料庫上面的所有權限,讓 danilo 可以對 danilo 資料庫進行任何操作:

GRANT ALL PRIVILEGES ON danilo.* TO 'danilo'@'localhost';

執行下列的指令,讓修改確定有生效:

FLUSH PRIVILEGES;

檢查是否資料無誤:

SHOW GRANTS FOR 'danilo'@'localhost';

使用 GRANT 設定好帳號的權限後,接著就可以使用新的帳號登入使用:

mariadb -u danilo -p

MariaDB 採用可插拔儲存引擎架構,這意味著同一資料庫中的不同資料表可以使用不同的引擎,每個引擎都針對特定的工作負載進行了最佳化。 預設且最常用的通用引擎是 InnoDB。 MariaDB 可以使用 RocksDB 作為儲存引擎(稱為 MyRocks),只能在 x86_64 下使用,並不支援 32 位元的架構。 要注意的是,如果使用 MyRocks 作為儲存引擎,雖然寫入的速度獲得提升,但是 RocksDB engine 並不支援 FOREIGN KEY

修改 MariaDB 設定檔 /etc/my.cnf

[mariadb]
plugin-load-add = ha_rocksdb

重啟 MariaDB 服務後,登入並執行以下指令檢查:

SHOW ENGINES;

在啟用後,建立資料表時指定儲存引擎就可以使用 RocksDB 作為儲存引擎。 下面是一個例子:

CREATE TABLE sensor_data (
    id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
    timestamp DATETIME NOT NULL,
    sensor_id SMALLINT UNSIGNED NOT NULL,
    value FLOAT NOT NULL
) ENGINE = RocksDB;

也可以先建立資料表,再使用下列的指令指定要使用的儲存引擎

ALTER TABLE sensor_data ENGINE=RocksDB;

如果要讓 RocksDB 成為預設的儲存引擎,需要修改 MariaDB 設定檔 /etc/my.cnf。 注意:在一般的情況下使用預設的 InnoDB 是不錯的主意, 因此需要的時候才在建立資料格的時候設定儲存引擎或者使用 ALTER TABLE 修改是一個不錯的做法。 這裡只是記錄如果要修改預設的儲存引擎應該怎麼做。

[mysqld]
#default-storage-engine=InnoDB
default-storage-engine=rocksdb

重啟 MariaDB 服務後,登入並執行以下指令檢查:

SHOW VARIABLES LIKE 'default_storage_engine';

MariaDB 可以使用 Mroonga 作為儲存引擎, Mroonga 支援包含 Chinese, Japanese, and Korean (CJK) 在內的全文搜尋功能,只能在 x86_64 下使用,並不支援 32 位元的架構。

修改 MariaDB 設定檔 /etc/my.cnf

[mariadb]
plugin-load-add = ha_rocksdb
plugin-load-add = ha_mroonga

也可以使用 SQL 指令啟用:

INSTALL SONAME 'ha_mroonga';

重啟 MariaDB 服務後,登入並執行以下指令檢查:

SHOW ENGINES;

相關連結

2026/04/14

C++ Crow

C++ 並不是網頁開發(Web Development)的首選語言, 但是在對效能十分要求或者是需要在資源略為受限的環境執行時,可以考慮使用 C++。 就目前而言,C++ 較為有名的選擇為效能極高並且支援多項功能的 Drogon, 不用安裝依賴函式庫的 Oat++, 以及設計上較為輕量但是功能也較少的 Crow。 還有一個選擇是 Wt, 但是就我個人而言,我認為 Wt 的策略,也就是使用 C++ 撰寫 UI 然後函式庫再轉譯為 HTML/CSS/JavaScript 的方式並不是一個好的做法。

Crow 是一套 C++ micro web framework,採用 Header Only 的設計,其靈感來自於 Python's Flask, 支援 HTTP 1.1 以及 Websocket,使用 C++ ASIO library 構建,特別適合建立 RESTful API 或 Web 服務。 Crow 的原作者在 2017 年停止維護, 不過之後自由軟體社群有人複製出來一份新的分支接手進行維護,目前仍然持續開發中。

在安裝前需要已經先安裝 ASIO development files,下面是在 openSUSE 的安裝指令:

sudo zypper in asio-devel

我使用 source code 安裝:

git clone https://github.com/CrowCpp/Crow.git
mkdir build; cd build; cmake .. -DCROW_BUILD_EXAMPLES=OFF -DCROW_BUILD_TESTS=OFF
sudo make install

(在不編譯範例以及測試程式的情況下,Crow 只會安裝 CMake 相關檔案以及 header files,所以不用執行 make, 只需要使用 make install 安裝)

Crow 預設的靜態檔案資源放置在 static 目錄,但是可以透過巨集設定目錄。接下來寫一個簡單的靜態網頁伺服器驗證可以正確編譯與使用。

main.cpp

#define CROW_STATIC_DIRECTORY "public"
#define CROW_STATIC_ENDPOINT "/<path>"
#include "crow.h"

int main()
{
    crow::SimpleApp app;

    CROW_ROUTE(app, "/")
    ([](const crow::request&, crow::response& res) {
        res.set_static_file_info("public/index.html");
        res.end();
    });

    app.port(18080).run();
    return 0;
}

CMakeLists.txt

cmake_minimum_required(VERSION 3.15)
project(Simple)

set(CMAKE_CXX_STANDARD 17)

# Search for Crow and required dependencies
find_package(Crow REQUIRED)

add_executable(simple main.cpp)

# Link the Crow library to your executable
target_link_libraries(simple PRIVATE Crow::Crow)

Crow 使用 Mustache 作為 template engine language, 接下建立一個簡單的網頁進行測試。 使用 Mustache 撰寫的網頁需要放在 templates 目錄下。

templates/fancypage.html

<!DOCTYPE html>
<html>
  <body>
  <p>Hello {{person}}!</p>
</body>
</html>

main.cpp

#include "crow.h"
// #include "crow_all.h"

int main() {
    crow::SimpleApp app;

    CROW_ROUTE(app, "/<string>")([](std::string name) {
        auto page = crow::mustache::load("fancypage.html");
        crow::mustache::context ctx({{"person", name}});
        return page.render(ctx);
    });

    app.port(18080).multithreaded().run();
}

CMakeLists.txt

cmake_minimum_required(VERSION 3.15)
project(Simple)

set(CMAKE_CXX_STANDARD 17)

# Search for Crow and required dependencies
find_package(Crow REQUIRED)

add_executable(simple main.cpp)

# Link the Crow library to your executable
target_link_libraries(simple PRIVATE Crow::Crow)

編譯成功後執行,使用瀏覽器瀏覽 http://localhost:18080/Bob 進行測試。

參考連結

2026/03/10

魔獸爭霸3

《魔獸爭霸III:混亂之治》(Warcraft III: Reign of Chaos)是暴雪娛樂出品的即時戰略遊戲,《魔獸爭霸》系列第三代作品, 於 2002 年發行。2003 年 5 月暴雪公司又發行了《魔獸爭霸III:寒冰霸權》(Warcraft III: The Frozen Throne), 它是本作的資料片。使用光碟安裝需要 8RTJDU-IH67-BQEL2A-BPSS-NX3197 與 728Y2W-B292-X7PCXZ-PPDX-PKCN72 之類的 CD key。 暴雪娛樂在《魔獸爭霸3》 1.21b 移除了光碟檢查機制,1.27b 則是最後一版可以離線安裝的版本。 《魔獸爭霸III:淬鍊重生》(Warcraft III: Reforged)是由暴雪娛樂開發與發行的即時戰略遊戲,在 2020 年發行, 為 2002 年的《魔獸爭霸3》之復刻版。

可以參考的資料:

使用 winetricks 安裝 lavfilters 解決在使用 Wine 11.3 執行遊戲時部份遊戲動畫黑屏的問題。

winetricks lavfilters

使用 -opengl 參數指定執行遊戲時使用 OpenGL (obsolete as of 1.29) 。


《魔獸爭霸3》使玩家集中注意力於「控制戰鬥單位」而非「生產戰鬥單位」。因此遊戲要求玩家關注於小規模的戰鬥而非宏觀的調兵遣將。 有四個種族在《魔獸爭霸3》中可以選擇,其中兩個在前作《魔獸爭霸2》已經出現,分別是 Human,Orc。 另外兩個則是《魔獸爭霸3》新增的種族:Undead,Night Elf。Naga 在製作《魔獸爭霸III:寒冰霸權》時曾經設想作為第五個可操控的種族, 但在遊戲測試時刪去。而官方解釋是:加上那迦族之後很難保證遊戲的平衡性,因為那迦族的部隊可直接跨過水域。

Gold 是遊戲中主要使用的資源,Lumber 則是第二種需要採集的資源。 Gold 只需要固定的人數採集(五人),更多的人數並不會增加採集速度,這是一個微小但是不錯的改善。

《魔獸爭霸3》加入了角色扮演遊戲的一些要素在即時戰略遊戲中,也就是全新的戰鬥單位英雄 (Hero), 是可以透過獲取經驗值升級並增強能力的超級戰鬥單位。 英雄有 Strength, Agility, Intelligence 三種屬性,其中一種屬性為某個英雄的主要屬性。 其可分為本族英雄和中立英雄。每當己方殺滅一個敵方單位或中立生物時,英雄便可獲取相當的經驗值而累積一定程度時其等級就會上升, 英雄等級最高為 10 級。等級每當上升時,英雄便可通過添加技能點學習或加強一項技能,不同英雄可學習之技能亦有所異。 遊戲過程中,一方最多可以有三個英雄出現(採用三級主堡制,所以需要主堡升級才能夠生產另外一個英雄)。 而如果英雄在戰鬥中陣亡,則可在祭壇中復活並且重生回到戰場上。

中立生物 (creeps) 可視為遊戲中創新之處。即使在多人遊戲中,也有完全由電腦控制的中立生物,以守衛地圖中關鍵的位置及中立的建築物。 它也成為選手間爭奪的資源,擊殺中立生物後便可獲得相當的經驗值、金錢及物品。使這個類似於角色扮演遊戲的玩家更具「攻擊性」而不會過於防衛, 從而提高遊戲的娛樂性及可觀性。

而另一影響遊戲策略的操施就是維護費徵取 ("upkeep" system),此操施源於一種概念——擁有越多戰鬥單位就須要付出越多的資源去滿足部隊的需要, 如士兵需要補給、武器需要打造、盔甲需要鍛煉等。該遊戲則以上稅實行此概念。當玩家部隊的數目到達一定程度時, 就須於每採集十塊黃金上繳其中三塊。當部隊數目抵達更高程度時,每十塊就須上繳六塊。 《魔獸爭霸III:混亂之治》的最大人口數為 90,《魔獸爭霸III:寒冰霸權》略為增加到 100。 也因為有維持費的設計,兵力的增長是有一些限制的,這使得玩家沒有不停的生產士兵以及運送士兵到前線的壓力, 讓玩家更著重兵力的多元化和相剋性。

  • Human
    • Well-rounded units
    • Buildings can be built anywhere
    • Peasants can become Militia at the main Town Hall
    • Multiple workers can be used to construct buildings faster
  • Orc
    • Very beefy and expensive units
    • Burrows coupled with Peons are used as a defense
    • Orc peons are immune while constructing buildings
    • Several orc units can be upgraded to salvage gold and lumber when attacking enemy buildings
  • Night Elf
    • Several buildings (called "Ancients") can uproot to move and attack land units
    • Night elf units have improved vision during nighttime after researching Ultravision
    • Wisps are immune while harvesting gold and can harvest lumber anywhere without needing to return their resources
    • Wisps sacrifice themselves when building Ancients
  • Undead
    • Can use corpses as a resource
    • Buildings must be built upon Blight
    • Acolytes only need to begin a building's summoning
    • Acolytes can harvest gold at Haunted Gold Mines without returning their resources

中立英雄為《魔獸爭霸III:寒冰霸權》加入的新機制,可以在 Tavern 雇用中立英雄。

Race Heroes
Human Alliance Paladin, Archmage, Mountain King, Blood Mage
Orcish Horde Blademaster, Far Seer, Tauren Chieftain, Shadow Hunter
Undead Scourge Death Knight, Lich, Dreadlord, Crypt Lord
Night Elf Sentinels Keeper of the Grove, Priestess of the Moon, Demon Hunter, Warden
Neutral Naga Sea Witch, Dark Ranger, Pandaren Brewmaster, Beastmaster, Pit Lord, Tinker, Firelord

每個種族的工人都是重要的單位,負責採集資源以及建造建築,包含 Human 的 Peasant, Orc 的 Peon,Night Elf 的 Wisp,以及 Undead 的 Acolyte。 增加人口總數的方法為建造特定的建築,有二個種類,主堡 (Main Building) 和糧食供應建築 (Food Supply), 包含 Human 的 Town Hall (+12) 和 Farm (+6), Orc 的 Great Hall (+10, +11 after 1.32.9) 和 Orc Burrow (+10), Night Elf 的 Tree of Life (+10) 和 Moon Well (+10), 以及 Undead 的 Necropolis (+10) 和 Ziggurat (+10)。 其中 Human 的 Town Hall 可以使用 Call to Arms 暫時將 Peasant 轉變成 Militia 進行防禦, Orc Burrow 可以讓工人 Peon 躲進去防守,Night Elf 的 Moon Well 具有補充法力和生命值的能力, Undead 的 Ziggurat 可以升級為 Nerubian Tower 或者是 Spirit Tower。

一般而言,祭壇 (Altar) 會是第一個建造的建築(Human 的 Altar of Kings、 Orc 的 Altar of Storms、Night Elf 的 Altar of Elders),但 Undead 與其它三個種族略有差別。 Undead 使用 Acolyte 收集 Gold,Ghoul 採集 Lumber, 而不像 Human 的工人 Peasant、Orc 的工人 Peon 以及 Night Elf 的工人 Wisp 可以採集 Gold 與 Lumber, 因此 Undead 第一個建築最好是 Crypt,而後才建造 Altar of Darkness。

《魔獸爭霸III:寒冰霸權》加入了各種族的商店,包含 Human 的 Arcane Vault, Orc 的 Voodoo Lounge,Night Elf 的 Ancient of Wonders,Undead 的 Tomb of Relics, 玩家可以在自己的商店購買與販賣物品。

《魔獸爭霸3》有以下 Attack Types 的區別:

  • Normal (most melee units)
    Normal attacks do extra damage against Medium armor, and reduced damage to Fortified armor.
  • Piercing (most ranged attackers)
    Piercing attacks do extra damage to Unarmored units and Light armor, and reduced damage to Fortified, Medium armor, and Heroes.
  • Siege (artillery)
    Siege attacks do extra damage to Fortified armor and Unarmored units, and reduced damage to Medium armor and Heroes.
  • Chaos (A few Creeps, Powerful Summoned Units)
    Chaos attacks do full damage to all armor types.
  • Magic Damage (most spellcasters, air units)
    Magic attacks do extra damage against Light and Heavy armor, and reduced damage to Medium, Fortified armor, and Heroes. Magic attacks do 66% extra damage to ethereal units, and zero damage to magic-immune units.
  • Hero
    Hero attacks do reduced damage to Fortified armor.

《魔獸爭霸3》有以下 Armor Types 的區別:

  • Unarmored (most spellcasters)
    Unarmored takes extra damage from Piercing, and Siege attacks. Some unarmored units such as Huntress, Gargoyle, Hippogryph, and Druid of the Talon in Storm Crow form have armor for balance reasons. Most unarmored units do not have armor.
  • Light (most flying units)
    Light armor takes extra damage from Piercing and Magic attacks.
  • Medium (most ranged attackers and workers)
    Medium armor takes extra damage from Normal attacks, reduces damage from Piercing, Magic, and Siege attacks.
  • Heavy (most low and high tech melee units)
    Heavy armor takes extra damage from Magic attacks.
  • Hero
    Heroes take reduced damage from Piercing, Magic, Spell, and Siege attacks.
  • Fortified (buildings)
    Fortified armor greatly reduces Piercing, Magic, Hero, and Normal attacks, but takes extra damage from Siege attacks.

遊戲中一些技能有自動施法 (Autocast) 的設計。自動施法技能是指可以開啟自動施放或關閉手動施放的技能。 開啟自動施法後,技能會在條件允許時自動施放。

《魔獸爭霸3》中的技能使用可以區分為 Active, Channel, Passive 以及 Aura。 Channel 技能要求使用者在技能啟動期間保持靜止。如果技能被打斷或取消,效果就會結束。 可以中斷的技能包含:

  • Human
    • Sorceress - Polymorph (uncastable on heroes before 1.36.2)
    • Mountain King - Storm Bolt
    • Dragonhawk Rider - Aerial Shackles (air units only)
  • Orc
    • Raider - Ensnare
    • Shadow Hunter - Hex
    • Tauren Chieftain - War Stomp
  • Night Elf
    • Druid of the Talon - Cyclone
    • Keeper of the Grove - Entangling Roots
  • Undead
    • Crypt Lord - Impale
    • Dreadlord - Sleep
    • Frost Wyrm - Freezing Breath (buildings only)
  • Neutral
    • Tinker - Cluster Rockets
    • Firelord - Soul Burn
    • Firelord - Volcano
    • Dark Ranger - Silence
    • Dark Ranger - Charm (uncastable on heroes)
    • Pit Lord - Doom (uncastable on heroes)

Aura(靈氣) 與 Passive 技能類似,沒有冷卻時間或法力消耗, Passive 技能影響的是單位或者是英雄本身,擁有 Aura 技能的單位影響周圍區域內的多個目標。 Aura 技能其種類包含 Archmage 的 Brilliance Aura, Paladin 的 Devotion Aura,Tauren Chieftain 的 Endurance Aura, Kodo Beast 的 War Drums Aura,Keeper of the Grove 的 Thorns Aura, Priestess of the Moon 的 Trueshot Aura,Death Knight 的 Unholy Aura, Dreadlord 的 Vampiric Aura。其中只有 Orc 的 Kodo Beast 並非英雄單位。

2026/02/16

Apache Drill

Apache Drill 使用 Java 撰寫, 它參考了谷歌的 BigQuery 的想法,是一個開放原始碼、可擴展、支援複雜資料的分散式 columnar SQL 查詢引擎, 它可以訪問 Apache Hadoop 的 HDFS,從各種主流文件格式(例如 Parquet、JSON 和 CSV)以及支援的資料庫中讀取數據。 Apache Drill 的優點就是它可以使用 SQL 查詢語言查詢各種不同格式的資料並且與資料互動。

下載程式後解壓縮放在某個目錄,使用 embedded mode 檢查是否可以執行:

bin/drill-embedded

結束程式:

!quit

Distributed Mode 需要 Apache ZooKeeper,並且在 drill-override.conf 設定相關的設定。

drill.exec: {
  cluster-id: "drillbits1",
  zk.connect: "localhost:2181"
}

首先要先啟動 ZooKeeper service。
再來是啟動 Drill server:

bin/drillbit.sh start

停止 server:

bin/drillbit.sh stop

執行 sqlline 驗證可以連線到 Drill server:

bin/sqlline -u jdbc:drill:schema=dfs;zk=localhost

SSL/TLS

使用 keytool 建立 keystroe:

keytool -genkeypair -alias server \
-dname "CN=localhost, OU=IT Department, O=Orange Inc. ,L=Taipei, S=Taiwan,C=TW" \
-ext SAN=DNS:localhost,IP:127.0.0.1 \
-keyalg RSA -keysize 2048 -sigalg SHA256withRSA -storetype PKCS12 \
-validity 3650 \
-keypass password -keystore ./trusted.keystore -storepass password

在 drill-override.conf 設定相關的設定。

drill.exec: {
  cluster-id: "drillbits1",
  zk.connect: "localhost:2181",
  ssl: {
    protocol: "TLSv1.3",
    keyStoreType: "pkcs12",
    keyStorePath: "/home/danilo/Programs/drill/conf/trusted.keystore",
    keyStorePassword: "password",
    trustStoreType: "pkcs12",
    trustStorePath: "/home/danilo/Programs/drill/conf/trusted.keystore",
    trustStorePassword: "password"
  },
  security.user.encryption.ssl: {
    enabled: true,
  },
}

在 drill-override.conf 設定相關的設定,對 WEB UI 允許 SSL/TLS:

drill.exec: {
  cluster-id: "drillbits1",
  zk.connect: "localhost:2181",
  ssl: {
    protocol: "TLSv1.3",
    keyStoreType: "pkcs12",
    keyStorePath: "/home/danilo/Programs/drill/conf/trusted.keystore",
    keyStorePassword: "password",
    trustStoreType: "pkcs12",
    trustStorePath: "/home/danilo/Programs/drill/conf/trusted.keystore",
    trustStorePassword: "password"
  },
  security.user.encryption.ssl: {
    enabled: true,
  },
  http: {
    enabled: true,
    ssl_enabled: true,
  },
}

Plain Security

在 drill-override.conf 設定相關的設定,允許 Plain Security:

drill.exec: {
  cluster-id: "drillbits1",
  zk.connect: "localhost:2181",
  ssl: {
    protocol: "TLSv1.3",
    keyStoreType: "pkcs12",
    keyStorePath: "/home/danilo/Programs/drill/conf/trusted.keystore",
    keyStorePassword: "password",
    trustStoreType: "pkcs12",
    trustStorePath: "/home/danilo/Programs/drill/conf/trusted.keystore",
    trustStorePassword: "password"
  },
  security: {
    auth.mechanisms : ["PLAIN"],
  },
  security.user.auth {
    enabled: true,
    packages += "org.apache.drill.exec.rpc.user.security",
    impl: "pam4j",
    pam_profiles: [ "sudo", "login" ]
  },
  security.user.encryption.ssl: {
    enabled: true,
  },
  http: {
    enabled: true,
    ssl_enabled: true,
    auth: {
      mechanisms: ["FORM"],
    },
  },
}

Apache Drill 支援使用 libpam4j 或者是 jpam 作為 PAM Authenticator。 其中 libpam4j 已被內建,所以只要設定正確就可以使用。

相關連結

2026/02/12

Apache HBase

Apache HBase 是一個開放原始碼、分散式、 列存儲 (Wide column store) 的 NoSQL 資料庫。 它參考了谷歌的 BigTable 論文,實現的程式語言為 Java,建構於 Apache Hadoop HDFS 之上, 其核心架構採用 Master-Slave 模式,主要包含協調服務 ZooKeeper、 管理節點 HMaster、處理資料讀寫的 RegionServers,以及負責儲存資料的 HDFS。 Apache HBase 安裝與 Apache Hadoop 一樣分為三種模式:Standalone, Pseudo-Distributed 與Fully-Distributed。 Fully-Distributed 只能運行在Apache Hadoop 上面。

將下載的 HBase 檔案放到 /home/danilo/Programs/hbase,並且設定好環境變數:

export HBASE_HOME=/home/danilo/Programs/hbase
export PATH=$PATH:$HBASE_HOME/bin

使用 Pseudo-Distributed 與 local file system 安裝模式,修改 hbase-site.xml

<configuration>
  <property>
    <name>hbase.rootdir</name>
    <value>file:///home/danilo/Programs/hbase</value>
  </property>
  <property>
    <name>hbase.tmp.dir</name>
    <value>./tmp</value>
  </property>
  <property>
    <name>hbase.unsafe.stream.capability.enforce</name>
    <value>false</value>
  </property>
  <property>
    <name>hbase.cluster.distributed</name>
    <value>true</value>
  </property>  
  <property>
    <name>hbase.zookeeper.property.dataDir</name>
    <value>/var/lib/zookeeper</value>
  </property>
  <property>
    <name>hbase.zookeeper.quorum</name>
    <value>localhost</value>
  </property>
  <property>
    <name>hbase.zookeeper.property.clientPort</name>
    <value>2181</value>
  </property>  
</configuration>

修改 hbase-env.sh

# Tell HBase whether it should manage it's own instance of ZooKeeper or not.
export HBASE_MANAGES_ZK=false

(HBase 可以設定是否自行管理自己的 ZooKeeper 叢集,這裡選擇使用外部的 ZooKeeper service。)

首先需要啟動 ZooKeeper service。
再來啟動 HBase:

bin/start-hbase.sh

停止 HBase:

bin/stop-hbase.sh

如果要執行 HBase shell:

bin/hbase shell

REST interface

啟動 HBase REST server(前景,使用 -port 指定 port):

bin/hbase rest start -p 8090

啟動 HBase REST server(背景,使用 -port 指定 port):

bin/hbase-daemon.sh start rest -p 8090

停止 HBaseHBase REST server(背景):

bin/hbase-daemon.sh stop rest

SSL/TLS

使用 keytool 建立 keystroe:

keytool -genkeypair -alias server \
-dname "CN=localhost, OU=IT Department, O=Orange Inc. ,L=Taipei, S=Taiwan,C=TW" \
-ext SAN=DNS:localhost,IP:127.0.0.1 \
-keyalg RSA -keysize 2048 -sigalg SHA256withRSA -storetype PKCS12 \
-validity 3650 \
-keypass password -keystore ./trusted.keystore -storepass password

修改 hbase-site.xml

<configuration>
  <property>
    <name>hbase.rootdir</name>
    <value>file:///home/danilo/Programs/hbase</value>
  </property>
  <property>
    <name>hbase.tmp.dir</name>
    <value>./tmp</value>
  </property>
  <property>
    <name>hbase.unsafe.stream.capability.enforce</name>
    <value>false</value>
  </property>
  <property>
    <name>hbase.cluster.distributed</name>
    <value>true</value>
  </property>  
  <property>
    <name>hbase.zookeeper.property.dataDir</name>
    <value>/var/lib/zookeeper</value>
  </property>
  <property>
    <name>hbase.zookeeper.quorum</name>
    <value>localhost</value>
  </property>
  <property>
    <name>hbase.zookeeper.property.clientPort</name>
    <value>2181</value>
  </property>
  <property>
    <name>hbase.rest.ssl.enabled</name>
    <value>true</value>
  </property> 
  <property>
    <name>hbase.rest.ssl.keystore.store</name>
    <value>/home/danilo/Programs/hbase/conf/trusted.keystore</value>
  </property> 
  <property>
    <name>hbase.rest.ssl.keystore.password</name>
    <value>password</value>
  </property> 
  <property>
    <name>hbase.rest.ssl.keystore.type</name>
    <value>pkcs12</value>
  </property>
  <property>
    <name>hbase.rest.ssl.truststore.store</name>
    <value>/home/danilo/Programs/hbase/conf/trusted.keystore</value>
  </property>
  <property>
    <name>hbase.rest.ssl.truststore.password</name>
    <value>password</value>
  </property>
  <property>
    <name>hbase.rest.ssl.truststore.type</name>
    <value>pkcs12</value>
  </property>
</configuration>

再來重新啟動 HBase server 與 HBase REST server 即可。

相關連結

2026/02/08

Apache ZooKeeper

Apache ZooKeeper 是 Apache 軟體基金會的一個軟體專案, 使用 Java 撰寫,它為大型分散式計算提供開源的分散式組態設定服務、同步服務和命名註冊。 ZooKeeper 曾經是 Hadoop 的一個子專案,但現在是一個獨立的頂級專案。

下面為設定 single node (Standalone Operation) 的方式。

將下載的 Apache ZooKeeper 檔案解壓縮以後放到 /opt/zookeeper,並且建立 /var/lib/zookeeper 目錄。

複製 conf/zoo_sample.cfg 為 conf/zoo.cfg,確定有下列的設定:

tickTime=2000
dataDir=/var/lib/zookeeper
clientPort=2181

啟動 server:

sudo bin/zkServer.sh start

如果要停止 server:

sudo bin/zkServer.sh stop

確定可以連線:

bin/zkCli.sh -server localhost:2181

接下來嘗試使用 systedm 管理,在 /usr/lib/systemd/system 目銾下建立 zookeeper.service

[Unit]
Description=ZooKeeper Service
Documentation=http://zookeeper.apache.org
Requires=network.target
After=network.target

[Service]
Type=forking
User=root
Group=root
SuccessExitStatus=143 # For stop service but status code is failed
ExecStart=/opt/zookeeper/bin/zkServer.sh start /opt/zookeeper/conf/zoo.cfg
ExecStop=/opt/zookeeper/bin/zkServer.sh stop /opt/zookeeper/conf/zoo.cfg
ExecReload=/opt/zookeeper/bin/zkServer.sh restart /opt/zookeeper/conf/zoo.cfg
WorkingDirectory=/var/lib/zookeeper
TimeoutSec=30
Restart=on-failure

[Install]
WantedBy=default.target

而後使用 systemd 啟動服務:

sudo systemctl start zookeeper.service

而後使用 systemd 查詢服務狀態:

sudo systemctl status zookeeper.service

而後使用 systemd 停止服務:

sudo systemctl stop zookeeper.service

相關連結

2026/02/04

Apache Geode

簡介

Apache Geode 是一個數據管理平台, 它為廣泛分佈的雲端架構中的資料密集型應用程式提供即時、一致的存取, 一般而言作為 In-Memory Data Grid (IMDG)、快取 (cache) 以及需要即時處理的場合使用。 Apache Geode 提供了 SQL-like 查詢語言,稱為 OQL (Object Query Language), Apache Geode 以 Java object 的方式儲存資料,所以選擇使用 OQL 查詢儲存的資料。

下載 Apache Geode 以後解壓縮放到某個目錄。gfsh 為 Apache Geode 用來管理的 shell tool。

執行 gfsh。下面是在 gfsh 執行的指令,資料來自於 Apache Geode in 15 Minutes or Less 的教學。

Locator 是 Geode 行程 (processes),它告訴新連線的成員正在執行的成員在哪裡,並為伺服器使用提供負載平衡。

start locator --name=locator1

Geode 提供了 web 界面的監控界面,下面是啟動的指令。預設使用者為 admin,密碼為 admin

start pulse

Geode server 是一個行程 (process),它作為叢集中一個長期運行且可配置的成員而存在。 Geode server 主要用於託管長期運行的資料區域,以及運行標準的 Geode 行程,例如用戶端/伺服器配置中的伺服器。

start server --name=server1 --server-port=40411

Regions 是 Geode 叢集的核心建置模組,用於組織資料。在此練習中建立的 Region 採用複製機制在叢集成員之間複製數據, 並利用持久化機制將資料儲存到磁碟。

create region --name=regionA --type=REPLICATE_PERSISTENT

列出目前的 regions:

list regions

列出 Geode 叢集的成員:

list members

描述 Geode region regionA 的資料:

describe region --name=regionA

下面使用 put 新增資料以及使用 query 查詢資料。

put --region=regionA --key="1" --value="one"
put --region=regionA --key="2" --value="two"
query --query="select * from /regionA"

如果你需要刪除一個 region,可以這樣做:

destroy region --name=regionA

如果要停止 server:

stop server --name=server1

關閉系統,包括 locator。

shutdown --include-locators=true

REST

Geode 讓使用者能夠使用 REST 介面存取資料。

啟動一個 locator。

start locator --name=locator1

並且使用以下的設定:

configure pdx --read-serialized=true --disk-store

然後在啟動 Geode server 時加入 --start-rest-api 選項。

start server --name=server1 --server-port=40411 \
--start-rest-api=true \
--http-service-port=8080 --http-service-bind-address=localhost

使用 curl 驗證是否可以使用:

curl -i http://localhost:8080/geode/v1

SSL

使用 keytool 建立 keystroe:

keytool -genkeypair -alias server \
-dname "CN=localhost, OU=IT Department, O=Orange Inc. ,L=Taipei, S=Taiwan,C=TW" \
-ext SAN=DNS:localhost,IP:127.0.0.1 \
-keyalg RSA -keysize 2048 -sigalg SHA256withRSA -storetype PKCS12 \
-validity 3650 \
-keypass password -keystore ./trusted.keystore -storepass password

在 Geode 的 config 目錄下建立一個新的檔案 gfsecurity.properties。 Apache Geode 使用 ssl-enabled-components 設定不同組件間的通訊是否需要使用 SSL/TLS。 all 表示全部都要使用,這裡設定為 web,表示使用在 REST 介面。

ssl-enabled-components=web
ssl-protocols=TLSv1.2,TLSv1.3
ssl-ciphers=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_RSA_WITH_AES_256_GCM_SHA384
ssl-keystore=/home/danilo/Programs/geode/config/trusted.keystore
ssl-keystore-password=password
ssl-keystore-type=pkcs12
ssl-truststore=/home/danilo/Programs/geode/config/trusted.keystore
ssl-truststore-password=password
ssl-truststore-type=pkcs12

使用 gfsh 啟動一個 locator。

start locator --name=locator1 --port=12345 \
--security-properties-file=/home/danilo/Programs/geode/config/gfsecurity.properties

並且使用以下的設定:

configure pdx --read-serialized=true --disk-store

使用 gfsh 啟動一個 server。

start server --name=server1 --server-port=40411 \
--start-rest-api=true \
--http-service-port=8080 --http-service-bind-address=localhost \
--security-properties-file=/home/danilo/Programs/geode/config/gfsecurity.properties

使用 curl 驗證是否可以使用:

curl -k -i https://localhost:8080/geode/v1

Authentication

以下的方式在 Java 24/25 以後,因為 SecurityManager 被禁止而無法使用。 In Java 17, the Security Manager was deprecated for removal under JEP 411. With JDK 24, its functionality will be effectively disabled. So you could not setup HTTP Basic Authentication support for Apache Geode by using SecurityManager since JDK 24.

將下列的內容儲存為 security.json,並且放到各個 locator 與 server 的目錄下。

{
  "roles": [
    {
      "name": "cluster",
      "operationsAllowed": [
        "CLUSTER:MANAGE",
        "CLUSTER:WRITE",
        "CLUSTER:READ"
      ]
    },
    {
      "name": "data",
      "operationsAllowed": [
        "DATA:MANAGE",
        "DATA:WRITE",
        "DATA:READ"
      ]
    },
    {
      "name": "region1&2Reader",
      "operationsAllowed": [
        "DATA:READ"
      ],
      "regions": ["region1", "region2"]
    }
  ],
  "users": [
    {
      "name": "super-user",
      "password": "1234567",
      "roles": [
        "cluster",
        "data"
      ]
    },
    {
      "name": "joebloggs",
      "password": "1234567",
      "roles": [
        "data"
      ]
    }
  ]
}

使用 gfsh 啟動一個 locator。

start locator --name=locator1 --port=12345 \
--security-properties-file=/home/danilo/Programs/geode/config/gfsecurity.properties \
--J=-Dgemfire.security-manager=org.apache.geode.examples.security.ExampleSecurityManager \
--classpath=.

連線到 locator 需要驗證:

connect --locator=localhost[12345] --user=super-user --password=1234567

並且使用以下的設定:

configure pdx --read-serialized=true --disk-store

使用 gfsh 啟動一個 server。

start server --name=server1 --locators=localhost[12345] --server-port=40411 \
--start-rest-api=true \
--http-service-port=8080 --http-service-bind-address=localhost \
--security-properties-file=/home/danilo/Programs/geode/config/gfsecurity.properties \
--J=-Dgemfire.security-manager=org.apache.geode.examples.security.ExampleSecurityManager \
--classpath=. --user=super-user --password=1234567

Memcached

Apache Geode 提供了 memcached 協議相容的介面。

使用 gfsh 啟動一個 locator。

start locator --name=locator1

並且在 config 新增或者修改 cache.xml,使用下列的設定:

<?xml version="1.0" encoding="UTF-8"?>
<cache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
       xmlns="http://geode.apache.org/schema/cache" 
       xsi:schemaLocation="http://geode.apache.org/schema/cache http://geode.apache.org/schema/cache/cache-1.0.xsd"
       version="1.0">
  <region name="gemcached">
    <region-attributes refid="PARTITION" />
  </region>
</cache>

使用 gfsh 啟動一個 server。

start server --name=server1 --server-port=40411 \
--memcached-port=11211 --memcached-bind-address=localhost \
--memcached-protocol=BINARY \
--cache-xml-file=/home/danilo/Programs/geode/config/cache.xml

--memcached-protocol 可以設為 ASCII 或者是 BINARY。 ASCII 是 libMemcached 的預設值,如果要使用 BINARY 需要設定。

使用 memcached-for-Tcl 進行驗證。

package require Memcache

memcache server add localhost 11211
memcache behavior MEMCACHED_BEHAVIOR_BINARY_PROTOCOL 1
memcache set moo "cows go moo"
memcache get moo result
puts $result

相關連結