Write Your Own QShell Utilities

Article ID: 54053

Over the past three years, I've posted several tips about how to use the utilities in QShell, and I've also had countless discussions in e-mail, on mailing lists, and in forums about how useful QShell's utilities can be.

However, did you know that you can write your own utilities for QShell? The tools that you see in QShell are nothing more than ordinary i5/OS programs. You can write your own tools for QShell in any ILE language. In this article, I demonstrate how to write a QShell utility in RPG.

QShell Utilities Are *PGM Objects

When you type a command at a QShell prompt, the command name is actually just a program name. When you press Enter, it searches your PATH for a program with that name and runs it. Each word that you list on the command line, separated by spaces, is treated as a separate parameter that's passed to that program.

For example, QShell contains a command named ls that lists the files in a given IFS directory, analogous to the MS-DOS dir command. To get a list of the objects in the /QIBM/UserData/OS400 directory, you first start an interactive QShell prompt by typing STRQSH, and then you type the following command:

ls -l /QIBM/UserData/OS400

What happened when you typed this? The system first searched your PATH to find the ls program. A PATH is similar to a library list. It's a list of IFS directories that the system searches when it's looking for a program. From QShell, you can display your current path by typing the following command:

echo $PATH

By default, the PATH contains only one directory, /usr/bin. That means that to be found, the ls program must be in the /usr/bin directory of the IFS. To see the location of the ls program, type this:

ls -l /usr/bin/ls

On my system, the output from the preceding command looks like this:

lrwxrwxrwx  1 QSYS  0                    27 Mar 27  2001 /usr/bin/ls -> /QSYS
.LIB/QSHELL.LIB/LS.PGM                                                       

The -> characters shown here refer to a symbolic link, which is a special object used to "redirect" from one object to another. In this example, the ls program in /usr/bin is redirected to a program named LS in the QSHELL library. Indeed, I can use the following command to see that this is the case:

WRKOBJ QSHELL/LS

Here's the output of the preceding command when I run it on my system:

                               Work with Objects                                
 Type options, press Enter.                                                     
   2=Edit authority        3=Copy   4=Delete   5=Display authority   7=Rename   
   8=Display description   13=Change description                                
 Opt  Object      Type      Library     Attribute   Text                        
      LS          *PGM      QSHELL      CPPLE                                   
                                                                         Bottom 
 Parameters for options 5, 7 and 13 or command                                  
 ===>                                                                           
 F3=Exit   F4=Prompt   F5=Refresh   F9=Retrieve   F11=Display names and types   
 F12=Cancel   F16=Repeat position to   F17=Position to

When QShell calls this *PGM object, it passes parameters for each option that I specified on the command line. In this example, it passes -l for the first parameter and /QIBM/UserData for the second. In essence, it's equivalent to typing the following command at a traditional command prompt:

CALL PGM(QSHELL/LS) PARM('-l' '/QIBM/UserData')

However, I do not recommend invoking the QShell tools this way. They are designed to run in QShell, not to be called directly, and they might rely on some features of QShell's environment to work properly, so even though it's possible to call them this way, I urge you to always call them through QShell.

As you can see, LS is an ordinary *PGM object in an ordinary library on my system. If I want to write my own utilities for QShell, all I have to do is write a program (as described next) and then insert a symbolic link into a directory in my PATH. Pretty cool, eh?

The Unix Utility Paradigm

Unix programmers are taught not to write a single utility that does everything. Instead, they try to write small, simple programs that do one specific thing very well. Each tool can be integrated with other tools to accomplish specific tasks and provide a great deal of versatility.

To let these tools easily integrate with one another, the tools never attempt to read directly from the keyboard or write directly to the screen. Instead, input is read from a special data stream known as "standard input," or STDIN for short. Output is written to a stream named "standard output" (STDOUT). An additional output stream called "standard error" (STDERR) is used for error messages that aren't part of the normal output of a program. That way, error messages can be kept separate from normal output.

Any of these streams can be redirected. For example, if you want to automate a program, you can redirect STDIN so that it reads its input from a file instead of the keyboard. Likewise, if you want to save the output of a program, you can redirect STDOUT to a file.

In addition to files, you can also use something called a pipe. A pipe redirects the STDOUT of one program to the STDIN of another program. This lets you combine utilities to use them together. For example, consider the following QShell command line:

ls /qibm/userdata | sort

In this example, the ls program lists the files in the /qibm/userdata directory, and the list of files is written to its STDOUT. The pipe (which looks like a vertical bar) in the preceding command line tells QShell to redirect the output from the ls program so that it's the input to the sort program. Sort is another IBM-supplied QShell utility, and it sorts the list of files for me.

Granted, when IBM wrote the ls program, it could've opted to provide a built-in sort capability instead of making it a separate program. However, that'd be less versatile. Because sort is separate from ls, it can be used to sort the output of any QShell command, and the other QShell commands are thereby freed from needing to provide a sort capability.

Another facet of the Unix paradigm is that programs return an exit status code indicating whether they've succeeded or failed. By convention, a utility that completes successfully returns an exit status of 0. If the command fails, it returns a positive number for the exit status. Unfortunately, the values of these positive numbers haven't been standardized, so each programmer tends to use different values to mean different things.

