CL Avanzado

Programacion CL mas alla de los comandos basicos: variables, control de flujo, manejo de archivos, APIs del sistema, MONMSG, data areas, data queues, creacion de comandos propios y patrones de automatizacion.

Programas CL vs comandos

CL (Control Language) puede usarse de dos maneras: como comandos individuales en la linea de comandos, o como programas compilados. Un programa CL es un fuente que se compila en un objeto *PGM o un modulo *MODULE ILE. La diferencia es como la que existe entre escribir comandos en bash y escribir un shell script compilado.

CL interactivo (linea de comandos)

  • Cada comando se ejecuta individualmente
  • No hay variables persistentes entre comandos
  • Sin control de flujo (IF/ELSE/DO)
  • Sin manejo estructurado de errores
  • Equivale a ejecutar comandos sueltos en bash
  • Ideal para tareas puntuales y exploracion

CL compilado (programa *PGM)

  • Se compila y ejecuta como unidad
  • Variables declaradas con DCL VAR
  • IF/ELSE, DO/ENDDO, SELECT/WHEN, DOWHILE
  • MONMSG para captura de errores
  • Equivale a un script bash completo y compilado
  • Ideal para automatizacion y procesos batch
CL — Dos formas de compilar programas CL
/* ============================================ */
/*  Opcion 1: CRTCLPGM (modelo OPM clasico)    */
/*  Crea un *PGM directamente del fuente        */
/* ============================================ */
CRTCLPGM PGM(MILIB/MIPGMCL)
         SRCFILE(MILIB/QCLSRC)
         SRCMBR(MIPGMCL)
         LOG(*YES)

/* ============================================ */
/*  Opcion 2: CRTBNDCL (modelo ILE, recomendado)*/
/*  Compila y enlaza en un paso                  */
/* ============================================ */
CRTBNDCL PGM(MILIB/MIPGMCL)
         SRCFILE(MILIB/QCLSRC)
         SRCMBR(MIPGMCL)
         DBGVIEW(*SOURCE)
         ACTGRP(*CALLER)
         DFTACTGRP(*NO)

/* ============================================ */
/*  Opcion 3: CRTCLMOD + CRTPGM (dos pasos ILE)*/
/*  Para enlazar con otros modulos               */
/* ============================================ */
CRTCLMOD MODULE(MILIB/MIPGMCL)
         SRCFILE(MILIB/QCLSRC)
         SRCMBR(MIPGMCL)
         DBGVIEW(*SOURCE)

CRTPGM PGM(MILIB/MIPGMCL)
       MODULE(MILIB/MIPGMCL)
       ACTGRP(*CALLER)
Recomendacion: Usa siempre CRTBNDCL con DFTACTGRP(*NO) para programas CL nuevos. Esto crea programas ILE que pueden participar en service programs y binding directories. CRTCLPGM crea programas OPM que estan limitados en el modelo ILE.

Variables y parametros

En CL las variables se declaran con DCL VAR y se modifican con CHGVAR. Los nombres de variable siempre comienzan con &. Los parametros se reciben en el bloque PGM PARM().

Tipo CLKeywordLargoEquivalente moderno
Character*CHAR1-9999string / varchar
Decimal*DECp sdecimal / number
Logical*LGL1boolean
Integer*INT2 o 4int / short
Unsigned Int*UINT2 o 4unsigned int
Pointer*PTR16void*
CL — Declaracion, asignacion y parametros
PGM PARM(&BIBLIOTECA &ACCION &RESULTADO)

  /* ---- Parametros recibidos ---- */
  DCL VAR(&BIBLIOTECA) TYPE(*CHAR) LEN(10)
  DCL VAR(&ACCION)     TYPE(*CHAR) LEN(10)
  DCL VAR(&RESULTADO)  TYPE(*CHAR) LEN(1)

  /* ---- Variables locales ---- */
  DCL VAR(&NOMBRE)    TYPE(*CHAR)  LEN(50) VALUE('Inicial')
  DCL VAR(&CONTADOR)  TYPE(*DEC)   LEN(7 0) VALUE(0)
  DCL VAR(&ENCONTRADO) TYPE(*LGL)  VALUE('0')
  DCL VAR(&TOTAL)     TYPE(*DEC)   LEN(11 2)
  DCL VAR(&FECHA)     TYPE(*CHAR)  LEN(10)
  DCL VAR(&RUTA)      TYPE(*CHAR)  LEN(256)

  /* ---- Asignacion con CHGVAR ---- */
  CHGVAR VAR(&NOMBRE) VALUE('Fernando Secchi')
  CHGVAR VAR(&CONTADOR) VALUE(&CONTADOR + 1)
  CHGVAR VAR(&ENCONTRADO) VALUE('1')

  /* ---- Concatenacion ---- */
  /* *CAT  = concatena directo (con blancos trailing) */
  /* *TCAT = trim derecho + concatena                 */
  /* *BCAT = un blanco entre valores                  */
  CHGVAR VAR(&RUTA) +
         VALUE('/home/' *TCAT &NOMBRE *TCAT '/datos.csv')

  /* ---- Substring con %SST ---- */
  DCL VAR(&PARTE) TYPE(*CHAR) LEN(10)
  CHGVAR VAR(&PARTE) VALUE(%SST(&NOMBRE 1 8))
  /* &PARTE = 'Fernando' */

  /* ---- Modificar parte de un string ---- */
  CHGVAR VAR(%SST(&NOMBRE 1 4)) VALUE('FERN')

  CHGVAR VAR(&RESULTADO) VALUE('S')

