PAPER NUMBER 4018 Programming the Command Interpreter An Introduction, a Dog, and New Tricks by Ken Robertson May 1995 Robelle Consulting 15399-102A Avenue Surrey, BC Canada V3R 7K1 (604) 582-1700 Introduction An overworked, understaffed data processing department is all too common in today's ever belt-tightening, down-sizing 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. A 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. 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 (Command Interpreter) was ported to MPE/iX machines, and beefed up so it would be usable as a run-time shell. The MPE/iX command interpreter has a generous command set, pushing 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. Its code, however, is interpreted, which may cause a CI solution to a problem to execute too slowly for practical purposes. In this case a 3GL, 4GL, or third party software solution would would have to be investigated. What 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 in the CI interactively, you can do with command files, and then some. You can use command files in situations that call for repetitive functions, such as re-compiling 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 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 convention commonly used is to create 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. *********************** logonudc option logon, nobreak logon.cmd.prod bye The above UDC is invoked at logon, and executes the command file called logon.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 OPTION NOHELP to prevent snooping eyes from reading the contents of a particular user command. [Note: A future operating system release will allow XEQ access for command files. Currently, you must have XEQ and READ access to the files in order to use them.] The Command File Structure The structure of a command file is as follows: PARM parameter declarations (optional) MPE command . . . MPE command 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. (Note to Qedit users: Qedit has a maximum recursive level of 10, which should be enough.) The 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, keep and test your command files, saving 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 sj. echo echo All executing jobs: > sj echo showjob;exec >> sj 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: anyparm search_string = "?" if "!search_string" = "?" then echo command file: phone search_string return endif run grep.bin.hpsys "-c !search_string" How can you echo variables into a file without getting the assigned value of the variable? Say, for example the value of the variable my_time was equal to "10:02". The statement echo echo The time is !mytime > cmdfile 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: echo echo The time is !!mytime > cmdfile That would write the string "echo The time is !my_time" into the first record of Cmdfile. Variables 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 256 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, and the name must start with either a letter or an underbar. Setting and Displaying Variables You have two methods for setting variables: use either the typical assignment command, or prompt a value from a terminal. 1. setvar variable_name expression 2. input variable_name prompt The expressions for Setvar can be made up of either numerics, strings, booleans, variable names, or variable de-referencing (sort of like pointers), all glued together with operators. For example, to concatenate the values of two string variables, you can do the following: setvar group_account "!hpgroup" + "." + "!hpaccount" If the logon group and account were PUB and SYS, the above would set the variable group_account to the value of PUB.SYS. Figure 1 shows a list of all of the operators possible in a Setvar command. Logical operators: AND, OR, XOR, NOT Boolean functions and values: BOUND, TRUE, FALSE, ALPHA, ALPHANUM, NUMERIC, ODD Comparison operators: =, <>, <, >, <=, >= Bit manipulation operators: LSL, LSR, CSR, CSL, BAND, BOR, BXOR, BNOT Arithmetic operators: MOD, ABS, * , / , + , -, ^ (exponentiation) Functions returning strings: CHR, DWNS, UPS, HEX, OCTAL, INPUT, LFT, RHT, RPT, LTRIM, RTRIM, STR Functions returning integers: ABS, LEN, MAX, MIN, ORD, POS, TYPEOF Other functions: FINFO, SETVAR figure 1. Setvar Operators Variable 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 uppercase. To see a list of them, simply enter the command showvar hp@ 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, parm local_myvar="Not Available In Stores" echo !local_myvar If after executing the above command file, you do a showvar local_myvar, the variable will not exist, and a CI error will grace your screen. The temporary variable my_var existed only during the execution of the command file. Local variables cannot be modified, and are accessed only 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. hithere parm hpjobname="ZZTOP" echo Good evening, !hpjobname. showvar hpjobjname Exexcuting Hithere gives us Good evening, ZZTOP HPJOBNAME = KEN 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 only within the command file that defines it. Getting 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 re-direction and the print command. I/O re-direction allows input or output to be directed to a file instead of to your terminal. I/O re-direction uses the symbols ">", ">>" and "<". Use ">" to re-direct 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. 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. After executing the above command file, the file Myfile will contain two lines, "Value 42" and "This is the second line". (Without quotes, of course.) The Input command uses I/O re-direction to read the first record of the file, and assigns the value to the variable my_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 consequent lines of a file to variables? You use the Print command to select the record that you want from the file, sending the output to a new file. print myfile;start=2;end=2 > myfile2 You can then use the Input command to extract the string from the second file. Rolling 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: setvar my_printer "biglaser" 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. byebye purge mygvars > $null file mygvars;save echo setvar my_printer "!my_printer" > *mygvars bye Whenever you type byebye, the setvar command is written to Mygvars and you are then logged off. The default close disposition of an I/O re-direction 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. Program Control - If/Else and While One of the reasons I like programming the CI is its lack of goto's. This much-debated programming style has sparked many a heated discussion. Consider the following: 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 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 exit 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. 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. 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. escape 007 Simulating Arrays It's true - arrays are not directly supported in the CI. However, because of some undocumented techniques (read: tricks), 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. I won't keep you in suspense. Here's the core method: setvar !variable_name!variable_index value By using the expression evaluation feature of the CI, you can have a changeable variable name in the Setvar command. CAVEAT 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 after 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, i.e., to create self-modifying code. For example, weirdcmd setvar variable_command "setvar apple ""orange""" !variable_command If you run the command file, and then display the contents of the variable, you will see weirdcmd {execute command file, above} showvar apple APPLE = orange To simulate arrays, you must assign a variable per 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. arraydef 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 The command file Arraydef allocates variables for each element of the array that you need. The call sequence would be something like arraydef months_ 12 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 bytes. The space used can be calculated by adding the number of characters in each variable name, plus 4 bytes per integer item, or the length of each string. 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: Symbol table full: addition failed. To continue, delete some variables, or start a new session. (CIERR 8122) 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 using it. arraydel 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 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. arrayset parm array_name element_nbr=0 anyparm set_value setvar !array_name!element_nbr !setvalue arrayget parm destination_var array_name element_nbr=0 anyparm get_value setvar !destination_var !array_name!element_nbr 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. 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 Calling 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. input yes_no,"Please enter yes or no: " if ups("!yes_no") = "YES" then Since people generally like to reply with one-character answers, you can add one more function to your command file. input yes_no,"Please enter yes or no: " if lft(ups("!yes_no"),1) = "Y" then In the above two examples, the ups() function upshifts the argument string. lft() returns the number of characters specified starting 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 not say if lft("ups("!yes_no")",1) = "Y" then Because of CI expansion rules, you cannot use quotes when you embed a function within a function. If you think about it, the ups() function in the above statement would never get resolved. If you play computer and expand the above statement, you get the following: IF lft("ups("yes")",1) = "Y" THEN which, when executed, produces the error, A string operator was expected but none was found. (CIERR 9755) 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 Two of the HP manual MPE/iX Commands. dwns( ) Shift string to lowercase finfo( ) Returns file info, such as eof, etc. rht( ) Returns rightmost count of characters str( ) Extract a portion from the middle of a string Popular 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. Obtaining 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 not work as you expect: remote purge bigfile if cierror <> 0 then echo error! endif In fact, the HP Commands reference manual states: 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). 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. remci.cmd.prod remote echoci tempci remote purge tempci > $null remote save tempci dscopy tempci:remmach;tempci;rep continue tempci echoci.cmd.prod parm tempfile echo setjcw cierror !cierror > !tempfile 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 that the Remci command file does is to remotely invoke the Echoci command file. This inserts the line "setjcw 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 cierror to the value of the remote cierror. You can now insert this command file call into your first jobstream and make it work. remote purge bigfile remci if cierror <> 0 then echo error purging remote bigfile! endif Job 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. checkjob 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 A gotcha about grep: grep will only read permanent 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 -c option used above tells grep to return only the count of strings found, while the i indicates that the search shouldn't care about upper or lower-case. (Ignore case, I guess.) In our command file, the search file is in the MPE name space, and must appear in UPPER CASE. Displaying 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. sja echo JOBNUM STATE IPRI JIN JLIST INTRODUCED JOB NAME&[esc]&a65C[esc]&d!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 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 [esc] indicates the escape character, ascii 27. That is, [esc]&dB is the escape sequence to turn on inverse video. Creating a Job History Have you ever streamed a job, cleared your screen, then wished you had remembered 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 jobhist_#, 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. jstream anyparm stream_jobname = ? echo JSTREAM 1.2 Type JOBHIST to see your jobstream history. if finfo("!stream_jobname",0) = 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 Jobhist showvar jobhist > jobhistt print jobhistt Listing 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. ljob 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 endif setvar ljobtmp_eof finfo("ljobtmp","eof") 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 The default print command in the Ljob command file is /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". CI 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, comment This command file puts the data from the current line comment into the variable, myline /listq * /:/listq * > tempfile input myline < tempfile Qedit versions 4.3 to 4.16 are inconsistent when you do a listq * > Tempfile first without doing a listq *. Tempfile may or may not contain two lines, with a blank first line. Doing a listq * guarantees that tempfile will contain only the current line of data, and no preceeding blank line. 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. findpara 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} 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. Where 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 MPE/iX Command 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 Two, which deal mostly with handling variables. You can get instant help on the subject of variables. As of MPE/iX 5.0 the online Help command, help variables, will output pages of text. (In pre-5.0 environments, the online 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. while awake = TRUE program the_ci endwhile Glossary 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. 3GL Third Generation Programming Language. COBOL, FORTRAN and Pascal are 3GL's. 4GL A programming language for managers - requires less knowledge about the operating system and/or hardware internals. CI Command Interpreter. This is the program that is run right after you log on. MPE/V Mostly harmless. Pre-cursor to the MPE/iX operating system, run on overworked machines. HP3000-L A list-server available via the Internet. Lots of juicy tidbits can be gleaned from this list. HPPATH A string variable used by the CI as a reference to determine where to look for programs and command files to execute. Interpreted CodePrograms 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 Interex Programmers Forum, held every spring at the HP Cupertino labs. JCW Job Control Word. A integer variable limited to the CM space. Local Variable A variable that can be referenced only within some "local" area, such as a command file. Macro A collection of commands designed to perform a task more efficiently. Shell A program, such as the CI, that is used as a working environment for launching processes, editing code, and maintaining a command interface. UDC User Defined Command. See page 2 for a description. WYGIWYG What You Got Is What You Get. Basically software without support. Addendum Although this table appears in the Commands Reference manual, its not always convenient to browse through such a tome. I am inserting it here for convenience. Expression Evaluator Functions 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