CL commands for RPG programmers: the system layer you cannot ignore

If you write RPG, you already use CL whether you realise it or not. Every time you submit a job, call a program from a menu, or check the job log, CL is the layer making it happen. Most RPG developers treat it as a black box — the stuff that runs before and after their program. That is a missed opportunity.

CL (Control Language) is the scripting language of IBM i. It is how you interact with the operating system, automate tasks, chain programs together, and handle the kind of system-level work that RPG was never designed for. Understanding it makes you a significantly more capable developer.

This post covers the CL commands and patterns that matter most to an RPG programmer.

What CL actually is

CL is not a general-purpose language like RPG. You do not write business logic in it. What you do write in it:

  • Job control — setting up the environment before a program runs
  • Program chaining — calling multiple programs in sequence with error handling between them
  • File operations — copying, clearing, deleting, and checking objects
  • Automation — scheduled jobs, batch processing, end-of-day routines
  • System queries — checking whether an object exists, reading job attributes, retrieving system values

A CL program is a compiled object on the system, just like an RPG program. You write the source, compile it with CRTCLPGM or CRTBNDCL, and call it the same way you call anything else.

The commands you will use constantly

CALL — run a program

CALL PGM(MYLIB/MYRPGPGM) PARM('OPTION1' '000100')

Parameters are passed as character strings in CL. Your RPG program receives them as its *ENTRY parameters. This is the most common way to chain programs together.

CHGVAR — set a variable

CL variables are declared with DCL and assigned with CHGVAR:

DCL VAR(&EMPID)  TYPE(*CHAR) LEN(6)
DCL VAR(&STATUS) TYPE(*CHAR) LEN(1)
DCL VAR(&COUNT)  TYPE(*DEC)  LEN(5 0)

CHGVAR VAR(&EMPID)  VALUE('100234')
CHGVAR VAR(&STATUS) VALUE('A')
CHGVAR VAR(&COUNT)  VALUE(0)

CL variable names always start with &. Types are *CHAR, *DEC, *LGL (logical, true/false), and *INT.

IF / ELSE / ENDIF — conditional logic

IF COND(&STATUS = 'A') THEN(DO)
  CALL PGM(MYLIB/ACTIVEPGM)
ENDDO
ELSE CMD(DO)
  CALL PGM(MYLIB/INACTIVEPGM)
ENDDO

The DO / ENDDO block is how you group multiple statements under an IF. Without it, only the immediately following command is conditional.

MONMSG — catch errors

This is one of the most important CL commands. MONMSG (Monitor Message) intercepts error messages so your program can handle them instead of crashing.

CHKOBJ OBJ(MYLIB/MYFILE) OBJTYPE(*FILE)
MONMSG MSGID(CPF9801) EXEC(DO)
  /* Object does not exist — handle it */
  SNDPGMMSG MSG('File MYFILE not found in MYLIB')
  GOTO CMDLBL(ENDPGM)
ENDDO

CPF9801 is the message ID for “object not found.” Every system error has a message ID in the CPF range. You can monitor for specific ones or use CPF0000 to catch any error.

CHKOBJ — check if an object exists

Before you try to use a file or program, check it exists:

DCL VAR(&FILEEXISTS) TYPE(*LGL) VALUE('1')

CHKOBJ OBJ(MYLIB/DATAFILE) OBJTYPE(*FILE)
MONMSG MSGID(CPF9801) EXEC(CHGVAR VAR(&FILEEXISTS) VALUE('0'))

IF COND(&FILEEXISTS = '1') THEN(DO)
  /* Safe to proceed */
ENDDO

OVRDBF — override a file

This is the command RPG developers need to know about most. OVRDBF redirects a file reference at runtime — your RPG program thinks it is opening CUSTMAST, but you have overridden it to open CUSTMAST_TEST instead.

OVRDBF FILE(CUSTMAST) TOFILE(TESTLIB/CUSTMAST_TEST)
CALL PGM(MYLIB/CUSTPGM)
DLTOVR FILE(CUSTMAST)

Always delete the override with DLTOVR after the call, or it will affect every subsequent program in the job that opens the same file.

CPYF — copy a file

CPYF FROMFILE(PRODLIB/ORDERS) TOFILE(TESTLIB/ORDERS) +
     MBROPT(*REPLACE) CRTFILE(*YES)