In QShell, you can see the exit status code for the last command that you ran by typing the following command:

echo $?

Here's a screen shot that helps illustrate how that works:

                               QSH Command Entry                                
 > ls /dir/doesnt/exist                                                         
   ls: 001-2113 Error found getting information for object /dir/doesnt/exist. No
    such path or directory.                                                     
   $                                                                            
 > echo $?                                                                      
   1                                                                            
   $                                                                            
 > ls /qibm                                                                     
   ProdData        XML             locales                                      
   UserData        include                                                      
   $                                                                            
 > echo $?                                                                      
   0                                                                            
   $                                                                            
 ===>                                                                           
 F3=Exit   F6=Print F9=Retrieve F12=Disconnect                                  
 F13=Clear F17=Top  F18=Bottom  F21=CL command entry

When you write your own utilities for QShell, you should follow the Unix paradigm that I've outlined here. You read your input from STDIN, and you write your output to STDOUT and your error messages to STDERR. If the program succeeds, it should set the exit code to 0, otherwise it should set the exit code to a positive number. The next section discusses how to do these things in RPG.

Writing Code That Uses STDIN, STDOUT, and STDERR in ILE RPG

If you've ever written a CGI program, STDIN and STDOUT probably sound familiar to you. That's because CGI was originally developed on Unix systems, where these data streams are the standard way of doing things. As any CGI programmer can tell you, the HTTP server comes with a Read Standard Input (QtmhRdStin) API and a Write Standard Output (QtmhWrStout) API. These APIs work with QShell as well! You can read STDIN and write STDOUT with these APIs, just as you would write a CGI program. Except, of course, the input won't be URL encoded, because it didn't come from a browser, and the output probably shouldn't be HTML, because it's not going to a browser.

Having said that, I dislike the CGI APIs. For one thing, they have an awkward, hard-to-read, syntax. For another, they require that you have the HTTP server installed, and not all systems with QShell will have that installed.

A better alternative is the APIs included in the ILE C runtime library. Specifically, the fgets() API reads a string from STDIN, and the fputs() API writes a string to STDOUT or STDERR. I find these APIs easier to use than the HTTP ones, and in fact, I prefer to use them for CGI programs as well.

To use the ILE C APIs from RPG, you need only bind to the QC2LE binding directory and have the appropriate prototypes defined to call the APIs. Here are the relevant definitions:

     H BNDDIR('QC2LE')

     D stdin           s               *   import('_C_IFS_stdin')
     D stdout          s               *   import('_C_IFS_stdout')
     D stderr          s               *   import('_C_IFS_stderr')

     D fgets           PR              *   ExtProc('_C_IFS_fgets')
     D   string                   65535a   options(*varsize)
     D   size                        10I 0 value
     D   stream                        *   value
     D fputs           PR            10I 0 ExtProc('_C_IFS_fputs')
     D   string                        *   value options(*string)
     D   stream                        *   value

Basically, if you want to write text to the screen, it's just a matter of passing a string containing the appropriate text to the fputs() API and telling fputs() that you want to write to STDOUT.

     D LF              C                   x'25'
     D msg             s             80a   varying

      /free

          msg = 'Hello world!' + LF;
          fputs(msg: stdout);

The LF constant in the preceding code is for the linefeed character. This tells QShell that you're done with the current line of text so that whatever you write next gets placed on a different line.

The STDOUT stream is an ongoing stream. You can continue to write more and more data to it if you like. Each time you write to it, it adds on to the end of what you wrote previously, all as one big long string of data.

For example, the following code writes the text "Hello world!" just as the previous example did. The following code will all appear as one line on the screen, because a line of text doesn't end until the LF character is sent.

          fputs('Hello ': stdout);
          fputs('world' : stdout);
          fputs('!'     : stdout);
          fputs(LF      : stdout);

Now, of course, you probably wouldn't want to write your code this way, because it's difficult to follow! I coded it like this only to illustrate the concept so that you'd understand how a stream works.

As you can see, writing output to STDOUT is pretty easy. In fact, when I want to write a quick and dirty program, such as a one-off program, I often use QShell's input and output instead of creating a display file. It's just simpler.

Retrieving input is nearly as easy. The only thing that makes it a little trickier is that the input is a null-terminated string like those used in ILE C instead of a fixed-length or VARYING string like we typically use in RPG. So you have to use the %str() built-in function (BIF) to convert it.

One more problem: Because the user presses Enter after typing the input, the string will contain an LF character at the end to symbolize the end of the line. If you use a VARYING string, you can chop this LF character off simply by subtracting one from the length of the string. Essentially, subtracting one from the length causes the last character in the string to be discarded.

      .
      .
     D LF              C                   x'25'
     D msg             s             80a   varying
     D inpbuf          s             80a
     D sz              s               *
     D name            s             80a   varying

      /free

          // Ask user for his/her name:

          msg = 'Please enter your name:' + LF;
          fputs(msg: stdout);

          //  Wait for input

          sz = fgets(inpbuf: %size(inpbuf): stdin);

          // convert to RPG-style string:

          name = %str(sz);
          %len(name) = %len(name) - 1;
          // Say hello to the user.

          msg = 'Hello ' + name + ', nice to meet you!' + LF;
          fputs(msg: stdout);
          .
          .

