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.
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)
CL compilado (programa *PGM)
/* ============================================ */
/* 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)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.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().
*CHAR1-9999string / varchar*DECp sdecimal / number*LGL1boolean*INT2 o 4int / short*UINT2 o 4unsigned int*PTR16void*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')
ENDPGMCL 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.
/* 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/* 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/* 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)
ENDDOShell scripting (bash)
CL programado
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.
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)
ENDPGMPGM
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/* 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 */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.
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/* 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)QCMDEXCEjecutar un comando CL dinamicamente
QCMDCHKValidar sintaxis de comando sin ejecutar
QUSRJOBIRecuperar informacion del job actual
QUSROBJDRecuperar descripcion de un objeto
QMHSNDPMEnviar mensaje de programa (baja nivel)
QMHRCVPMRecibir mensaje de programa
QUILNGTXMostrar texto largo en pantalla
QSYRUSRIRecuperar informacion de perfil de usuario
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.
/* 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)
ENDDOPGM
/* 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:
ENDPGMCPF0000Todos los mensajes CPF (catch generico)
MCH0000Todos los errores de maquina
CPF2105Objeto no encontrado para DLTF/DLTOBJ
CPF9801Objeto no existe en biblioteca
CPF9802No autorizado al objeto
CPF0864Fin de archivo al leer con RCVF
CPF2110Biblioteca no encontrada en CHKOBJ
CPF5813Archivo ya existe (CRTSAVF, CRTPF)
CPF1338Error en CALL a programa
CPFA0A0Directorio IFS ya existe (MKDIR)
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.
/* 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)/* 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)
ENDDOUna 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.
/* 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')/* 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/* 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 */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.
/* 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/* 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)Los siguientes son patrones que aparecen frecuentemente en programas CL de produccion. Representan soluciones probadas para problemas habituales.
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/* 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