The Work with Command Stack Utility

CL
Typography
  • Smaller Small Medium Big Bigger
  • Default Helvetica Segoe Georgia Times

Brief: Retrieving executed commands can save you from having to rekey them. But finding the one you're looking for may be more trouble than it's worth...unless you put the WRKCMDSTK utility to work for you.

OS/400's command retrieval function (F9) is great-it allows you to reexecute a previously keyed command quickly and easily. But suppose you want to run a command that is 10 steps back? That's easy. Press F9 10 times-oops, you pressed it 11 times. Since the retrieve function allows only backwards retrieval, you have to clear the command line and start over again. You may find it easier to rekey the command from scratch.

The command entry screen offers another good way to retrieve executed commands-but this too has its problems. The commands are so cluttered up with messages that you may see only one or two commands at a time. When the command you're looking for is many steps back, you may have to scroll through several pages before you find it.

I've often wondered why the command entry screen doesn't have a command key to filter out everything except the commands. This problem was the source of inspiration for the Work with Command Stack (WRKCMDSTK) utility.

A Better Way

The WRKCMDSTK utility presented in this article gives you a way to easily access commands you have executed, even when they are many steps back. The utility displays all previously run commands and allows you to prompt or execute them.

WRKCMDSTK does not have any parameters, so there is no need to prompt. Just key the command on any command line and press Enter. The first time you use WRKCMDSTK during an interactive session, you'll see a status message on the bottom of your screen which says "Building temporary work file." You may also notice a slight delay while the utility performs some initialization routines. Subsequent uses of this command prior to signing off do not produce the message or the extended delay.

WRKCMDSTK presents you with a screen similar to the one shown in 1. Initially, the cursor is positioned on the last command on the subfile display-the most recently entered command, excluding the WRKCMDSTK command itself. WRKCMDSTK is never shown on the display in order to prevent recursive calls. Commands longer than 76 bytes are shown with an ellipsis (...) at the end of the command. The ellipsis signifies that the command has been truncated on the right to fit on the screen. If there is more than one page of commands, you may need to scroll backward to find the command you want.

WRKCMDSTK presents you with a screen similar to the one shown in Figure 1. Initially, the cursor is positioned on the last command on the subfile display-the most recently entered command, excluding the WRKCMDSTK command itself. WRKCMDSTK is never shown on the display in order to prevent recursive calls. Commands longer than 76 bytes are shown with an ellipsis (...) at the end of the command. The ellipsis signifies that the command has been truncated on the right to fit on the screen. If there is more than one page of commands, you may need to scroll backward to find the command you want.

To access an executed command, position the cursor on the command and press F4 to prompt or F16 to execute. The WRKCMDSTK display shows you any messages generated by a reexecuted command in a message subfile at the bottom of the screen. Once you prompt or execute a command, WRKCMDSTK places it at the bottom of the subfile and repositions the cursor.

You should keep a couple of restrictions in mind as you use this utility. First, if you use an Attention key program, you'll find that your Attention key doesn't work while you're displaying the WRKCMDSTK panel. Don't worry; this condition is intentional and only temporary. When you exit the utility, it restores the use of your Attention key. This restriction is necessary because while you're using WRKCMDSTK, the utility must have control of all command execution. An Attention key program can potentially execute a command without informing the WRKCMDSTK utility, so the Attention key is temporarily disabled.

The second restriction is that this utility will not allow you to access commands longer than 1,024 bytes. I had to place a limit on the command length, and 1,024 bytes seemed ample for most commands.

The Nuts and Bolts

The command definition for WRKCMDSTK is presented in 2 (page 31) and the CMD007C1 command-processing program (CPP) is shown in 3 (page 32). The CPP creates two objects in library QTEMP if they don't already exist. The first is physical file CMD007PF (4, page 32) for storing executed commands. The second is user space CMD007US for retrieving commands from the job log. The CPP calls CMD007RG (5) to present executed commands to the user. This is where most of the processing takes place.

The command definition for WRKCMDSTK is presented in Figure 2 (page 31) and the CMD007C1 command-processing program (CPP) is shown in Figure 3 (page 32). The CPP creates two objects in library QTEMP if they don't already exist. The first is physical file CMD007PF (Figure 4, page 32) for storing executed commands. The second is user space CMD007US for retrieving commands from the job log. The CPP calls CMD007RG (Figure 5) to present executed commands to the user. This is where most of the processing takes place.