ENDPGM
Importante: En CL todos los parametros se pasan por referencia. Si el programa llamado modifica un parametro, el programa llamador vera el cambio. Esto es diferente a la mayoria de lenguajes modernos donde los parametros son por valor por defecto.

Control de flujo

CL ofrece las estructuras basicas de control: IF/ELSE, SELECT/WHEN, DOWHILE, DOUNTIL y DO con iteracion. Comparado con shell scripting, la sintaxis es mas verbosa pero mas explicita.

CL — IF / THEN / ELSE
/* IF simple */
IF COND(&ACCION *EQ 'BACKUP') THEN(DO)
  SAVLIB LIB(&BIBLIO) DEV(*SAVF) SAVF(QGPL/BACKUP)
  SNDPGMMSG MSG('Backup completado') TOPGMQ(*EXT)
ENDDO

/* IF / ELSE */
IF COND(&TIPO *EQ 'B') THEN(DO)
  /* Proceso batch */
  SBMJOB CMD(CALL PGM(MILIB/PROCESO)) JOB(BATCH01)
ENDDO
ELSE CMD(DO)
  /* Proceso interactivo */
  CALL PGM(MILIB/PANTALLA)
ENDDO

/* Operadores de comparacion:
   *EQ  igual        *NE  distinto
   *GT  mayor que    *LT  menor que
   *GE  mayor igual  *LE  menor igual */

/* Operadores logicos */
IF COND((&ESTADO *EQ 'A') *AND (&SALDO *GT 0)) THEN(DO)
  CHGVAR VAR(&MENSAJE) VALUE('Cliente activo con saldo')
ENDDO

IF COND((&TIPO *EQ 'A') *OR (&TIPO *EQ 'B')) THEN(DO)
  CALL PGM(MILIB/PROCESAR)
ENDDO
CL — SELECT / WHEN / OTHERWISE
/* SELECT es el equivalente a switch/case */
SELECT
  WHEN COND(&OPCION *EQ '01') THEN(DO)
    CALL PGM(MILIB/ALTA)
  ENDDO
  WHEN COND(&OPCION *EQ '02') THEN(DO)
    CALL PGM(MILIB/CONSULTA)
  ENDDO
  WHEN COND(&OPCION *EQ '03') THEN(DO)
    CALL PGM(MILIB/BAJA)
  ENDDO
  OTHERWISE CMD(DO)
    SNDPGMMSG MSG('Opcion no valida: ' *CAT &OPCION) +
              TOPGMQ(*SAME)
  ENDDO
ENDSELECT
CL — DOWHILE / DOUNTIL / DO con LEAVE
/* DOWHILE: evalua al inicio (puede no ejecutarse nunca) */
CHGVAR VAR(&I) VALUE(1)
DOWHILE COND(&I *LE 10)
  SNDPGMMSG MSG('Iteracion: ' *CAT %CHAR(&I)) TOPGMQ(*EXT)
  CHGVAR VAR(&I) VALUE(&I + 1)
ENDDO

/* DOUNTIL: evalua al final (ejecuta al menos una vez) */
DOUNTIL COND(&RESPUESTA *EQ 'S')
  SNDUSRMSG MSG('Desea continuar? (S/N)') VALUES('S' 'N') +
            MSGRPY(&RESPUESTA)
ENDDO

/* DO con ITERATE y LEAVE */
CHGVAR VAR(&I) VALUE(0)
DOWHILE COND('1' = '1')   /* Loop infinito controlado */
  CHGVAR VAR(&I) VALUE(&I + 1)

  IF COND(&I *GT 100) THEN(LEAVE)  /* Salir del loop */

  RCVF
  MONMSG MSGID(CPF0864) EXEC(LEAVE)  /* EOF = salir */

  IF COND(&CLIACTIVO *NE 'A') THEN(ITERATE)  /* Saltar */

  /* Procesar solo clientes activos */
  CALL PGM(MILIB/PROCLI) PARM(&CLIID)
ENDDO

