.com.sel(1) .com {ifout starts here} .comment Choose the output device parameters for this document .if outfinal . if outrecord . out(las 30 c1 r+ s4).com Robelle bound version, attached . else . out(las 30 c1 r- s4).com Robelle bound version . endif .elseif outhelpcomp . out(lpt q+ w80).com helpcomp;parm=1 .elseif outa4 . if outlpt . if outrecord . out(lpt s4 r+).com A4 paper, $Stdlist/LP/disc, attached . else . out(lpt s4 r-).com A4 paper, $Stdlist/LP/disc . endif . elseif outlaser . if outrecord . if outdouble . out(las 30 s5 r+ d+).com A4 paper, LaserJet, attached, duplex . else . out(las 30 s5 r+).com A4 paper, LaserJet, attached . endif . else . if outdouble . out(las 30 s5 r- d+).com A4 paper, LaserJet, duplex . else . out(las 30 s5 r-).com A4 paper, LaserJet . endif . endif . else.com No other outxxx jcws specified . out(lpt s4 r-).com generic: A4 paper, $Stdlist/LP/disc . endif .else . if outlpt . if outrecord . out(lpt s3 r+).com Letter, $Stdlist/LP/disc, attached . else . out(lpt s3 r-).com Letter, $Stdlist/LP/disc . endif . elseif outlaser . if outrecord . if outdouble . out(las 30 s4 r+ d+).com Letter, LaserJet, attached, duplex . else . out(las 30 s4 r+).com Letter, LaserJet, attached . endif . else . if outdouble . out(las 30 s4 r- d+).com Letter, LaserJet, duplex . else . out(las 30 s4 r-).com Letter, LaserJet . endif . endif . else.com No outxxx jcws specified . out(lpt s3 r-).com Letter, generic: $Stdlist/LP/disc . endif .endif .comment .include f92286f.qlibdata.robelle .include styles.qlibdata.robelle .form(k1 [ ///l51 / #31"Programming the CI"/#34"4018-"Pn:0////]) .form(k2 [ ////l51 //// ]) .form 2 .if not outhelpcomp . mar(k1 l0) . mar(k2 l0) .endif .opt (f- l- r-) .title 4018 .com |6PAPER NUMBER 4018| .font 3 Programming the Command Interpreter |1An Introduction, a Dog, and New Tricks| .font 1 by Ken Robertson July 1995 Robelle Consulting Ltd. Unit 201, 15399-102A Ave. Surrey, B.C.@@Canada V3R 7K1 Toll-free:@@1-800-561-8311 Phone:@@(604) 582-1700 Fax:@@(604) 582-1799 E-mail:@@ken_robertson&@robelle.com WWW:@@http://www.robelle.com .font 0 Copyright Robelle Consulting Ltd. 1995 Permission is granted to reprint this document (but ~not~ for profit), provided that copyright notice is given. .opt .font 0 .count 0 .form 1 .page .opt (l- r- f-) |3 Programming the Command Interpreter| |1An Introduction, a Dog, and New Tricks| |1by Ken Robertson| .opt |3Introduction| An overworked, understaffed data processing department is all too common in today's ever belt-tightening, downsizing and de-staffing companies. An ad-hoc request may come to the harried data processing manager. She may throw her hands up in despair and say, "It can't be done. Not within the time frame that you need it in." Of course, every computer-literate person knows deep down in his heart that every programming request can be fulfilled, if the programmer has enough hours to code, debug, test, document and implement the new program. The informed DP manager knows that programming the Command Interpreter (CI) can sometimes reduce that time, changing the "impossible deadline" into something more achievable. In this paper you will learn some of the programming concepts of the CI. You will also learn how you can use the CI to solve some common data processing problems. You will find a short glossary at the end that explains some of the terms used in this paper. |3A Little History| Before MPE/iX, (and even before MPE/XL), there was a run-time environment for the MPE/V class of HP computers called the Command Interpreter (CI). The Command Interpreter on MPE/V machines had limited programming capability, with If@/@@Else constructs and numeric variables limited to values between 0 and 65535. The basic interface of the MPE/V CI was ported to MPE/iX machines, and beefed up to be usable as a run-time shell. The MPE/iX Command Interpreter has a generous command set that pushes the shell into the realm of a true programming tool. Its ability to evaluate expressions, and to perform I/O on files allows the end user to perform simple data processing functions without having to resort to using a 3GL. The CI can be used to solve complex problems. Since its code is interpreted, a CI solution to a problem may execute too slowly for practical purposes. In this case, a 3GL, 4GL, or third- party software solution would have to be investigated. |3What are Command Files?| Command files are a collection of commands in flat files, of either variable or fixed length record structure, that reside in the MPE or POSIX file space. Phew! Basically, command files are what you could call MPE macros. Anything that you can do interactively in the CI, you can do with command files, and then some. You can use command files in situations that call for repetitive functions, such as recompiling source code, special spooler commands, etc. Command files are also great when you want to hide details from the end user. A command file is executed when its name is typed in the CI, or invoked from a command file or programming shell. Just as in program execution, the user's Hppath is searched to determine the location of the command file. When you're deciding where to store a command file, it's a good idea to have some conventions in mind. One common convention involves creating a CMD group (short for command group) in every account for local command files, and a production CMD group that everyone can access. You can set up a logon UDC to ensure that the Hppath variable is set correctly. Hey - why not have a logon command file? Well, you can't, because the current version of MPE doesn't support it. If you insist on having one, you can always make a logon UDC that executes your logon command file. .style example *********************** logonudc option logon, nobreak logon.cmd.prod bye .style body The above UDC is invoked at logon, and executes the command file called |5logon.cmd.prod|. The last line in the command file logs the user off. One question that often arises is "Which are better - command files or UDC's?" Interestingly enough, UDC stands for User Defined Command. UDC's must be managed differently from command files, and have small differences in their internal organization. A UDC file is a library of callable commands with multiple entry points, whereas a command file has but one entry point. You manage UDC files via the Setcatalog and Showcatalog commands. If you need to make modifications to a UDC, you must make sure that no users are accessing the file when you want to save your changes. UDC files must be attached to a session using the Setcatalog command. This means that the UDC file (and all of the UDC's you set up) will be in use while a user is logged on. In order to make changes to a UDC file, you will have to either bump the users accessing the file off the system, or create a brand new UDC file and issue a new Setcatalog command. The changes that you made will take effect only when the user logs back on again. With command files, on the other hand, you may create new ones at any time. They will be immediately accessible to anyone who is logged on. You still need exclusive access to delete or to make changes to an existing command file, but users are also less likely to be accessing command files. In the current operating system, UDC's do tend to be more secure - you can use |5option nohelp| to prevent snooping eyes from reading the contents of a particular user command. (Note: A future operating system release will allow execute access for command files. Currently, you must have execute and read access to the files in order to use them.) .page 12 |3The Command File Structure| The structure of a command file is as follows: .style example PARM parameter declarations (optional) MPE command . . . MPE command .style body Any MPE command can be used. This includes If@/@@Else, and While to form looping constructs. You can also invoke any command file or UDC by its name. Command files can be called recursively, nesting levels up to 30 times deep. |2(Note to Qedit users: Qedit has a maximum recursive level of 10, which should be enough.)| .page 7 |3The Programming Environment| Programmers will tell you that they need an "environment in which to operate". Development of code should progress smoothly, with a minimum of effort required to translate ideas into working programs. For this you need an editor, and in the case of 3GL and 4GL, a program (compiler) to compile your code. Strictly speaking, you don't need an editor to create CI "programs", but it does make your job of writing the code a lot easier. Qedit, from Robelle Consulting, is a flexible editor in which to develop CI programs. You can stay inside Qedit to write, to keep and to test your command files, which can save you steps in the traditional "Text, Keep, Exit, Try Again, and Edit" cycle. If you don't want to use an editor to create command files, you can use a feature of the CI called I/O redirection. For example, the following CI commands will build a simple command file called |5sj|. .font 5 echo echo All executing jobs: > sj echo showjob;exec >> sj .font 0 Note that as your command files become more complex, you may find it trickier to edit them in this manner. Consider a command file that can be used to look up telephone numbers from a flat file: .style example anyparm search_string = "?" if "!search_string" = "?" then echo command file: phone search_string return endif grep.hpbin.sys "-i '!search_string' PHONES" .style body How can you echo variables into a file without getting the assigned value of the variable? Say, for example, the value of the variable |5my_time| was equal to "10:02". The statement, .style example echo echo The time is !mytime > cmdfile .style body would put the string "echo The time is 10:02" into the first record of the file. To get the variable name into the file, you must use two exclamation marks at the beginning of the variable name, like this: .style example echo echo The time is !!mytime > cmdfile .style body That would write the string "echo The time is !my_time" into the first record of Cmdfile. .page 8 |3Variables| As in any program, you use variables to keep track of things. HP provides three types of variables in the CI: numeric, boolean and string. Numerics are limited to integers, booleans to true or false, and strings to lengths up to 255 characters. Unfortunately, there is no provision for arrays. However, if you really need them, they can be simulated (slowly!) using either files or strings, or translatable naming conventions. We'll discuss this a bit later. The variable name itself can be a combination of alphanumeric characters and the underbar character, but the name must start with either a letter or an underbar. .page 7 |3Setting and Displaying Variables| You have two methods for setting variables: use the typical assignment command, or prompt a value from a terminal. .font 5.opt (f-) 1. setvar |2variable_name| |2expression| 2. input |2variable_name| |2prompt| .font 0 .opt The expressions for Setvar can be made up of either numerics, strings, booleans, variable names, or variable dereferencing (sort of like pointers), all glued together with operators. For example, to concatenate the values of two string variables, you can do the following: .font 5 setvar group_account "!hpgroup"@+@"."@+@"!hpaccount" .font 0 If the logon group and account were Pub and Sys, the above would set the variable |5group_account| to the value of Pub.Sys. Figure 1 shows a list of all of the possible operators in a Setvar command. .page 12 .font 6 .box(h9 w21).box(r+1 h1 w55).box(r+2 w55).box(r+3 w55) .box(r+4 w55).box(r+5 h2 w55).box(r+7 h1 w55) .box(h9 w55) .opt (f-).mar(r+12) #+2Logical operators: #+23AND, OR, XOR, NOT #+2Boolean functions and values: #+23BOUND, TRUE, FALSE, ALPHA, ALPHANUM, NUMERIC, ODD #+2Comparison operators: #+23=, <>, <, >, <=, >= #+2Bit manipulation operators: #+23LSL, LSR, CSR, CSL, BAND, BOR, BXOR, BNOT #+2Arithmetic operators: #+23MOD, ABS, * , / , + , -, ^ (exponentiation) #+2Functions returning strings: #+23CHR, DWNS, UPS, HEX, OCTAL, INPUT, LFT, RHT, RPT, #+2 #+23LTRIM, RTRIM, STR #+2Functions returning integers: #+23ABS, LEN, MAX, MIN, ORD, POS, TYPEOF #+2Other functions: #+23FINFO, SETVAR #+2 .font 0 .opt.opt(l- r-).mar |2Figure 1. Setvar Operators| .opt.font 0 .style body .page 4 |3Variable Locality and Longevity| Although some variables seem permanent, only a few system-supplied, read-only variables are permanent. For example, the HPSUSAN variable contains the special CPU number unique to each HP 3000/iX computer. All HP-defined variables start with the letters HP, and appear in upper case. To see a list of them, simply enter this command, .style example showvar hp&@ .style body User variables@-@the variables that you create@-@can have a lifetime as long as your session, but no longer. You create variables using the Setvar command, and delete them using the Deletevar command. CI variables are global to your session. If you create a variable and then start up a new CI shell, the variables will be accessible from the new shell as well. You do have the ability to create local variables which exist only during the execution of a command file. You define local variables with the Parm line in the command file. For example, .style example parm local_myvar="Not Available In Stores" echo !local_myvar .style body If after executing the above command file, you do a |5showvar local_myvar|, the variable will not exist, and a CI error will grace your screen. The temporary variable |5my_var| existed only during the execution of the command file. Local variables cannot be modified, and are only accessed by reference within the command files. If you have a local variable and a permanent variable with the same name, then the local variable takes precedence during expansion. Make sure that your local variables have different names from your permanent ones, otherwise you'll have more confusion than you need at debugging time. The following command file illustrates local versus permanent variable referencing. .style example |1hithere| parm hpjobname="ZZTOP" echo Good evening, !hpjobname. showvar hpjobjname Executing Hithere gives us: Good evening, ZZTOP HPJOBNAME = KEN .style body Attention System Managers: Don't worry!@ This trick won't fool programs that retrieve the value of variables using the HPCIGETVAR intrinsic, or command files that are nested within a command file. The value of the local variable will be returned during expansion |1only| within the command file that defines it. .page 10 |3Getting Data Into and Out of Files| So you want to keep some data around for awhile? Use a file! Well, you knew that already, I'll bet. What you probably didn't know is that you can get data into and out of files fairly easily using I/O redirection and the print command. I/O redirection allows input or output to be directed to a file instead of to your terminal. I/O redirection uses the symbols ">", ">>" and "<". Use ">" to redirect output to a temporary file (you can make the file permanent if you use a file command); use ">>" to append output to the file; finally, use "<" to re-direct input from a file. .style example echo Value 96 > myfile echo This is the second line >> myfile input my_var < myfile setvar mynum_var str("!my_var",7,2) setvar mynum_var_2 !mynum_var - (6 * 9 ) echo The answer to the meaning of life, the universe echo and everything is !mynum_var_2. .style body After executing the above command file, the file Myfile will contain two lines, "Value 96" and "This is the second line" (without quotes, of course). The Input command uses I/O redirection to read the first record of the file, and assigns the value to the variable |5my_var|. The first Setvar extracts the number from the middle of the string, and proceeds to use the value in an important calculation in the next line. How can you assign the data in the second and subsequent lines of a file to variables? You use the Print command to select the record that you want from the file, and send the output to a new file. .style example print myfile;start=2;end=2 > myfile2 .style body You can then use the Input command to extract the string from the second file. .page 4 |3Rolling Your Own System Variables| It's easy enough to create a static file of Setvar commands that gets invoked at logon time, and it's not difficult to modify the file programmatically. For example, let's say that you would like to remember a particular variable from session to session, such as the name of your favorite printer. You can name the file that contains the Setvars, Mygvars. It will contain the line: .style example setvar my_printer "biglaser" .style body The value of this variable may change during your session, but you may want to keep it for the next time that you log on. To do this, you must replace your normal logoff procedure (the Bye or Exit command) with a command file that saves the variable in a file, and then logs you off. .style example |1byebye| purge mygvars > $null file mygvars;save echo setvar my_printer "!my_printer" > *mygvars bye .style body Whenever you type |5byebye|, the Setvar command is written to Mygvars, and you are then logged off. The default close disposition of an I/O redirection file is TEMP, which is why you have to specify a file equation. Because you are never certain that this file exists beforehand, doing a Purge ensures that it does not. .page 3 |3Program Control - If/Else and While| One of the reasons I like programming the CI is its lack of |2goto's|. This much-debated programming style has sparked many a heated discussion. Consider the following: .style example echo Powers of 2... if "!hpjobname" = "KARNAK" then setvar loop_count 1 setvar temp 1 while !loop_count < 10 do setvar temp !temp * 2 echo 2^!loop_count = !temp setvar loop_count !loop_count + 1 endwhile else echo Sorry, you're not authorized to know. endif .style body The above command file will display a powers of two table from one though nine for the user who is logged on as KARNAK. It is good programming practice to indent code inside If and While blocks, because it makes debugging much easier. The CI doesn't care about leading spaces or blank lines, but you must use the Comment command to insert comments. There are times when you wish to get out of a loop in an ungraceful manner. To do this, use the Return command. I often use this when I display help in a command file, and I don't want to clutter further code with a big If@/@@Endif block. .style example parm hidden_away="?" if "!hidden_away" = "?" then echo This command file doesn't do much, echo but it does it well. return endif echo Local variable is !hidden_away. .style body Another way to terminate loops and nested command files is to use the Escape command. This command will blow you right back to the CI. (Using the Return command only exits the current command file.) You can optionally set the CIERROR JCW by adding a value to the end of the Escape command. .style example escape 007 .style body .page 5 |3Simulating Arrays| It's true - arrays are not directly supported in the CI. However, because of some undocumented techniques (read |2tricks|), you can simulate arrays. One question that may immediately pop into your head is "Why would I want to use arrays?" Arrays are useful for table driven events, such as returning days per month, sessions on ldevs, etc. .page 5 I won't keep you in suspense. Here's the core method: .style example setvar !variable_name!variable_index |2value| .style body By using the expression evaluation feature of the CI, you can have a changeable variable name in the Setvar command. |1CAVEAT USER:| This only works within command files!| If you try to do this interactively, the expression evaluation on the Setvar command is performed for the part of the command line |2after| the variable name. Within a command file, the entire line is evaluated before being passed to the CI for re-evaluation. In much the same way, you can use command files to change sequences of commands, such as creating self-modifying code. For example, .style example |1weirdcmd| setvar variable_command "setvar apple ""orange""" !variable_command .style body If you run the command file, and then display the contents of the variable, you will see: .style example weirdcmd |0{execute command file, above}| showvar apple |APPLE = orange| .style body To simulate arrays, you must assign one variable for each element. For example, you would assign months_12 for months(12). This variable can be either string or numeric, but keep in mind that string variables can be up to 256 characters long. Here are a few command files that allow you to manipulate arrays. .style example |1arraydef| parm array_name nbr_elements=0 initial_value=0 setvar array_index 0 while !array_index <= !nbr_elements do setvar !array_name!array_index !initial_value setvar array_index array_index + 1 endwhile .style body The command file Arraydef allocates variables for each element of the array that you need. The call sequence would be something like, .style example arraydef months_ 12 .style body Just as you put an index in parentheses, I like to put underbars at the end of array names so that they are more readable. You can use this command file to ensure that you have enough room in the CI data area to store your array. Under MPE/iX release 5.0, space for variables is limited to roughly 20K. The space used can be calculated by adding the number of characters in each variable name, plus 4 bytes per integer variable or the length of each string variable. For example, the integer variable "XYZZY" takes up 5 bytes for the name, and four more bytes for its integer value. When you run out of space, you get the following error from the CI: .style example Symbol table full: addition failed. To continue, delete some variables, or start a new session. (CIERR 8122) .style body At the 1995 IPROF conference, I asked the MPE roundtable how much symbol space was actually available, but no one could give a definitive answer. As a question to my question, the roundtable countered with "How many people have ever run out of symbol space?" Only one person put up his hand in a room of 200 or so programmers - me. Just wait until more people start using arrays. The following command file allows you to return the space used by your pseudo-array when you are finished with it. .style example |1arraydel parm array_name nbr_elements=0 setvar array_index 0 while !array_index < !nbr_elements do deletevar !array_name!array_index setvar array_index array_index + 1 endwhile .style body To demonstrate how arrays can be set (and values returned), the following two command files, Arrayset and Arrayget, use the expression evaluation feature of the CI to convert the element number to the actual variable name. Setvar is called to set either the value, or the name of the variable passed to the command file. .style example |1arrayset| parm array_name element_nbr=0 anyparm set_value setvar !array_name!element_nbr !setvalue .page 4 |1arrayget| parm destination_var array_name element_nbr=0 anyparm get_value setvar !destination_var !array_name!element_nbr .style body Here's a quick command file to show how you can use arrays in a month table. It uses the Input command to prompt the user for the number of days for each month. .style example setvar months_nbr 0 while !months_nbr < 12 do setvar months_nbr months_nbr + 1 input months_!months_nbr; && prompt="Enter the number of days in month !months_nbr: " endwhile deletevar months_nbr .style body .page 5 |3Calling All Functions!| The CI has a number of built-in functions that you can call from the CI commands If and Setvar. The purpose of these functions is to provide some higher programming features at the CI level. For example, when you accept user input, you may want to upshift the string to minimize your If logic. .style example input yes_no,"Please enter yes or no: " if ups("!yes_no") = "YES" then .style body Since people generally like to reply with one-character answers, you can add one more function to your command file. .style example input yes_no,"Please enter yes or no: " if lft(ups("!yes_no"),1) = "Y" then .style body In the above two examples, the |5ups()| function upshifts the argument string. |5lft()| returns the number of specified characters. The characters start at the leftmost portion of the string argument. If you look closely, you'll notice that there are no quotes around the inside function call, that is you do |1not| say, .style example if lft("ups("!yes_no")",1) = "Y" then .style body Because of CI expansion rules, you cannot use quotes when you embed a function within a function. If you think about it, the |5ups()| function in the above statement would never get resolved. If you play computer and expand the above statement, you get the following: .style example IF lft("ups("yes")",1) = "Y" THEN .style body which, when executed, produces the error, |5A string operator was expected but none was found. (CIERR 9755)| .page 8 Here are a couple of useful functions. You'll find the rest (as of MPE/iX 4.5) described at the end of this paper, or you can check Appendix B in Volume 2 of the HP's |2MPE/iX Commands Reference Manual|. .style example dwns(@) #15Shift string to lowercase finfo(@) #15Returns file info, such as eof, etc. rht(@) #15Returns rightmost count of characters str(@) #15Extract a portion from the middle of a string .style body .page 5 |3Popular Command Files| To finish off this paper, I've included some sample command files that not only illustrate previously discussed techniques, but also include a few new tricks. .page 4 |3Obtaining the Remote CIERROR| If you have multiple HP 3000's linked with NS services, then you will know exactly why you need this functionality. You cannot get the remote value of the CIERROR JCW using traditional methods. More importantly, you cannot easily detect when a remote command fails. For example, the following commands inside a job will |1not| work as you expect: .style example remote purge bigfile if cierror <> 0 then echo error! endif .style body In fact, the HP Commands reference manual states: .mar (l+4 r-4) .font 0 When used to invoke commands on remote systems the COMMAND or HPCICOMMAND intrinsics do not return a meaningful status code. For more information on calling intrinsics refer to the MPE/iX Intrinsics Reference Manual (32650-90028). .font 0 .mar .style body Clearly you must do a bit more work to get your desired results. I've created two command files that you will need called Remci.Cmd.Prod and Echoci.Cmd.Prod. .page 5 .style example |1remci.cmd.prod| remote echoci tempci remote purge tempci > $null remote save tempci dscopy tempci:remmach;tempci;rep continue tempci |1echoci.cmd.prod parm tempfile echo setjcw cierror !cierror > !tempfile .style body You will need to keep the Remci command file on the local machine, and the Echoci command file on the remote one. The first thing the Remci command file does is remotely invoke the Echoci command file. This inserts the line |5setjcw cierror [cierror value]| into the temporary file Tempci. Remci then pulls the file over to the local machine via Dscopy, and executes the file, setting the current |5cierror| to the value of the remote |5cierror|. You can now insert this command file call into your first jobstream and make it work. .style example remote purge bigfile remci if cierror <> 0 then echo error purging remote bigfile! endif .style body .page 4 |3Job Monitoring| In a perfect world, jobs would never fail, and no one would never mistype a job number when aborting a job. Since the world is far from perfect, it's nice to have something that helps monitor critical jobs. .style example |1checkjob| anyparm jobname purge sojfile >$null purge sojfile,temp >$null purge grepout,temp >$null purge grepout > $null showjob job=&@j;exec >sojfile save sojfile run grep.hpbin.sys;info="-ci '!jobname' SOJFILE" > grepout save grepout input nbr_found < grepout if !nbr_found = 0 then echo [esc]&dB!jobname is not running. setvar job_is_running FALSE else setvar job_is_running TRUE echo !jobname is in exec status. endif .style body A gotcha about |5grep|: |5grep| will only read |2permanent| files, and it doesn't pay attention to file equations. This powerful tool ported from UNIX is used to quickly find occurrences of strings within a file. The |2-c| option used above tells |5grep| to return only the count of strings found, while the |2i| indicates that the search shouldn't care about upper or lower case, (|1I|gnore case, I guess.) In our command file, the search file is in the MPE name space, and must appear in UPPER CASE. |3Displaying Active Jobs| At Robelle, our shop occasionally has two to three hundred jobs in a scheduled state. Typing Showjob produces pages of data that one doesn't necessarily wish to view. The following command file called Sja will show only the jobs that are in the EXEC state, and some statistics about jobs in other states. .page 5 .style example |1sja| echo JOBNUM STATE IPRI JIN JLIST INTRODUCED JOB NAME&&[esc]&&a65C[esc]&&dJ!hpsysname purge sojfile,temp >$null showjob job=&@j;exec >sojfile setvar lastline finfo("sojfile","eof") - 4 print sojfile;start=4;end=!lastline if !hpjoblimit < !hpjobcount then echo [esc]&&dA************ W A R N I N G *********** echo [esc]&&dBNumber of jobs executing exceeds JOBLIMIT echo [esc]&&dA************************************** elseif (!hpjobcount + 1) < !hpjoblimit then echo [esc]&&dA******* W A R N I N G **************** echo [esc]&&dB Joblimit will allow two jobs to run echo [esc]&&dB at the same time echo [esc]&&dA************************************** elseif !hpjoblimit = !hpjobcount then echo [esc]&&dH** WARNING: The JLIMIT queue is full. endif echo && [esc]&&dJ Sessions: !hpsescount && [esc]&&dBJobs EXEC: !hpjobcount (limit !hpjoblimit) && [esc]&&dB WAIT: !hpwaitjobs && [esc]&&dJ SCHED: !hpschedjobs [esc]&&d@&& [esc]&&dJ SUSP: !hpsuspjobs .style body The above command file doesn't have any sneaky tricks, but it does use I/O redirection and a file function call to determine the number of lines in the output of the Showjob;exec command. If you subtract four from the EOF, you get the number of jobs executing. Note that |5[esc]| indicates the escape character, ASCII 27. That is, |5[esc]&&dB| is the escape sequence to turn on inverse video. |3Creating a Job History| .com new KR Have you ever streamed a job, cleared your screen, then wished you could remember the job number that MPE assigned the stream? The following command file, Jstream, takes care of this requirement nicely. It streams the job passed to it, and maintains a history of stream events. It creates a new variable called |5jobhist_&#|, where &# is a unique job number. The jobname, current time, and job number are kept in this variable. The command file Jobhist provides a simple history viewer. .com endnew .style example .page |1jstream| anyparm stream_jobname = ? echo JSTREAM 1.2 Type JOBHIST to see your jobstream history. if finfo("!stream_jobname","exists") = FALSE then echo Yowza! I can't access !stream_jobname escape 52 return endif setjcw cierror 0 continue stream !stream_jobname > str52673 setvar temp_cierror !cierror if temp_cierror <> 0 then print str52673 endif input stream_out < str52673 purge str52673,temp > $null if lft(stream_out, 3) <> " #J" then escape !temp_cierror endif if bound(job_nbr_streamed) = FALSE then setvar job_nbr_streamed 0 endif setvar job_nbr_streamed !job_nbr_streamed + 1 if !job_nbr_streamed > 100 then setvar job_nbr_streamed 1 endif setvar jobhist_!job_nbr_streamed "!stream_out !hptimef !stream_jobname" echo Job !stream_out !hptimef !stream_jobname |1jobhist| showvar jobhist&@ > jobhistt print jobhistt .style body .page 4 |3Listing Spoolfiles| In order to see the output spoolfile from a job, you must first know its spoolfile number. This task is really a number of steps which are easily replaced by the following command file. .style example |1ljob| parm jnum="&@",listcommand="/ljq" if "!hpjobtype" <> "S" then echo I'm sorry, but LJOB can only be run from a session. return endif setvar ljob_jnum ups("!jnum")-"J"-"&#" if not numeric("!ljob_jnum") echo echo usage: LJOB job_number <,print command = '/LQJ'> echo echo eg. LJOB 1010,/lqj (use Qedit lqj command) echo LJOB 590,/lqj ]-50 (list last 50 lines) echo LJOB 42 return endif setjcw cierror = 0 continue spoolf o&@;seleq=[jobnum=#j!ljob_jnum and filedes="$STDLIST"] && ;show >ljobtmp if cierror <> 0 then setvar ljobtmp_eof 0 else setvar ljobtmp_eof finfo("ljobtmp","eof") endif if ljobtmp_eof < 4 then echo Spoolfile for job #&!ljob_jnum does not exist. else print ljobtmp;start=4;end=4 >ljobtmp2 input spooline < ljobtmp2 setvar spoolid str("!spooline",2,7) !listcommand !spoolid.out.hpspool endif .style body The default print command in the Ljob command file is |5/lqj|, which is a Qedit command to list the file without displaying line numbers, and to pause every 22 lines. If you do not have Qedit, you can set the default list command to "print". .page 4 |3CI Programming Inside Qedit| You can use all MPE/iX CI commands in Qedit. One of the neatest tricks you can try is to get a line of data from a Qedit file into a variable. You can do this with one of Qedit's undocumented features: put a colon in front of the "/" in a Qedit command, and then on the next line, use an Input command. For example, .style example comment This command file puts the data from the current comment line into the variable, myline /set list init off /:/listq * > tempfile /set list init on input myline < tempfile .style body As printing is by nature a complex operation, there is no simple way for Qedit to handle carriage control. Depending on the source file, it is possible that Qedit would decide to write an initial cctl record to the re-directed output file. This is an undesirable feature in most cases. To ensure that this does not occur, use the above undocumented Qedit command, |2set list init off|. After you are finished with your re-directions, you must remember to set list init on. This trick can really help you when you're adding custom features to Qedit. Use the following command file to find COBOL paragraph names without changing the current line pointer. I use this feature when I can't recall the exact name of the paragraph that I want to reference. .style example |1findpara| parm para_name left_window=7 :/ver zz > zztemp {save old zz in temp file} /zz */* {save the current line number} setvar g_para_name "!para_name" setvar g_left_window "!left_window" setvar right_window len("!g_para_name") + !g_left_window /list "!para_name" (!g_left_window/!right_window) :/list zz > $null {return to line number w/o seeing the line number /useq zztemp {restore old zz} .style body .break You can invoke Findpara at any time, but I usually use it while in Visual mode. Paragraph names must start in column eight for this command file to work properly. You'll notice that Findpara saves your old ZZ marker in a file, and restores the original after doing its work. Qedit doesn't have this feature. Hey! We just extended Qedit's command set. With a little bit of work, we could even have multiple bookmarks. I'll leave this as an exercise for the reader. |3Where to go from here...| No doubt you've often heard that the only way to learn something is to actually do it. This thought applies directly to learning to program the CI - you must jump in and get your feet wet. Start programming the CI today! A very well written HP manual, the |2MPE/iX Commands Reference Manual|, is available on CD-ROM and on paper. I recommend that you at least browse through it. Pay particular attention to Appendices A, B and C in Volume 2 which deal mostly with handling variables. You can get instant help on the subject of variables. As of MPE/iX 5.0, the on-line Help command |5help variables| provides pages of text. (In pre-5.0 environments, the on-line Help in MPEX from VESOFT will display approximately the same thing.) And of course, if you really get stuck, you always have the Internet, and the list-server HP3000-L. But that's a topic for another paper entirely. .style example while awake = TRUE program the_ci endwhile .style body .page |3Glossary| Some acronyms require no explanation; others are fairly obscure to the uninitiated. This short glossary explains some of the terms and acronyms specific to this paper. .mar (l+14) .par (s0+ u14) `3GL #+1Third Generation Programming Language. COBOL, FORTRAN and Pascal are 3GL's. `4GL #+1A programming language for managers; requires less knowledge about the operating system and/or hardware internals. `CI #+1Command Interpreter. This is the program that is run right after you log on. `MPE/V #+1Mostly harmless. Precursor to the MPE/iX operating system, run on overworked machines. `HP3000-L #+1A list-server available via the Internet. Lots of juicy tidbits can be gleaned from this list. `Hppath #+1A string variable used by the CI as a reference to determine where to look for programs and command files to execute. `Interpreted Code#+1Programs that are compiled "on the fly". Each statement in an interpreted program is evaluated every time it is executed. Interpreted code tends to be a lot slower to execute than compiled code. `IPROF #+1|1I|nterex |1Pro|grammers |1F|orum, held every spring at the HP Cupertino labs. `JCW #+1Job Control Word. An integer variable limited to the CM space. `Local Variable #+1A variable that can be referenced only within some "local" area, such as a command file. `Macro #+1A collection of commands designed to perform a task more efficiently. `Shell #+1A program, such as the CI, that is used as a working environment for launching processes, editing code, and maintaining a command interface. `UDC #+1User Defined Command. A file of callable commands with multiple entry points. See page 2 of this paper. `WYGIWYG#+1What You Got Is What You Get. Basically software without support. .mar.par .page .style body |3Addendum| Although this table appears in the |2MPE/iX Commands Reference Manual|, you may not always have access to it. I am inserting it here for convenience. .opt (f- l- r-) Expression Evaluator Functions .opt .opt (f-) .if not outhelpcomp .font 6 Symbol Function Example Result .box (h0) +(numeric) addition 4 + 5 9 +(string) concatenate "abc" + "de" abcde -(numeric) subtraction 12 - 6 6 -(string) deletion of first "abc" - "b" ac occurrence * multiplication 4 * 5 20 / integer division 79/ 10 7 ^ exponentiation (9) 2^3 8 either " or ' string identifier either "abc" or 'abc' abc () parentheses (3 + 4) * 2 14 < less than (1) 5 < 6 TRUE <= less than or equal "abc" <= "abc" TRUE (1) > greater than (1) "xyz" > "abc" TRUE >= greater than or "abc" >= "abc" TRUE equal (1) <> not equal 5 <> 6 TRUE = equal "xyz"= "xyz" TRUE ABS(integer) absolute value abs(-4) 4 ALPHA(string) check if a string is alpha('abcd') TRUE alphabetic alpha('ab3d ef') FALSE ALPHANUM(string) check if a string is alphanum('abCd') TRUE only alphabetics and alphanum('45abd') TRUE digits alphanum('3d ef') FALSE AND logical and 7=7 and 5=5 TRUE BAND bitwise and 7 band 13 5 BNOT bitwise not bnot 5 -6 BOR bitwise or 5 bor 2 7 BOUND(varname) variable bound(HPPATH) TRUE definition test (2) BXOR bitwise 7 bxor 5 2 exclusive or CHR(integer) ASCII value chr(65) A (integer) ===> character CSL circular shift -2 csl 2 -5 left (3) CSR circular shift -7 csr 1 -4 right (3) DWNS(string) shift string dwns('aBC&&#dE') abc&#de to lowercase (7) FINFO(filename,option) file FINFO('x.pub',0) TRUE information (6) HEX(integer) convert to hex(329) $149 hexadecimal string INPUT([prompt][,wait]) accept user input('Enter Enter choice: Y input (10) choice:',20) Return "Y" LEN(string) string length len("abc") 3 LFT(string, &# chars) left string lft('abc',2) ab extraction LSL logical shift 7 lsl 1 14 left LSR logical shift -7 lsr 1 2,147,483,644 right LTRIM(string[,trimstr]) trim left end 'X'+ltrim(' abc') Xabc of string (11) "X"+ltrim('...abc', Xabc '.') MAX(num1[,num2...]) find largest max(5,4-3,70,0) 70 of several integers MIN(num1[,num2...]) find smallest min(5,4,-3,70,0) -3 of several integers MOD modulo (4) 25 mod 2 1 NOT logical not not(2>1) FALSE NUMERIC (string) check if a numeric('12345') TRUE string is all numeric('$a234ef') FALSE digits OCTAL(integer) convert to octal(329) %511 octal string ODD(integer) determine if odd(233) TRUE integer is odd odd(-2) FALSE OR logical or 5=5 or 2=3 TRUE ORD(string) ordinal (8) ord('AbcD') 65 POS(find str,source find Nth pos('ab','cgabd') 3 str[,n]) occurrence of pos('.','file.grp.acct ',2) 9 find str in pos('.','file.grp.acct ',- 9 source str (-N 1) searches from right) (12) RHT(string, &# chars) right string rht("abc",2) bc extraction RPT(string,count) repeat a rpt('aBc',3) aBcaBcaBc string (-count rpt('aBc',-3) cBacBacBa reverses string) RTRIM(string[,trimstr]) trim right end rtrim('abc ')+'X' abcX of string (11) rtrim('abc...','.')+"X " abc X SETVAR(varname,expr) return result setvar(myvar,2*3+5) sets variable @@@@@@@@@@@@@@@@@@@@ of expr and @@@@@@@@@@@@@@@@@@@ myvar to 11 and @@@@@@@@@@@@@@@@@@@@ set varname to @@@@@@@@@@@@@@@@@@@ returns 11 @@@@@@@@@@@@@@@@@@@@ result (13) @@@@@@@@@@@@@@@@@@@ STR(string,start pos, &# general string str('abcde',2,3) bcd chars) extraction TYPEOF(expression) type of typeof(HPPATH) 2 (string) variable or expression (5) UPS(string) shift string ups('aBc5d') ABC5D to uppercase (7) XOR logical 7=7 xor 5=5 TRUE exclusive or .else Symbol Function Example Result +(numeric) addition 4 + 5 9 +(string) concatenate "abc" + "de" abcde -(numeric) subtraction 12 - 6 6 -(string) deletion of first "abc" - "b" ac occurrence * multiplication 4 * 5 20 / integer division 79/ 10 7 ^ exponentiation (9) 2^3 8 either " or ' string identifier either "abc" or 'abc' abc () parentheses (3 + 4) * 2 14 < less than (1) 5 < 6 TRUE <= less than or equal "abc" <= "abc" TRUE (1) > greater than (1) "xyz" > "abc" TRUE >= greater than or "abc" >= "abc" TRUE equal (1) <> not equal 5 <> 6 TRUE = equal "xyz"= "xyz" TRUE ABS(integer) absolute value abs(-4) 4 ALPHA(string) check if a string is alpha('abcd') TRUE alphabetic alpha('ab3d ef') FALSE ALPHANUM(string) check if a string is alphanum('abCd') TRUE only alphabetics and alphanum('45abd') TRUE digits alphanum('3d ef') FALSE AND logical and 7=7 and 5=5 TRUE BAND bitwise and 7 band 13 5 BNOT bitwise not bnot 5 -6 BOR bitwise or 5 bor 2 7 BOUND(varname) variable bound(HPPATH) TRUE definition test (2) BXOR bitwise 7 bxor 5 2 exclusive or CHR(integer) ASCII value chr(65) A (integer) ===> character CSL circular shift -2 csl 2 -5 left (3) CSR circular shift -7 csr 1 -4 right (3) DWNS(string) shift string dwns('aBC&&#dE') abc&#de to lowercase FINFO(filename file FINFO('x.pub',0) TRUE ,option) information HEX(integer) convert to hex(329) $149 hexadecimal string INPUT([prompt] accept user input('Enter Enter choice: Y [,wait]) input (10) choice:',20) Return "Y" LEN(string) string length len("abc") 3 LFT(string, left string lft('abc',2) ab &# chars) extraction LSL logical shift 7 lsl 1 14 left LSR logical shift -7 lsr 1 2,147,483,644 right LTRIM(string trim left end 'X'+ltrim(' abc') Xabc [,trimstr]) of string (11) "X"+ltrim('...abc', Xabc '.') MAX(num1[,num2...]) ind largest max(5,4-3,70,0) 70 of several integers MIN(num1[,num2...]) find smallest min(5,4,-3,70,0) -3 of several integers MOD modulo (4) 25 mod 2 1 NOT logical not not(2>1) FALSE NUMERIC (string) check if a numeric('12345') TRUE string is all numeric('$a234ef') FALSE digits OCTAL(integer) convert to octal(329) %511 octal string ODD(integer) determine if odd(233) TRUE integer is odd odd(-2) FALSE OR logical or 5=5 or 2=3 TRUE ORD(string) ordinal (8) ord('AbcD') 65 POS(find str,source find Nth pos('ab','cgabd') 3 str[,n]) occurrence of pos('.','file.grp.acct',2) 9 find str in pos('.','file.grp.acct',- 9 source str (-N 1) searches from right) (12) RHT(string, &# chars) right string rht("abc",2) bc extraction RPT(string,count) repeat a rpt('aBc',3) aBcaBcaBc string (-count rpt('aBc',-3) cBacBacBa reverses string) RTRIM(string trim right end rtrim('abc ')+'X' abcX [,trimstr]) of string (11) rtrim('abc...','.')+"X " abc X SETVAR(varname,expr) return result setvar(myvar,2*3+5) sets variable of expr and myvar to 11 and set varname to returns 11 result (13) STR(string,start general string str('abcde',2,3) bcd pos, &# chars) extraction TYPEOF(expression) type of typeof(HPPATH) 2 (string) variable or expression (5) UPS(string) shift string ups('aBc5d') ABC5D to uppercase (7) XOR logical 7=7 xor 5=5 TRUE exclusive or .endif .mar