CMD007RG is a standard subfile program that updates CMD007PF whenever it runs a command. The program takes advantage of two APIs to retrieve messages from the job's message queue. Additional APIs are used to manage a message subfile.

CMD007RG begins by executing the *INZSR subroutine where it loads the subfile. It first reads the CMD007PF file. This file stores request-type messages (commands) retrieved from the job log. The program loads these commands into a subfile in display file CMD007DF (6). Next, the program calls the List Job Log Messages (QMHLJOBL) API. This API copies messages sent to the job's message queue into a user space. The program performs a loop to retrieve each of the messages from the user space by calling the Retrieve User Space (QUSRTVUS) API. The program extracts request messages and writes them to the subfile. The program also writes these messages to the CMD007PF file so that it doesn't have to extract them again on subsequent uses of the utility within the same job.

CMD007RG begins by executing the *INZSR subroutine where it loads the subfile. It first reads the CMD007PF file. This file stores request-type messages (commands) retrieved from the job log. The program loads these commands into a subfile in display file CMD007DF (Figure 6). Next, the program calls the List Job Log Messages (QMHLJOBL) API. This API copies messages sent to the job's message queue into a user space. The program performs a loop to retrieve each of the messages from the user space by calling the Retrieve User Space (QUSRTVUS) API. The program extracts request messages and writes them to the subfile. The program also writes these messages to the CMD007PF file so that it doesn't have to extract them again on subsequent uses of the utility within the same job.

Once the subfile is loaded, the DSPSCR (Display Screen) subroutine presents the subfile to the user. When the user presses a valid command key, the program calls the Remove Program Messages (QMHRMVPM) API to clear the message subfile at the bottom of the screen.

CMD007RG waits for the user to press a function key. If the user press F4 (Prompt) or F16 (Execute), the program drops into a do while equal (DOWEQ) loop. This loop continues until the user press F3 (Exit), F12 (Cancel), or Enter. F4 displays the prompt screen for the selected command; F16 executes the command without prompting. F4 and F16 are processed by the PROCES subroutine. This routine starts by retrieving the command from the subfile record where the cursor was located. The command is then passed to CMD007C2 (7) where it is prompted, if necessary, and then executed.

CMD007RG waits for the user to press a function key. If the user press F4 (Prompt) or F16 (Execute), the program drops into a do while equal (DOWEQ) loop. This loop continues until the user press F3 (Exit), F12 (Cancel), or Enter. F4 displays the prompt screen for the selected command; F16 executes the command without prompting. F4 and F16 are processed by the PROCES subroutine. This routine starts by retrieving the command from the subfile record where the cursor was located. The command is then passed to CMD007C2 (Figure 7) where it is prompted, if necessary, and then executed.

CMD007C2 prompts and executes the command and passes back any generated messages. It calls QCMDCHK to prompt the command if the user pressed F4. It calls QCMDEXC to execute the command. Finally, it calls the Move Program Messages (QMHMOVPM) API to move any generated messages to the messages subfile of CMD007RG.

The PROCES subroutine then writes the command back out to the subfile. It also writes the command to the CMD007PF physical file so that the utility can retrieve it on subsequent calls. The program then redisplays the subfile to the user. This process continues as long as the user makes a selection.

Commands Galore

If you hate to rekey commands, then give WRKCMDSTK a try. Use it as a supplement to F9 and the command entry screen. In some cases, it might make sense to use one method over another. F9 is good for recently executed commands. The command entry screen is better when you want access to the commands and any messages they generate. But when you need to see a concise list of your executed commands, WRKCMDSTK is the best choice.

Robin Klima is a senior technical editor for Midrange Computing.

At a Glance

The Work with Command Stack (WRKCMDSTK) utility presents you with a list of commands you have executed within your interactive job. From the list you can select a command to prompt or reexecute. This utiilty contains the following components.

WRKCMDSTK: The command interface for the utility. There are no parameters for this command.

CMD007C1: The command processing program (CPP) which creates physical file CMD007PF and user space CMD007US in library QTEMP and then calls RPG program CMD007RG.