Shell scripting (bash)

  • if [ $x -eq 1 ]; then ... fi
  • case $opt in A) ... ;; esac
  • while [ cond ]; do ... done
  • for i in 1 2 3; do ... done
  • continue / break

CL programado

  • IF COND(&X *EQ 1) THEN(DO) ... ENDDO
  • SELECT WHEN ... OTHERWISE ENDSELECT
  • DOWHILE COND(...) ... ENDDO
  • No hay FOR nativo (usar DOWHILE con contador)
  • ITERATE / LEAVE

Manejo de archivos en CL

Los programas CL pueden leer archivos fisicos y logicos de Db2 usando DCLF (Declare File) y RCVF (Receive File). Tambien trabajan con display files para pantallas interactivas y con archivos del IFS.

CL — Leer un archivo secuencialmente con RCVF
PGM

  DCLF FILE(MILIB/CLIENTES)  /* Declara el archivo */
  DCL VAR(&TOTAL)   TYPE(*DEC)  LEN(11 2) VALUE(0)
  DCL VAR(&CONTEO)  TYPE(*DEC)  LEN(7 0)  VALUE(0)

  /* Los campos del archivo se convierten automaticamente
     en variables CL con & prefijo: &CLID, &CLINOM, etc. */

  DOWHILE COND('1' = '1')
    RCVF                       /* Lee el siguiente registro */
    MONMSG MSGID(CPF0864) EXEC(LEAVE)  /* Fin de archivo */

    IF COND(&CLIACTIVO *EQ 'A') THEN(DO)
      CHGVAR VAR(&TOTAL) VALUE(&TOTAL + &CLISALDO)
      CHGVAR VAR(&CONTEO) VALUE(&CONTEO + 1)
    ENDDO
  ENDDO

  SNDPGMMSG MSG('Clientes activos: ' *CAT %CHAR(&CONTEO) +
    *BCAT 'Saldo total: ' *CAT %CHAR(&TOTAL)) TOPGMQ(*EXT)

ENDPGM
CL — Display file (pantalla interactiva)
PGM

  DCLF FILE(MILIB/MENUD)     /* Display file */
  DCL VAR(&OPCION) TYPE(*CHAR) LEN(2)

  DOWHILE COND('1' = '1')
    SNDRCVF RCDFMT(MENU01)   /* Enviar y recibir pantalla */

    /* F3 = Salir (indicador 03 del display file) */
    IF COND(&IN03 *EQ '1') THEN(LEAVE)

    SELECT
      WHEN COND(&OPCION *EQ '01') THEN(CALL PGM(MILIB/ALTA))
      WHEN COND(&OPCION *EQ '02') THEN(CALL PGM(MILIB/CONS))
      WHEN COND(&OPCION *EQ '03') THEN(CALL PGM(MILIB/MODIF))
      OTHERWISE CMD(DO)
        CHGVAR VAR(&MSGERR) VALUE('Opcion invalida')
        /* Re-display con mensaje de error */
      ENDDO
    ENDSELECT
  ENDDO

ENDPGM
CL — Operaciones IFS (sistema de archivos integrado)
/* Copiar tabla Db2 a CSV */
CPYTOIMPF FROMFILE(MILIB/CLIENTES) +
          TOSTMF('/home/fernando/clientes.csv') +
          MBROPT(*REPLACE) STMFCCSID(1208) +
          RCDDLM(*CRLF) DTAFMT(*DLM) +
          RMVBLANK(*TRAILING) STRDLM(*DBLQ)

/* Importar CSV a tabla Db2 */
CPYFRMIMPF FROMSTMF('/home/fernando/importar.csv') +
           TOFILE(MILIB/CLIENTES) +
           MBROPT(*ADD) RCDDLM(*CRLF) DTAFMT(*DLM)

/* Copiar fuente RPG a stream file */
CPYTOSTMF FROMMBR('/QSYS.LIB/MILIB.LIB/QRPGLESRC.FILE/GESTCLI.MBR') +
          TOSTMF('/home/fernando/gestcli.rpgle') +
          STMFOPT(*REPLACE) STMFCCSID(1208)

/* Crear directorio en IFS */
MKDIR DIR('/home/fernando/exports')
MONMSG MSGID(CPFA0A0)  /* Ya existe, ignorar */

/* Verificar existencia de archivo IFS */
CHKOBJ OBJ('/home/fernando/datos.csv') OBJTYPE(*STMF)
MONMSG MSGID(CPFA0A9) EXEC(DO)
  SNDPGMMSG MSG('Archivo no encontrado en IFS') TOPGMQ(*EXT)
ENDDO

/* Borrar archivo IFS */
RMVLNK OBJLNK('/home/fernando/temp.csv')
MONMSG MSGID(CPFA0A9)  /* No existe, ignorar */

APIs del sistema desde CL