The fputs() API saves the null-terminated (C-style) string to the variable that you pass in the first parameter. It limits the length of the input to the length that you specify in the second parameter, and of course, the stream to read from is specified in the third parameter. It's a good idea to use the %size() BIF for the second parameter to make sure that the user can't provide more input than your variable can hold (which could cause the dreaded "unpredictable results").

Thanks to %size(), the fgets() API doesn't try to read more data than can fit in my inpbuf variable. However, the number of characters that the user can type is unrestricted. %size() restricts only the number of characters that I retrieve with fgets(). If the user types more data than I retrieve with this call to fgets(), QShell saves the data, and I retrieve it on subsequent calls to the fgets() API.

If the fgets() API is successful, it returns a pointer to the input buffer. You can pass that pointer to the %str() BIF to convert the null-terminated string to an RPG-style string, as I did in the preceding code. If the fgets() API encounters an error, it returns *NULL instead of a valid pointer.

Writing to STDERR is no different than writing to STDOUT, except, of course, that you specify STDERR on the fputs() API. Here's a code excerpt that prints an error message to STDERR when fgets() returns null.

         sz = fgets(inpbuf: %size(inpbuf): stdin);
         if (sz = *null);
            fputs('Error calling fgets() API!'+LF: stderr);
            return;
         endif;

Of course, if the user pressed Enter by itself, and not actually key a name, that'd probably be a "wrong answer." So I might as well check for that right away.

          if (sz <> *null and %len(%str(sz))>1);
               name = %str(sz);
               %len(name) = %len(name)-1;
               msg = 'Hello ' + name + ', nice to meet you!' + LF;
               fputs(msg: stdout);
          else;
               msg = 'Invalid name. Seek professional help.' + LF;
               fputs(msg: stderr);
          endif;

Setting the Exit Status Code

On a Unix system, the exit status is the return code of the program itself, typically set by calling the exit() API. It appears that on i5/OS, the exit() API actually ends the activation group and is really just an alias for the CEETREC() API. This means that to set an exit status, you have to end the activation group. The easiest way to do that is to code the program with ACTGRP(*NEW). For example:

     H DFTACTGRP(*NO) ACTGRP(*NEW) BNDDIR('QC2LE')
         .
         .
     D CEETREC         PR
     D   rc                          10I 0 const options(*omit)
     D   user_rc                     10I 0 const options(*omit)
         .
         .
          sz = fgets(inpbuf: %size(inpbuf): stdin);
          if (sz <> *null and %len(%str(sz))>1);
               name = %str(sz);
               %len(name) = %len(name)-1;
               msg = 'Hello ' + name + ', nice to meet you!' + LF;
               fputs(msg: stdout);
               CEETREC(*omit: 0);
          else;
               msg = 'Invalid name. Seek professional help.' + LF;
               fputs(msg: stderr);
               CEETREC(*omit: 1);
          endif;

In the preceding example, I set the return value to 0 when the user enters something for the name, and I set it to 1 when the user just presses Enter without typing anything. Now a QShell script can look for that failure code and handle it properly.

Running the RPG Code from QShell

After you've written some code using the techniques outlined here, you can run it from QShell by typing its IFS path name. For example, if your program is called HELLO and you compile it into a library named UTILS, you'd run it by typing the following QShell command:

/QSYS.LIB/UTILS.LIB/HELLO.PGM

Here's a screen shot:

                               QSH Command Entry                                
   $                                                                            
 > /qsys.lib/utils.lib/hello.pgm                                               
   Hello there!                                                                 
   Please enter your name:                                                      
 > Mr. Scott Klement                                                            
   Hello Mr. Scott Klement, nice to meet you!                                   
   $                                                                            
 ===>                                                                           
 F3=Exit   F6=Print F9=Retrieve F12=Disconnect                                  
 F13=Clear F17=Top  F18=Bottom  F21=CL command entry                            

Fini

I've run out of space for this week . . . Darn! I was having so much fun, too! There are a bunch of other details that I didn't have space to cover, but I think the gist of it is pretty clear: You can write your own utilities for QShell in RPG using the preceding techniques.

Now think about how you might want to use this ability. For example, you might convert one of your report programs to print to QShell instead of to the spool. Then the user could run the report and send the output to the grep tool to search for a string, or to the sort tool to sort the report by a given column, or to QShell's Rfile tool to print the report. Or maybe you'd have a second tool that converts the output to HTML so it could be displayed in a browser, or a third tool that takes the HTML and e-mails it to a user. The sky, as they say, is the limit.

To download the HELLO program that I demonstrated in this article, use the following link:
http://www.pentontech.com/IBMContent/Documents/article/54053_166_QShellHello.zip

ProVIP Sponsors

ProVIP Sponsors