CMD007RG: An RPG program which retrieves commands from the job log, presents them to the user, and processes the user's request.

CMD007C2: A CL program which prompts and executes commands.

CMD007US: A user space for retrieving executed commands from the job log.

CMD007PF: A physical file to store executed commands.

CMD007DF: A display file which contains a subfile to display executed commands.


The Work with Command Stack Utility

Figure 1 The Work with Command Stack Screen

 Work with Command Stack Position cursor to prompt or execute a command: wrkactjob dspmsg qsysopr CPYF FROMFILE(DPSDS/ITEM01PF) TOFILE(TESTLIB/ITEM01PF) MBROPT(*REPLACE) I... RUNQRY QRYFILE((TESTLIB/ITEM01PF)) OUTTYPE(*PRINTER) WRKOUTQ OUTQ(QPRINT) WRKWTR WTR(SYSPRT01) STRPRTWTR DEV(SYSPRT01) OUTQ(QPRINT) wrksplf wrkcfgsts *lin qremote WRKSBSJOB SBS(QBATCH) SBMJOB CMD(SAVLIB LIB(DPSDS) DEV(SYSTAP01) OUTPUT(*PRINT)) JOB(SAVLIB) JO... WRKSBMJOB SBMFROM(*JOB) WRKJOBQ JOBQ(QBATCH) Bottom F3=Exit F4=Prompt F12=Cancel F16=Execute 
The Work with Command Stack Utility

Figure 2 Command Definition WRKCMDSTK

 /*===============================================================*/ /* To compile: */ /* */ /* CRTCMD CMD(XXX/WRKCMDSTK) PGM(XXX/CMD007C1) + */ /* SRCFILE(XXX/QCMDSRC) */ /* */ /*===============================================================*/ WRKCMDSTK: CMD PROMPT('Work with Command Stack') 
The Work with Command Stack Utility

Figure 3 CL Program CMD007C1

 /*==================================================================*/ /* To compile: */ /* */ /* CRTCLPGM PGM(XXX/CMD007C1) SRCFILE(XXX/QCLSRC) */ /* */ /*==================================================================*/ PGM DCL VAR(&USRSPC) TYPE(*CHAR) LEN(20) VALUE('CMD007US QTEMP') DCL VAR(&ATR) TYPE(*CHAR) LEN(10) DCL VAR(&SIZE) TYPE(*DEC) LEN(10 0) VALUE(1024) DCL VAR(&INIT) TYPE(*CHAR) LEN(1) DCL VAR(&AUT) TYPE(*CHAR) LEN(10) VALUE('*CHANGE') DCL VAR(&TEXT) TYPE(*CHAR) LEN(50) VALUE('WRKCMDSTK') DCL VAR(&LIB) TYPE(*CHAR) LEN(10) DCL VAR(&MSGID) TYPE(*CHAR) LEN(7) DCL VAR(&MSGF) TYPE(*CHAR) LEN(10) DCL VAR(&MSGFLIB) TYPE(*CHAR) LEN(10) DCL VAR(&MSGDTA) TYPE(*CHAR) LEN(80) /* Send all errors to error handling routine */ MONMSG MSGID(CPF0000) EXEC(GOTO CMDLBL(ERROR)) /* Create work file in QTEMP if necessary */ CHKOBJ OBJ(QTEMP/CMD007PF) OBJTYPE(*FILE) MONMSG MSGID(CPF9801) EXEC(DO) SNDPGMMSG MSGID(CPF9898) MSGF(QCPFMSG) MSGDTA('Building + temporary work file..') TOPGMQ(*EXT) MSGTYPE(*STATUS) RTVOBJD OBJ(CMD007PF) OBJTYPE(*FILE) RTNLIB(&LIB) CRTDUPOBJ OBJ(CMD007PF) FROMLIB(&LIB) OBJTYPE(*FILE) + TOLIB(QTEMP) ENDDO /* Create user space in QTEMP if necessary */ CHKOBJ OBJ(QTEMP/CMD007US) OBJTYPE(*USRSPC) MONMSG MSGID(CPF9801) EXEC(CALL PGM(QUSCRTUS) PARM(&USRSPC &ATR + &SIZE &INIT &AUT &TEXT)) /* Call program to list joblog commands */ SETATNPGM PGM(*CURRENT) SET(*OFF) OVRDBF FILE(CMD007PF) TOFILE(QTEMP/CMD007PF) CALL PGM(CMD007RG) DLTOVR FILE(CMD007PF) SETATNPGM PGM(*PRVINVLVL) /* Branch around error handling routine */ GOTO CMDLBL(ENDPGM) /* Error handling routine */ ERROR: + RCVMSG MSGTYPE(*EXCP) MSGDTA(&MSGDTA) MSGID(&MSGID) MSGF(&MSGF) + MSGFLIB(&MSGFLIB) SNDPGMMSG MSGID(&MSGID) MSGF(&MSGFLIB/&MSGF) MSGDTA(&MSGDTA) + MSGTYPE(*ESCAPE) ENDPGM: + ENDPGM 