MBROPT(*REPLACE) replaces existing data. CRTFILE(*YES) creates the target file if it does not exist.

CLRPFM — clear a physical file member

CLRPFM FILE(MYLIB/WORKTABLE)

Deletes all records from a file without deleting the file itself. Essential before reloading work tables.

SBMJOB — submit a job to batch

SBMJOB CMD(CALL PGM(MYLIB/NIGHTLY) PARM('FULL')) +
       JOB(NIGHTLYRPT) +
       JOBD(MYLIB/BATCHJD) +
       OUTQ(MYLIB/BATCHOUTQ)

This runs the program in the background as a separate job. Your CL program continues immediately — it does not wait for the batch job to finish.

SNDPGMMSG — send a message

SNDPGMMSG MSG('Processing complete') MSGTYPE(*INFO)

SNDPGMMSG MSGID(CPF9898) MSGF(QCPFMSG) +
           MSGDTA('Critical error in nightly job') +
           MSGTYPE(*ESCAPE)

*INFO sends an informational message to the job log. *ESCAPE terminates the program and propagates the error to the caller — similar to throwing an exception.

A complete CL program pattern

Here is a typical CL program structure that an RPG developer might write to wrap a batch process:

PGM PARM(&RUNTYPE)

DCL VAR(&RUNTYPE)   TYPE(*CHAR) LEN(4)
DCL VAR(&FILEREADY) TYPE(*LGL)  VALUE('1')
DCL VAR(&ERRORMSG)  TYPE(*CHAR) LEN(100)

/* Check the input file exists */
CHKOBJ OBJ(PRODLIB/ORDERPF) OBJTYPE(*FILE)
MONMSG MSGID(CPF9801) EXEC(DO)
  CHGVAR VAR(&FILEREADY) VALUE('0')
  CHGVAR VAR(&ERRORMSG)  VALUE('Input file ORDERPF not found')
ENDDO

IF COND(&FILEREADY = '0') THEN(DO)
  SNDPGMMSG MSG(&ERRORMSG) MSGTYPE(*ESCAPE)
ENDDO

/* Clear the work table */
CLRPFM FILE(PRODLIB/ORDWRK)
MONMSG MSGID(CPF0000) EXEC(DO)
  SNDPGMMSG MSG('Warning: could not clear work table') MSGTYPE(*INFO)
ENDDO

/* Override for test mode */
IF COND(&RUNTYPE = 'TEST') THEN(DO)
  OVRDBF FILE(ORDERPF) TOFILE(TESTLIB/ORDERPF_T)
ENDDO

/* Call the RPG program */
CALL PGM(PRODLIB/ORDRPG) PARM(&RUNTYPE)
MONMSG MSGID(CPF0000) EXEC(DO)
  SNDPGMMSG MSG('ORDRPG failed - check job log') MSGTYPE(*ESCAPE)
ENDDO

/* Clean up override */
IF COND(&RUNTYPE = 'TEST') THEN(DO)
  DLTOVR FILE(ORDERPF)
ENDDO

SNDPGMMSG MSG('Order processing complete') MSGTYPE(*INFO)

ENDPGM: RETURN
ENDPGM

This pattern — check, clear, override, call, clean up — covers most of what a CL wrapper program needs to do.

Running CL commands from inside RPG

Sometimes you need to trigger a CL command from within an RPG program. The bridge is the QCMDEXC API:

**FREE
dcl-pr QCMDEXC extpgm;
  command  char(3000) const;
  cmdlen   packed(15:5) const;
end-pr;

dcl-s CmdString char(3000);

CmdString = 'CLRPFM FILE(MYLIB/WORKTABLE)';
QCMDEXC(CmdString: %len(%trimr(CmdString)));

This lets your RPG program clear a file, submit a job, or override a file reference at runtime — without needing a separate CL program.

The commands worth learning next

The commands in this post cover the majority of what you will write in practice. Once comfortable, the natural next step is:

  • RTVJOBA — retrieve job attributes (current user, job name, date format) into CL variables
  • RTVSYSVAL — retrieve system values like the current date or time zone
  • WRKJOBSCDE — manage scheduled jobs for automation
  • RUNSQL — run SQL statements from CL, useful for data setup in batch routines

CL rewards learning incrementally. Each command you add to your repertoire opens up a new class of problems you can solve at the system level, without touching your RPG application code at all.

Next post: DB2 for i — the database hiding in plain sight.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top