IBM i expone cientos de APIs del sistema que se pueden llamar desde CL usando CALL o CALLPRC. La mas usada es QCMDEXC para ejecutar comandos dinamicamente, pero hay muchas otras para manejo de objetos, jobs y seguridad.

CL — QCMDEXC: ejecutar comandos dinamicos
DCL VAR(&CMD)    TYPE(*CHAR) LEN(512)
DCL VAR(&CMDLEN) TYPE(*DEC)  LEN(15 5)

/* Construir comando dinamicamente */
CHGVAR VAR(&CMD) VALUE('DSPFFD FILE(' *TCAT +
       &BIBLIO *TCAT '/' *TCAT &ARCHIVO *TCAT +
       ') OUTPUT(*OUTFILE) OUTFILE(QTEMP/FFDOUT)')
CHGVAR VAR(&CMDLEN) VALUE(%LEN(%TRIMR(&CMD)))

/* Ejecutar el comando construido */
CALL PGM(QCMDEXC) PARM(&CMD &CMDLEN)
MONMSG MSGID(CPF0000) EXEC(DO)
  SNDPGMMSG MSG('Error ejecutando: ' *CAT &CMD) TOPGMQ(*EXT)
ENDDO
CL — APIs de sistema comunes
/* QCMDCHK: Verificar sintaxis de un comando sin ejecutarlo */
DCL VAR(&CMD)    TYPE(*CHAR) LEN(512)
DCL VAR(&CMDLEN) TYPE(*DEC)  LEN(15 5)
CHGVAR VAR(&CMD) VALUE('WRKOBJ OBJ(MILIB/*ALL) OBJTYPE(*PGM)')
CHGVAR VAR(&CMDLEN) VALUE(%LEN(%TRIMR(&CMD)))
CALL PGM(QCMDCHK) PARM(&CMD &CMDLEN)
MONMSG MSGID(CPF0000)  /* Comando invalido */

/* QUSRJOBI: Recuperar informacion del job */
DCL VAR(&RCVVAR)  TYPE(*CHAR) LEN(86)
DCL VAR(&RCVLEN)  TYPE(*INT)  LEN(4)  VALUE(86)
DCL VAR(&FORMAT)  TYPE(*CHAR) LEN(8)  VALUE('JOBI0100')
DCL VAR(&JOBID)   TYPE(*CHAR) LEN(26) VALUE('*                         ')
DCL VAR(&INTJOBID) TYPE(*CHAR) LEN(16) VALUE(' ')
CALL PGM(QUSRJOBI) PARM(&RCVVAR &RCVLEN &FORMAT +
                         &JOBID &INTJOBID)