The Work with Command Stack Utility

Figure 4 Physical File CMD007PF

 *=============================================================== * To compile: * * CRTPF FILE(XXX/CMD007PF) SRCFILE(XXX/QDDSSRC) * *=============================================================== *. 1 ...+... 2 ...+... 3 ...+... 4 ...+... 5 ...+... 6 ...+... 7 A R CMDREC A CMDATE 7 TEXT('Date') A CMTIME 6 TEXT('Time') A CMKEY 4 TEXT('Message Key') A CMCMD 1024 TEXT('Command') A K CMDATE A K CMTIME *. 1 ...+... 2 ...+... 3 ...+... 4 ...+... 5 ...+... 6 ...+... 7 
The Work with Command Stack Utility

Figure 5 RPG Program CMD007RG

 *=============================================================== * To compile: * * CRTRPGPGM PGM(XXX/CMD007RG) SRCFILE(XXX/QRPGSRC) * *=============================================================== *. 1 ...+... 2 ...+... 3 ...+... 4 ...+... 5 ...+... 6 ...+... 7 FCMD007PFUF E K DISK A FCMD007DFCF E WORKSTN F SRN KSFILE SFLREC I SDS I 244 253 JOB I 254 263 USR I 264 2690NBR I IDS I 1 20CEN I 1 40YEAR I 5 60MONTH I 7 80DAY I 2 80CYMD I I 'CMD007US QTEMP ' 9 28 USRSPC I I 256 B 29 320SIZINF I B 33 360STRPOS I B 37 400STRLEN I B 41 440STKCNT IERRCOD IDS I I 8 B 1 40BYTPRV I B 5 80BYTAVA IMSGINF IDS 256 I I -1 B 1 40MAXMSG I I '*NEXT ' 5 14 LSTDIR I 15 24 JOBNAM I 25 34 USRPRF I 35 40 JOBNUM I 41 56 JOBID I I X'00000000' 57 60 STRKEY I I 1024 B 61 640SMXFLT I B 65 680SMXSLT I I 85 B 69 720OFFRET I I 1 B 73 760NUMRET I I 89 B 77 800OFFINV I I 1 B 81 840LENINV I 85 85 RESERV I I 0302 B 86 890SELIDS I I '*' 90 90 MSGDTA IGENDS DS 140 I B 125 1280OFFLST I B 133 1360NUMLST ILSTHDR DS 64 I B 1 40BGNOFF I B 5 80FLDOFF I 24 25 MSGTYP I 26 29 MSGKEY I 50 56 DATSNT I 57 62 TIMSNT ILSTDTA DS 1056 I B 29 320DTALEN IDTAMSG DS 1024 I 1 2 DTACHK I 'wrkcmdstk' C LC I 'WRKCMDSTK' C UC *=============================================================== C EXSR DSPSCR * Do while F4 or F16 key pressed C *IN04 DOWEQ*ON C *IN16 OREQ *ON C EXSR PROCES C EXSR DSPSCR C ENDDO * Drop to here if F3, F12, or Enter key pressed C SETON LR *=============================================================== C *INZSR BEGSR * Initialize subroutine C MOVEL'*' PGMQ * Load records from database C READ CMDREC 99 C *IN99 DOWEQ*OFF C MOVELCMCMD HDCMD C MOVELHDCMD SFCMD C ' ' CHECKHDCMD:77 99 C *IN99 IFEQ *ON C MOVE '...' SFCMD C ENDIF C MOVE *ON *IN90 C ADD 1 SRN 50 C WRITESFLREC C CMKEY IFNE *BLANKS C MOVELCMKEY STRKEY C ENDIF C READ CMDREC 99 C ENDDO * Load job name from program status data structure C MOVELJOB JOBNAM C MOVELUSR USRPRF C MOVELNBR JOBNUM * Call the List Job Log Messages API C CALL 'QMHLJOBL' C PARM USRSPC C PARM 'LJOB0100'FMTNAM 8 C PARM MSGINF C PARM SIZINF C PARM 'JSLT0100'FMTSEL 8 C PARM ERRCOD * Load the starting position and offset of the generic header C Z-ADD1 STRPOS C Z-ADD140 STRLEN * Retrieve the generic header C CALL 'QUSRTVUS' C PARM USRSPC C PARM STRPOS C PARM STRLEN C PARM GENDS * Load the starting position of the list data section C OFFLST ADD 1 STRPOS * Repeat for each entry in the list data section C DO NUMLST * Retrieve an entry from the list header section C CALL 'QUSRTVUS' C PARM USRSPC C PARM STRPOS C PARM 64 STRLEN C PARM LSTHDR * Test for request message C MSGTYP IFEQ '08' C FLDOFF ADD 1 STRPOS * Retrieve an entry from the list detail section C CALL 'QUSRTVUS' C PARM USRSPC C PARM STRPOS C PARM 1056 STRLEN C PARM LSTDTA C PARM ERRCOD * Write to subfile and database C BYTAVA IFEQ 0 C MSGKEY ANDNESTRKEY C 1024 SUBSTLSTDTA:33 DTAMSG C DTACHK IFNE '/*' C DTALEN SUBSTDTAMSG HDCMD P C ' ' CHECKHDCMD POS 50 C POS IFGT 0 C 1025 SUB POS LEN 50 C LEN SUBSTHDCMD:POS HDCMD P C LC:UC XLATEHDCMD WCS 9 C WCS IFNE UC C MOVELHDCMD SFCMD C ' ' CHECKHDCMD:77 99 C *IN99 IFEQ *ON C MOVE '...' SFCMD C ENDIF C MOVE *ON *IN90 C ADD 1 SRN 50 C WRITESFLREC C MOVELDATSNT CMDATE C MOVELTIMSNT CMTIME C MOVELMSGKEY CMKEY C MOVELHDCMD CMCMD C WRITECMDREC C ENDIF C ENDIF C ENDIF C ENDIF C ENDIF * Increment starting position to point to next entry C BGNOFF ADD 1 STRPOS C ENDDO * Load subfile page and highest subfile record number fields C Z-ADDSRN RCDNBR C Z-ADDSRN MAXSRN 50 * C ENDSR *=============================================================== C DSPSCR BEGSR * Display screen C WRITEMSGCTL C WRITEFKEYS C EXFMTCTLREC * Clear all messages from the program message queue C CALL 'QMHRMVPM' C PARM PGMQ C PARM STKCNT C PARM MSGKY 4 C PARM '*ALL' MSGRMV 10 C PARM ERRCOD * C ENDSR *============================================================ C PROCES BEGSR * Process F4 and F16 keys C CSRRRN IFNE 0 C MOVEL*IN04 PROMPT 1 C CSRRRN CHAINSFLREC 99 C *IN99 IFEQ *OFF C CALL 'CMD007C2' C PARM HDCMD C PARM PROMPT C MOVELHDCMD SFCMD C ' ' CHECKHDCMD:77 99 C *IN99 IFEQ *ON C MOVE '...' SFCMD C ENDIF C Z-ADD*YEAR YEAR C Z-ADD*MONTH MONTH C Z-ADD*DAY DAY C CEN IFEQ 19 C Z-ADD0 CEN C ELSE C Z-ADD1 CEN C ENDIF C MOVE CYMD CMDATE C TIME TIME 60 C MOVE TIME CMTIME C MOVELMSGKEY CMKEY C MOVELHDCMD CMCMD C WRITECMDREC C ENDIF C ADD 1 MAXSRN C Z-ADDMAXSRN SRN C WRITESFLREC C Z-ADDSRN RCDNBR C ENDIF * C ENDSR *. 1 ...+... 2 ...+... 3 ...+... 4 ...+... 5 ...+... 6 ...+... 7 
The Work with Command Stack Utility

Figure 6 Display File CMD007DF

 *=============================================================== * To compile: * * CRTDSPF FILE(XXX/CMD007DF) SRCFILE(XXX/QDDSSRC) * *=============================================================== *. 1 ...+... 2 ...+... 3 ...+... 4 ...+... 5 ...+... 6 ...+... 7 A DSPSIZ(24 80 *DS3) A CA03(03) A CA04(04) A CA12(12) A CA16(16) A VLDCMDKEY(30) A R SFLREC SFL A SFCMD 76 O 4 4 A HDCMD 1024 H A R CTLREC SFLCTL(SFLREC) A SFLSIZ(0019) A SFLPAG(0018) A OVERLAY A 90 SFLDSP A SFLDSPCTL A N03 SFLEND(*MORE) A SFLCSRRRN(&CSRRRN) A CSRRRN 5S 0H A RCDNBR 4S 0H SFLRCDNBR(CURSOR) A 3 2'Position cursor to prompt or execu- A te a command:' A COLOR(BLU) A 1 29'Work with Command Stack' A DSPATR(HI) A R FKEYS OVERLAY A 23 2'F3=Exit' A COLOR(BLU) A 23 13'F4=Prompt' A COLOR(BLU) A 23 26'F12=Cancel' A COLOR(BLU) A 23 40'F16=Execute' A COLOR(BLU) A R MSGSFL SFL A SFLMSGRCD(24) A MSGKEY SFLMSGKEY A PGMQ SFLPGMQ A R MSGCTL SFLCTL(MSGSFL) A SFLDSP A SFLDSPCTL A SFLSIZ(2) A SFLPAG(1) A N03 SFLEND A SFLINZ A PGMQ SFLPGMQ *. 1 ...+... 2 ...+... 3 ...+... 4 ...+... 5 ...+... 6 ...+... 7 