/* RUNSQL: ejecutar SQL directo desde CL (V7R3+) */
RUNSQL SQL('DELETE FROM MILIB.TEMPORAL +
  WHERE FECHA < CURRENT_DATE - 30 DAYS') +
  COMMIT(*NONE)

RUNSQL SQL('INSERT INTO MILIB.AUDITORIA +
  (USUARIO, ACCION, FECHA) VALUES +
  (CURRENT_USER, ''LOGIN'', CURRENT_TIMESTAMP)') +
  COMMIT(*CHG)
QCMDEXC

Ejecutar un comando CL dinamicamente

QCMDCHK

Validar sintaxis de comando sin ejecutar

QUSRJOBI

Recuperar informacion del job actual

QUSROBJD

Recuperar descripcion de un objeto

QMHSNDPM

Enviar mensaje de programa (baja nivel)

QMHRCVPM

Recibir mensaje de programa

QUILNGTX

Mostrar texto largo en pantalla

QSYRUSRI

Recuperar informacion de perfil de usuario

MONMSG y manejo de errores

MONMSG es el equivalente de try/catch en CL. Puede usarse a nivel de comando (inline) para capturar errores de un comando especifico, o a nivel de programa (global) para capturar cualquier error no manejado. Los mensajes se identifican con codigos CPFxxxx y MCHxxxx.

CL — MONMSG inline (por comando)
/* Borrar archivo: si no existe, ignorar */
DLTF FILE(MILIB/TEMPORAL)
MONMSG MSGID(CPF2105)  /* CPF2105 = objeto no encontrado */

/* Borrar con accion alternativa */
DLTF FILE(MILIB/TEMPORAL)
MONMSG MSGID(CPF2105) EXEC(DO)
  SNDPGMMSG MSG('Archivo TEMPORAL no existia, se continua') +
            TOPGMQ(*SAME)
ENDDO

/* Multiples codigos en un MONMSG */
CALL PGM(MILIB/PROCESO)
MONMSG MSGID(CPF0000 MCH0000) EXEC(DO)
  /* Captura cualquier error CPF o MCH */
  DCL VAR(&ERRMSG) TYPE(*CHAR) LEN(256)
  RCVMSG MSGTYPE(*EXCP) MSG(&ERRMSG)
  SNDPGMMSG MSG('Error en PROCESO: ' *CAT &ERRMSG) +
            TOPGMQ(*EXT)
ENDDO

/* Verificar objeto antes de usarlo */
CHKOBJ OBJ(MILIB/CLIENTES) OBJTYPE(*FILE)
MONMSG MSGID(CPF9801) EXEC(DO)
  /* Objeto no existe: crearlo */
  CRTPF FILE(MILIB/CLIENTES) SRCFILE(MILIB/QDDSSRC)
ENDDO
CL — MONMSG global (nivel de programa)
PGM
  /* MONMSG global: DEBE ir inmediatamente despues de PGM/DCL */
  MONMSG MSGID(CPF0000) EXEC(GOTO CMDLBL(ERROR))

  /* --- Logica del programa --- */
  SAVLIB LIB(MILIB) DEV(*SAVF) SAVF(QGPL/BACKUP)
  SNDPGMMSG MSG('Backup completado OK') TOPGMQ(*EXT)
  GOTO CMDLBL(FIN)

  /* --- Bloque de error --- */
  ERROR:
    DCL VAR(&MSGID)   TYPE(*CHAR) LEN(7)
    DCL VAR(&MSGTXT)  TYPE(*CHAR) LEN(256)
    DCL VAR(&MSGDTA)  TYPE(*CHAR) LEN(100)
    RCVMSG MSGTYPE(*EXCP) MSGID(&MSGID) MSG(&MSGTXT) +
           MSGDTA(&MSGDTA) RMV(*YES)
    SNDPGMMSG MSG('ERROR ' *CAT &MSGID *BCAT &MSGTXT) +
              TOPGMQ(*EXT)
    /* Re-enviar como *ESCAPE al caller */
    SNDPGMMSG MSGID(CPF9898) MSGF(QCPFMSG) +
              MSGDTA('Error en programa de backup') +
              MSGTYPE(*ESCAPE)
  FIN:

ENDPGM
CPF0000

Todos los mensajes CPF (catch generico)

MCH0000

Todos los errores de maquina

CPF2105

Objeto no encontrado para DLTF/DLTOBJ

CPF9801

Objeto no existe en biblioteca

CPF9802

No autorizado al objeto

CPF0864

Fin de archivo al leer con RCVF

CPF2110

Biblioteca no encontrada en CHKOBJ

CPF5813

Archivo ya existe (CRTSAVF, CRTPF)

CPF1338

Error en CALL a programa

CPFA0A0

Directorio IFS ya existe (MKDIR)

Data areas y data queues

Las data areas son objetos que almacenan valores simples compartidos entre programas (como variables globales persistentes). Las data queuesson colas de mensajes para comunicacion asincrona entre programas, similares a un message broker ligero como Redis queues o RabbitMQ.

CL — Data areas: crear, leer, modificar
/* Crear data area tipo *CHAR */
CRTDTAARA DTAARA(MILIB/ESTADO) TYPE(*CHAR) LEN(50) +
          VALUE('ACTIVO') TEXT('Estado del proceso batch')

/* Crear data area tipo *DEC */
CRTDTAARA DTAARA(MILIB/SECUENCIA) TYPE(*DEC) LEN(7 0) +
          VALUE(0) TEXT('Ultimo numero de secuencia')

/* Leer data area completa */
DCL VAR(&ESTADO) TYPE(*CHAR) LEN(50)
RTVDTAARA DTAARA(MILIB/ESTADO) RTNVAR(&ESTADO)

/* Leer parte de una data area (substring) */
DCL VAR(&PARCIAL) TYPE(*CHAR) LEN(10)
RTVDTAARA DTAARA(MILIB/ESTADO *SST(1 10)) RTNVAR(&PARCIAL)

/* Modificar data area */
CHGDTAARA DTAARA(MILIB/ESTADO) VALUE('PROCESANDO')

/* Modificar parte de una data area */
CHGDTAARA DTAARA(MILIB/ESTADO (1 10)) VALUE('INACTIVO  ')

/* Incrementar contador decimal */
DCL VAR(&SEQ) TYPE(*DEC) LEN(7 0)
RTVDTAARA DTAARA(MILIB/SECUENCIA) RTNVAR(&SEQ)
CHGVAR VAR(&SEQ) VALUE(&SEQ + 1)
CHGDTAARA DTAARA(MILIB/SECUENCIA) VALUE(&SEQ)

/* LDA: Data area local del job (1024 bytes, privada) */
CHGDTAARA DTAARA(*LDA (1 10)) VALUE('MIAPP     ')
DCL VAR(&LDAVAL) TYPE(*CHAR) LEN(10)
RTVDTAARA DTAARA(*LDA (1 10)) RTNVAR(&LDAVAL)
CL — Data queues: comunicacion entre programas
/* Crear una data queue */
CRTDTAQ DTAQ(MILIB/ORDENES) MAXLEN(256) +
        TEXT('Cola de ordenes de produccion') +
        SEQ(*FIFO)       /* FIFO, LIFO o *KEYED */

/* Crear data queue con clave (keyed) */
CRTDTAQ DTAQ(MILIB/PRIORIDAD) MAXLEN(256) +
        SEQ(*KEYED) KEYLEN(2)

/* Enviar mensaje a la data queue (desde CL via QSNDDTAQ) */
DCL VAR(&DTAQNM) TYPE(*CHAR)  LEN(10) VALUE('ORDENES')
DCL VAR(&DTAQLIB) TYPE(*CHAR) LEN(10) VALUE('MILIB')
DCL VAR(&DTAQLEN) TYPE(*DEC)  LEN(5 0) VALUE(256)
DCL VAR(&DTAQDAT) TYPE(*CHAR) LEN(256)

CHGVAR VAR(&DTAQDAT) VALUE('ORD001|ALTA|2024-01-15|PROD-A')
CALL PGM(QSNDDTAQ) PARM(&DTAQNM &DTAQLIB &DTAQLEN &DTAQDAT)

/* Recibir mensaje de la data queue (QRCVDTAQ) */
DCL VAR(&WAIT) TYPE(*DEC) LEN(5 0) VALUE(30) /* 30 seg */
CALL PGM(QRCVDTAQ) PARM(&DTAQNM &DTAQLIB &DTAQLEN +
                         &DTAQDAT &WAIT)

IF COND(&DTAQLEN *GT 0) THEN(DO)
  /* Hay mensaje, procesarlo */
  SNDPGMMSG MSG('Recibido: ' *CAT &DTAQDAT) TOPGMQ(*EXT)
ENDDO
ELSE CMD(DO)
  /* Timeout, no habia mensajes */
  SNDPGMMSG MSG('Sin ordenes pendientes') TOPGMQ(*EXT)
ENDDO
Data queue vs Data area: Usa data areas para configuracion y flags simples (un valor que muchos programas leen). Usa data queues para comunicacion asincrona entre procesos (productor/consumidor). Las data queues son la base de muchas arquitecturas de procesamiento en IBM i.

Crear comandos propios

Una capacidad unica de IBM i es crear tus propios comandos con CRTCMD. Estos comandos aparecen en el sistema como cualquier otro, con prompting (F4), validacion de parametros, ayuda en linea y completion automatico. Es como crear un CLI personalizado con validacion integrada.

CMD source — Definicion del comando EXPORTCSV
/* Fuente: QCMDSRC/EXPORTCSV */
/* Este archivo define los parametros del comando */

CMD PROMPT('Exportar tabla a CSV')

PARM KWD(ARCHIVO) TYPE(*NAME) LEN(10) MIN(1) +
     PROMPT('Archivo a exportar')

PARM KWD(BIBLIOTECA) TYPE(*NAME) LEN(10) DFT(*CURLIB) +
     SPCVAL((*CURLIB)) PROMPT('Biblioteca')

PARM KWD(DIRECTORIO) TYPE(*PNAME) LEN(256) +
     DFT('/home/exports') +
     PROMPT('Directorio destino IFS')

PARM KWD(DELIMITADOR) TYPE(*CHAR) LEN(1) RSTD(*YES) +
     DFT(',') VALUES(',' ';' '|') +
     PROMPT('Delimitador de campos')

PARM KWD(CABECERA) TYPE(*LGL) DFT(*YES) +
     SPCVAL((*YES '1') (*NO '0')) +
     PROMPT('Incluir cabecera')
CL — Programa CPP (Command Processing Program)
/* EXPORTCSV: programa que procesa el comando */
PGM PARM(&ARCHIVO &BIBLIOTECA &DIRECTORIO &DELIM &CABEC)

  DCL VAR(&ARCHIVO)    TYPE(*CHAR) LEN(10)
  DCL VAR(&BIBLIOTECA) TYPE(*CHAR) LEN(10)
  DCL VAR(&DIRECTORIO) TYPE(*CHAR) LEN(256)
  DCL VAR(&DELIM)      TYPE(*CHAR) LEN(1)
  DCL VAR(&CABEC)      TYPE(*LGL)
  DCL VAR(&RUTA)       TYPE(*CHAR) LEN(300)

  MONMSG MSGID(CPF0000) EXEC(GOTO CMDLBL(ERROR))

  /* Construir ruta destino */
  CHGVAR VAR(&RUTA) VALUE(&DIRECTORIO *TCAT '/' +
         *TCAT &ARCHIVO *TCAT '.csv')

  /* Crear directorio si no existe */
  MKDIR DIR(&DIRECTORIO)
  MONMSG MSGID(CPFA0A0)

  /* Ejecutar exportacion */
  CPYTOIMPF FROMFILE(&BIBLIOTECA/&ARCHIVO) +
            TOSTMF(&RUTA) +
            MBROPT(*REPLACE) STMFCCSID(1208) +
            RCDDLM(*CRLF) DTAFMT(*DLM) +
            RMVBLANK(*TRAILING) STRDLM(*DBLQ) +
            FLDDLM(&DELIM)

  SNDPGMMSG MSG('Exportado: ' *CAT &RUTA) TOPGMQ(*EXT)
  GOTO CMDLBL(FIN)

  ERROR:
    DCL VAR(&ERRTXT) TYPE(*CHAR) LEN(256)
    RCVMSG MSGTYPE(*EXCP) MSG(&ERRTXT)
    SNDPGMMSG MSGID(CPF9898) MSGF(QCPFMSG) +
              MSGDTA('Error exportando: ' *CAT &ERRTXT) +
              MSGTYPE(*ESCAPE)
  FIN:

ENDPGM
CL — Crear y usar el comando
/* Compilar el programa CL que procesa el comando */
CRTBNDCL PGM(MILIB/EXPORTCSV) SRCFILE(MILIB/QCLSRC) +
         DFTACTGRP(*NO)

/* Crear el comando apuntando al programa */
CRTCMD CMD(MILIB/EXPORTCSV) PGM(MILIB/EXPORTCSV) +
       SRCFILE(MILIB/QCMDSRC) SRCMBR(EXPORTCSV)

/* Ahora se usa como cualquier comando IBM i: */
EXPORTCSV ARCHIVO(CLIENTES) BIBLIOTECA(PRODLIB) +
          DIRECTORIO('/home/exports/2024') +
          DELIMITADOR(';')

/* Con F4 se obtiene prompting automatico con validacion */

Depuracion de CL

IBM i incluye un depurador interactivo (STRDBG) que funciona con programas CL compilados con DBGVIEW(*SOURCE). Permite colocar breakpoints, inspeccionar variables y ejecutar paso a paso.

CL — Depuracion con STRDBG
/* Compilar con vista de debug */
CRTBNDCL PGM(MILIB/MIPGMCL) SRCFILE(MILIB/QCLSRC) +
         DBGVIEW(*SOURCE) DFTACTGRP(*NO)

/* Iniciar depuracion */
STRDBG PGM(MILIB/MIPGMCL)

/* En el depurador interactivo: */
/* F6  = Agregar breakpoint en linea actual */
/* F10 = Step over (ejecutar siguiente linea) */
/* F11 = Mostrar valor de variable */
/* F12 = Reanudar ejecucion */
/* F17 = Watch variable (avisar cuando cambie) */

/* Agregar breakpoint en linea 25 */
/* Comando en pantalla de debug: */
/*   BREAK 25                     */

/* Evaluar una variable */
/*   EVAL &NOMBRE                 */
/*   EVAL &CONTADOR               */

/* Modificar variable durante debug */
/*   EVAL &ESTADO = 'TEST'        */

/* Terminar depuracion */
ENDDBG
CL — Tecnicas de depuracion sin STRDBG
/* Tecnica 1: Log con SNDPGMMSG (como console.log) */
SNDPGMMSG MSG('DEBUG: &NOMBRE=' *CAT &NOMBRE +
  *BCAT '&ESTADO=' *CAT &ESTADO) TOPGMQ(*EXT)

/* Tecnica 2: Dump del job (escribe todo al job log) */
DMPCLPGM

/* Tecnica 3: Log a data queue para monitoreo */
DCL VAR(&LOGMSG) TYPE(*CHAR) LEN(256)
CHGVAR VAR(&LOGMSG) VALUE(%CHAR(%SST(&TIMESTAMP 1 26)) +
  *BCAT 'PASO:3 VAR:' *CAT &VARIABLE)
CALL PGM(QSNDDTAQ) PARM('DEBUGQ   ' 'QTEMP     ' +
  X'00000100' &LOGMSG)

/* Tecnica 4: DSPJOBLOG despues de ejecutar */
CALL PGM(MILIB/MIPGMCL) PARM('TEST')
DSPJOBLOG OUTPUT(*PRINT)

Patrones comunes

Los siguientes son patrones que aparecen frecuentemente en programas CL de produccion. Representan soluciones probadas para problemas habituales.

CL — Patron: proceso batch con logging
PGM PARM(&PERIODO)

  DCL VAR(&PERIODO)  TYPE(*CHAR) LEN(6)  /* AAAAMM */
  DCL VAR(&JOBNAME)  TYPE(*CHAR) LEN(10)
  DCL VAR(&NUMRECS)  TYPE(*DEC)  LEN(10 0)
  DCL VAR(&ERRORES)  TYPE(*DEC)  LEN(5 0) VALUE(0)

  /* Error handler global */
  MONMSG MSGID(CPF0000 MCH0000) EXEC(GOTO CMDLBL(ERROR))

  /* --- Inicio: log y contexto --- */
  RTVJOBA JOB(&JOBNAME)
  SNDPGMMSG MSG('=== INICIO cierre ' *CAT &PERIODO +
    *BCAT 'Job:' *BCAT &JOBNAME) TOPGMQ(*EXT)

  /* --- Paso 1: verificar prerequisitos --- */
  CHKOBJ OBJ(PRODLIB/FACTURAS) OBJTYPE(*FILE)
  RTVMBRD FILE(PRODLIB/FACTURAS) MBR(*FIRST) +
          NBRCURRCD(&NUMRECS)

  IF COND(&NUMRECS *EQ 0) THEN(DO)
    SNDPGMMSG MSG('Sin registros') TOPGMQ(*EXT)
    GOTO CMDLBL(FIN)
  ENDDO

  /* --- Paso 2: backup previo --- */
  CRTSAVF FILE(QTEMP/BKPCIERRE)
  MONMSG MSGID(CPF5813)  /* Ya existe */
  CLRSAVF FILE(QTEMP/BKPCIERRE)
  SAVOBJ OBJ(FACTURAS) LIB(PRODLIB) +
         DEV(*SAVF) SAVF(QTEMP/BKPCIERRE)

  /* --- Paso 3: ejecutar proceso RPG --- */
  CALL PGM(PRODLIB/CIERRE01) PARM(&PERIODO &ERRORES)

  /* --- Paso 4: exportar resultados --- */
  EXPORTCSV ARCHIVO(RESUMEN) BIBLIOTECA(PRODLIB) +
            DIRECTORIO('/home/exports/' *CAT &PERIODO)

  SNDPGMMSG MSG('=== FIN cierre OK. Errores: ' +
    *CAT %CHAR(&ERRORES)) TOPGMQ(*EXT)
  GOTO CMDLBL(FIN)

  ERROR:
    DCL VAR(&ERRID)  TYPE(*CHAR) LEN(7)
    DCL VAR(&ERRTXT) TYPE(*CHAR) LEN(256)
    RCVMSG MSGTYPE(*EXCP) MSGID(&ERRID) MSG(&ERRTXT)
    SNDPGMMSG MSG('*** ERROR: ' *CAT &ERRID +
      *BCAT &ERRTXT) TOPGMQ(*EXT)
    SNDPGMMSG MSG('Cierre ABORTADO para ' *CAT &PERIODO) +
              TOMSGQ(QSYSOPR)
  FIN:

ENDPGM
CL — Patron: wrapper de entorno (setup/teardown)
/* RUNPROD: configura entorno, ejecuta, y limpia */
PGM PARM(&PROGRAMA &BIBLIOTECA)

  DCL VAR(&PROGRAMA)   TYPE(*CHAR) LEN(10)
  DCL VAR(&BIBLIOTECA) TYPE(*CHAR) LEN(10)
  DCL VAR(&CURLIB_ANT) TYPE(*CHAR) LEN(10)
  DCL VAR(&LIBL_OK)    TYPE(*LGL)  VALUE('0')

  MONMSG MSGID(CPF0000) EXEC(GOTO CMDLBL(CLEANUP))

  /* --- SETUP: guardar y configurar entorno --- */
  RTVJOBA CURLIB(&CURLIB_ANT)
  CHGCURLIB CURLIB(&BIBLIOTECA)
  ADDLIBLE LIB(&BIBLIOTECA) POSITION(*FIRST)
  MONMSG MSGID(CPF2103)  /* Ya en la lista */
  CHGVAR VAR(&LIBL_OK) VALUE('1')

  /* Setear data area de control */
  CHGDTAARA DTAARA(&BIBLIOTECA/CONTROL) VALUE('PROCESANDO')

  /* --- EXECUTE --- */
  CALL PGM(&BIBLIOTECA/&PROGRAMA)

  /* --- TEARDOWN (exito) --- */
  CHGDTAARA DTAARA(&BIBLIOTECA/CONTROL) VALUE('FINALIZADO')

  CLEANUP:
    /* Restaurar entorno original */
    IF COND(&LIBL_OK *EQ '1') THEN(DO)
      RMVLIBLE LIB(&BIBLIOTECA)
      MONMSG MSGID(CPF0000)
    ENDDO
    CHGCURLIB CURLIB(&CURLIB_ANT)
    MONMSG MSGID(CPF0000)

ENDPGM
Buena practica: Siempre incluye un bloque de cleanup al final de tus programas CL que restaure el entorno. Si el programa modifica la library list, current library o data areas de control, asegurate de revertir esos cambios tanto en el flujo normal como en el de error.