The Work with Command Stack Utility

Figure 7 CL Program CMD007C2

 /*===============================================================*/ /* To compile: */ /* */ /* CRTCLPGM PGM(XXX/CMD007C2) SRCFILE(XXX/QCLSRC) */ /* */ /*===============================================================*/ PGM PARM(&CMD &PROMPT) DCL VAR(&CMD) TYPE(*CHAR) LEN(1024) DCL VAR(&CMD2) TYPE(*CHAR) LEN(1025) DCL VAR(&PROMPT) TYPE(*LGL) LEN(1) DCL VAR(&MSGKEY) TYPE(*CHAR) LEN(4) DCL VAR(&MSGTYP) TYPE(*CHAR) LEN(40) VALUE('*COMP + *DIAG *ESCAPE *INFO') DCL VAR(&PGMQUE) TYPE(*CHAR) LEN(10) VALUE('*') DCL VAR(&NBRTYP) TYPE(*CHAR) LEN(4) VALUE(X'00000004') DCL VAR(&STKCNT) TYPE(*CHAR) LEN(4) VALUE(X'00000001') DCL VAR(&ERRCOD) TYPE(*CHAR) LEN(4) VALUE(X'00000000') /* Send all errors to message forwarding routine */ MONMSG MSGID(CPF0000) EXEC(GOTO CMDLBL(FORWARD)) /* Prompt command if requested */ IF COND(&PROMPT) THEN(DO) CHGVAR VAR(&CMD2) VALUE('?' *CAT &CMD) CALL PGM(QCMDCHK) PARM(&CMD2 1025) CHGVAR VAR(&CMD) VALUE(&CMD2) ENDDO /* Execute command */ CALL PGM(QCMDEXC) PARM(&CMD 1024) /* Forward all messages to caller */ FORWARD: + CALL PGM(QMHMOVPM) PARM(&MSGKEY &MSGTYP &NBRTYP &PGMQUE &STKCNT + &ERRCOD) ENDPGM 
BLOG COMMENTS POWERED BY DISQUS