Writing Programs for Qshell

Overview

If you have a specific goal you want to accomplish within Qshell, the first thing you should do is look for a utility or sequence of utilities that will serve your purpose. If no utility is available, you should write a Qshell script. If the Qshell language cannot do what you need, you'll have to write a program.

Although any program can run under Qshell, a program must do the following four tasks to be useful from within shell scripts and consistent with other Qshell utilities:

  1. Read from the standard input device (stdin) .
  2. Write to the standard output device ( stdout ) .
  3. Write to the standard error device (stderr) .
  4. Return an exit status to Qshell.

Writing programs that can operate under Qshell is not difficult at all. It is especially easy in C, C++, and Java, because those languages have functions to handle standard input and output. However, it is not difficult in RPG or COBOL, either.

This chapter explains how to write programs that can run under Qshell.

Running Programs Within Qshell

Qshell doesn't use the call command to start a program. Instead, you must type the program's name , in IFS style. The following example illustrates how a program can run within Qshell:

/qsys.lib/mylib.lib/myshellpgm.pgm

Program MYSHELLPGM is stored in library MYLIB. To run the program within Qshell, enter the program's name in IFS format.

A second example is given in Figure 19.1. Note that the name of the program is given in IFS notation.

ls -l t[h-k]* /qsys.lib/jsmith.lib/reversestr.pgm tacgsmtloht 2002 92 rpA 806 0 HTIMSJ 1 ---xwrxwr- fig.pu-bmuht 2002 81 tcO 773 0 HTIMSJ 1 ---xwrxwr- niedlit 2002 02 peS 54 0 HTIMSJ 1 ----wr-wr- tuoedlit 2002 02 peS 54 0 HTIMSJ 1 ----wr-wr- ttaywedlit 2002 72 peS 824 0 HTIMSJ 1 ----wr-wr- soommtrkopyzzjmkt 2002 31 peS 6 0 HTIMSJ 1 -wr-wr-wr- /home/JSMITH $

Figure 19.1: Program REVERSESTR in library JSMITH reads the output of the ls command, reverses each line, and writes to stdout .

Perhaps a better method for running programs is by means of symbolic links. You can create a symbolic link either by using CL's Add Link command (ADDLNK) or by using Qshell's ln command.

The following example shows how to create a symbolic link from CL:

ADDLNK OBJ('/qsys.lib/jsmith.lib/reversestr.pgm') + NEWLNK('/home/jsmith/revstr') + LNKTYPE(*SYMBOLIC)

The ADDLNK command creates a symbolic link called revstr , which points to program REVERSESTR in library JSMITH.

To create a symbolic link within Qshell, use the ln utility with the -s option, as shown here:

ln -s /qsys.lib/jsmith.lib/reversestr.pgm revstr

Regardless of which method you use, the symbolic link is created in the IFS and points to a program in the library file system.

Qshell's ls (List Directory Contents) command can indicate symbolic links in several ways. The l (letter "ell") option indicates symbolic links in two ways:

In the following example, the fact that mylnk is a symbolic link is indicated by the letter l in the first position of the permissions, and also by the name of the link followed by an arrow and the name of the program:

ls -l revs* lrwxrwxrwx 1 SMITH 0 35 May 4 12:52 blnk -> /qsys.lib/s.lib/bpgm.pgm /home/jsmith $

The blnk in the example is an alternate name for program BPGM, in library S.

The F option of the ls command indicates a symbolic link by affixing an "at" symbol (@) to the end of the name of a symbolic link. This option also appends an asterisk (*) to the names of executable files and a slash (/) to the names of directories. Figure 19.2 provides an example of this option. The file revstr is the only symbolic link in this directory. The ls utility indicates this fact by adding the "at" sign to the end of the file name. The file isempty.qsh is the only executable file, and temp is the only directory.

ls -F isempty.qsh* temp/ zebra revstr@ upper.txt zugzwang /home/jsmith/temp $

Figure 19.2: Use the F option to distinguish regular files from directories and executables.

Once the symbolic link has been created, you can use it to run the program within Qshell, as Figure 19.3 illustrates. Program REVERSESTR in library JSMITH reads the output of the ls command and writes to stdout. The name of the program is specified in the symbolic link revstr .

ls -l t[h-k]* revstr tacgsmtloht 2002 92 rpA 806 0 HTIMSJ 1 ---xwrxwr- fig.pu-bmuht 2002 81 tcO 773 0 HTIMSJ 1 ---xwrxwr- niedlit 2002 02 peS 54 0 HTIMSJ 1 ----wr-wr- tuoedlit 2002 02 peS 54 0 HTIMSJ 1 ----wr-wr- ttaywedlit 2002 72 peS 824 0 HTIMSJ 1 ----wr-wr- soommtrkopyzzjmkt 2002 31 peS 6 0 HTIMSJ 1 -wr-wr-wr- /home/jsmith $

Figure 19.3: An alias executes a program within a pipeline.

Writing RPG Programs for Qshell

RPG is not as well-suited as Java and C for writing Qshell programs, since it does not have direct support for the standard devices, nor can it directly return an exit status. This only makes sense, since RPG was designed to access databases, not stream files. However, RPG programs can run in Qshell, and the fact that RPG programs can access the database, data areas, data queues, and other objects is a good reason to consider it as an appropriate language for Qshell programs.

Binding to C Functions

There are several ways to make RPG use the standard device files. One method is to bind RPG programs to C functions. Figure 19.4 lists the source code of RPG program, REVERSESTR, which reverses the characters in each line read from standard input. Notice that this program binds to C's gets, puts , and exit functions.

// read from stdin and write a reversed string to stdout H dftactgrp(*no) actgrp(*new) bnddir('QC2LE') D GetString pr * extproc('gets') D 4096a D PutString pr 10i 0 extproc('puts') D 4096a D Exit pr extproc('exit') D 10i 0 value D ReverseString pr 4096a D String 4096a D PStatus s * D pssrStatus s n D Info s 4096a D Outfo s 4096a /free PStatus = GetString (Info); dow PStatus <> *null; Outfo = ReverseString (Info); PutString (Outfo); PStatus = GetString (Info); enddo; *inlr = *on; exit (0); // ********************************** // begsr *pssr; *inlr = *on; if pssrStatus = *off; pssrStatus = *on; else; return; endif; exit (1); return; endsr; /end-free // ********************************** // P ReverseString b D pi 4096a D String 4096a D ndx s 10i 0 D Workfo s 4096a varying /free for ndx = 1 to %len(String); if %subst(String: ndx: 1) = x'00'; leave; endif; Workfo = %subst(String: ndx: 1) + Workfo; endfor; Workfo = Workfo + x'00'; return (Workfo); /end-free P e

Figure 19.4: You can write RPG programs that run in Qshell by binding to C input-output functions.

Figure 19.5 shows REVERSESTR at work.

ls -l ba* -rwxrwxrwx 1 JSMITH 0 313 Jun 3 2002 backup.qsh -rwxrwx--- 1 JSMITH 0 156 Aug 28 2002 batch01.qsh /home/JSMITH $ ls -l ba* /qsys.lib/jsmith.lib/reversestr.pgm hsq.pukcab 2002 3 nuJ 313 0 HTIMSJ 1 xwrxwrxwr- hsq.10hctab 2002 82 guA 651 0 HTIMSJ 1 ---xwrxwr- /home/JSMITH $

Figure 19.5: RPG program REVERSESTR reads the output of the ls command, reversing each line and writing to standard output.

The Unix type APIs

Perhaps a better way than binding to C functions is to use the Unix-type APIs, because they handle stream-file lines of any length. The Unix-type APIs that you will need are Read, Write, and Exit. You can access these APIs via the QC2LE binding directory.

The Read API gets a specified number of characters from an input file. If fewer than the specified number of characters remain in the file, Read gets the remainder of the file. Read gets any characters it finds, including control characters such as carriage -return and linefeed . That is, RPG does not read the input line by line, but character by character. It is up to you, the programmer, to decide what to do with the retrieved data.

RPG program NBRLINES, shown in Figure 19.6, reads from standard input and numbers the lines as they are written to standard output. This is similar in function to the cat utility with the n option. Take note of the following:

* To compile: * CRTBNDRPG PGM(xxx/NBRLINES) + * SRCFILE(xxx/QRPGLESRC) SRCMBR(NBRLINES) H option(*srcstmt) bnddir('QC2LE') dftactgrp(*no) actgrp(*new) D ReadStream pr 10i 0 extproc('read') D fd 10i 0 value D info * value D length 10u 0 value D WriteStream pr 10i 0 extproc('write') D fd 10i 0 value D outfo * value D length 10u 0 value D exit pr extproc('exit') D 3u 0 value D sds D PgmName *proc D PgmStatus 11 15 D PgmStmtNbr 21 28 D PgmException 40 46 D PgmErrorMsg 91 170 D LF c const('x25') D ix s 10i 0 D ox s 10i 0 D datalen s 10i 0 D lineno s 10i 0 D NewLine s n inz(*on) D pssrStatus s n D info s 4096a D outfo s 4096a /free datalen = ReadStream (0: %addr(info): %size(info)); dow datalen > *zero; for ix = 1 to datalen; if NewLine; lineno += 1; outfo = %editc(lineno:'4') + ': ;' WriteStream (1: %addr(outfo): %len(%trimr(outfo))+1); NewLine = *off; endif; ox += 1; %subst(outfo:ox:1) = %subst(info:ix:1); if %subst(info:ix:1) = LF; WriteStream (1: %addr(outfo): ox); ox = *zero; NewLine = *on; endif; endfor; datalen = ReadStream (0: %addr(info): %size(info)); enddo; if ox > *zero; WriteStream (1: %addr(outfo): %len(%trimr(outfo))); endif; *inlr = *on; exit (0); // ==================================================== begsr *pssr; *inlr = *on; if pssrStatus = *off; pssrStatus = *on; else; return; endif; outfo = 'Unexpected error.' + LF; WriteStream (2: %addr(outfo): %len(%trimr(outfo))); outfo = 'Program....: ' + PgmName + LF; WriteStream (2: %addr(outfo): %len(%trimr(outfo))); outfo = 'Statement..: ' + PgmStmtNbr + LF; WriteStream (2: %addr(outfo): %len(%trimr(outfo))); outfo = 'Status.....: ' + PgmStatus + LF; WriteStream (2: %addr(outfo): %len(%trimr(outfo))); outfo = 'Exception..: ' + PgmException + LF; WriteStream (2: %addr(outfo): %len(%trimr(outfo))); outfo = PgmErrorMsg + LF; WriteStream (2: %addr(outfo): %len(%trimr(outfo))); exit (1); endsr; /end-free

Figure 19.6: The NBRLINES program uses the Unix-type APIs to process std input and output.

The ReadStream procedure attempts to retrieve 4,096 bytes of input from stdin (file descriptor 0). The number of bytes read is returned into variable datalen . On all reads except the last, the system returns 4,096 bytes of data. On the last successful read, datalen may return fewer bytes. When ReadStream tries to read past the end of file, the system returns a value of zero.

After each execution of ReadStream, the program enters a loop that examines each character of data and decides what action to take. If the character is not a linefeed character, it is added to the end of an output string. If the character is a linefeed, the output string is written to stdout, and the program prepares to build a new line of output.

Figure 19.7 shows the output from a normal run of program NBRLINES.

ls test* /qsys.lib/jsmith.lib/nbrlines.pgm 1: test01.qsh 2: test02.qsh 3: test03.qsh 4: test04.qsh 5: test04a.qsh 6: test05.qsh 7: test06.qsh 8: test07.qsh 9: test08.qsh 10: test1.pl /home/JSMITH $

Figure 19.7: The RPG program NBRLINES reads the output of the ls command and writes to stdout, numbering lines in the process.

If something goes wrong, the *pssr subroutine writes an error message to stderr. Figure 19.8 shows the output of an abnormal run of NBRLINES.

ls test* /qsys.lib/jsmith.lib/nbrlines.pgm 1: test01.qsh Unexpected error. Program....: NBRLINES Statement..: 00005100 Status.....: 00000 Exception..: MCH1211 Attempt made to divide by zero for fixed point operation. /home/JSMITH $ echo $? 1 /home/JSMITH $

Figure 19.8: If NBRLINES encounters an error, it writes descriptive information to the stderr file and sets the exit status to one.

The previous example shows one way that NBRLINES indicates success or failure ”by writing to either stdout or stderr. In addition, the exit procedure sets the return code to zero to indicate that the program ran successfully, or to one to indicate that the program ended abnormally.

A Better Solution

You can make these APIs easier to use by wrapping them in subprocedures that assume file descriptors, allow constant values, and do not use pointers. This section presents a service program that implements these improvements.

Figure 19.9 contains the source code from which the subprocedure is built. The source member, STDIO, is first compiled to create a module. Then, the module is used to create a service program, also named STDIO.

* Service program to handle stdio * to create: * CRTRPGMOD MODULE(xxx/STDIO) + * SRCFILE(xxx/QRPGLESRC) SRCMBR(STDIO) * CRTSRVPGM SRVPGM(xxx/STDIO) MODULE(STDIO) H nomain bnddir('QC2LE') /define stdio_srvpgm /copy prototypes,stdio // **************** stdin *************** // P stdin b export D pi 10i 0 D info 4096a varying D length s 10i 0 D buffer s 4096a /free monitor; length = ReadStream (0: %addr(buffer): %size(buffer)); if length > *zero; info = %subst(buffer: 1: length); else; %len(info) = *zero; endif; return length; on-error; stderr ('Unexpected error in stdin routine.' + CRLF); cexit (1); return -1; endmon; /end-free P e // **************** stdout *************** // P stdout b export D pi D outfo 4096 const varying D buffer s 4096 /free monitor; buffer = outfo; WriteStream (1: %addr(buffer): %len(outfo)); on-error; stderr ('Unexpected error in stdout routine.' + CRLF); cexit (1); return; endmon; /end-free P e // **************** stderr *************** // P stderr b export D pi D outfo 4096 const varying D buffer s 4096 /free monitor; buffer = outfo; WriteStream (2: %addr(buffer): %len(outfo)); on-error; cexit (1); return; endmon; /end-free P e // **************** exit *************** // P exit b export D pi D status 3u 0 value /free cexit (status); /end-free P e

Figure 19.9: Wrapping the Unix-type APIs in a subprocedure makes them easier to use.

Both the service program and any programs that reference it require procedure prototypes for the I/O subprocedures. The prototype member in Figure 19.10 not only defines procedure prototypes, but frequently used constants, as well. The last part of the member is conditioned by compiler condition stdio_srvpgm, which should be defined when creating the subprocedure module and undefined when creating calling modules.

* read stdin, write stdout and stderr * update exit status * define constants needed by programs that use this service pgm * define condition stdio_srvpgm when compiling the service pgm * but not when compiling programs that use the service pgm D TAB c const(x'05') D CR c const(x'0d') D LF c const(x'25') D CRLF c const(x'0d25') D stdin pr 10i 0 D info 4096a varying D stdout pr D outfo 4096 const varying D stderr pr D outfo 4096 const varying D stdwrite pr D fd 10i 0 value D outfo 4096 const varying D exit pr D 3u 0 value /if defined(stdio_srvpgm) D ReadStream pr 10i 0 extproc('read') D fd 10i 0 value D info * value D length 10u 0 value D WriteStream pr 10i 0 extproc('write') D fd 10i 0 value D outfo * value D length 10u 0 value D cexit pr extproc('exit') D 3u 0 value /endif

Figure 19.10: Programs that use the standard I/O subprocedures should include this copybook member.

Figure 19.11 is the source code for another version of the NBRLINES program. It differs from the previous version in that it uses the routines in the STDIO service program.

* Copy from standard input to standard output, numbering lines * To compile: * CRTRPGMOD MODULE(xxx/NBRLINES) + * SRCFILE(xxx/QRPGLESRC) + * SRCMBR(NBRLINES) * CRTPGM PGM(NBRLINES) + * MODULE(NBRLINES) + * BNDSRVPGM(STDIO) H option(*srcstmt) /copy prototypes,stdio D sds D PgmName *proc D PgmStatus 11 15 D PgmStmtNbr 21 28 D PgmException 40 46 D PgmErrorMsg 91 170 D ix s 10i 0 D datalen s 10i 0 D lineno s 10i 0 D NewLine s n inz(*on) D pssrStatus s n D info s 4096a varying D outfo s 4096a varying /free datalen = stdin (info); dow datalen > *zero; for ix = 1 to datalen; if NewLine; lineno += 1; outfo = %editc(lineno:'4') + ': '; NewLine = *off; endif; outfo = outfo + %subst(info:ix:1); if %subst(info:ix:1) = LF; stdout (outfo); %len(outfo) = *zero; NewLine = *on; endif; endfor; datalen = stdin (info); enddo; if %len(outfo) > *zero; stdout (outfo); endif; *inlr = *on; exit (0); // ==================================================== begsr *pssr; *inlr = *on; if pssrStatus = *off; pssrStatus = *on; else; return; endif; stdout ('Unexpected error.' + LF); stdout ('Program....: ' + PgmName + LF); stdout ('Statement..: ' + PgmStmtNbr + LF); stdout ('Status.....: ' + PgmStatus + LF); stdout ('Exception..: ' + PgmException + LF); stdout (PgmErrorMsg + LF); exit (1); endsr; /end-free

Figure 19.11: This RPG program binds to the standard I/O subprocedures.

Writing COBOL Programs for Qshell

Writing COBOL programs that will operate within Qshell is similar in concept to writing RPG programs for Qshell. Figure 19.12 contains the source code for a COBOL version of NBRLINES. Like the RPG module in Figure 19.11, this COBOL module binds to the Unix-type APIs.

process nomonoprc nostdtrunc Identification division. Program-ID. NbrLines. * Copy from standard input to standard output, numbering lines * To compile: * CRTCBLMOD MODULE(xxx/NBRLINES) * SRCFILE(xxx/SRC) * SRCMBR(NBRLINES) * CRTPGM PGM(NBRLINES) * MODULE(NBRLINES) * BNDDIR(QC2LE) Environment division. Data division. Working-storage section. 01 LineFeed pic x value x"25". 01 ix pic s9(9) binary value zero. 01 ox pic s9(9) binary value zero. 01 LineNbr pic s9(9) binary value zero. 01 LineNbrOut pic z(9). 01 Info pic x(4096). 01 InfoAddress pointer. 01 Outfo pic x(4096). 01 OutfoAddress pointer. 01 DataLen pic s9(9) binary value zero. 01 ReturnCode pic s9(9) binary value zero. 01 NewLine pic x value "Y". Procedure division. Main-Paragraph. Set InfoAddress to address of Info. Set OutfoAddress to address of Outfo. Perform Read-Data. Perform Process-data until DataLen <= zero. Stop run. Process-data. Perform varying ix from 1 by 1 until ix > DataLen if NewLine = "Y" perform StartNewLine end-if Compute ox = ox + 1 Move Info(ix:1) to Outfo(ox:1) If Info(ix:1) = LineFeed perform Write-routine move "Y" to NewLine end-if end-perform. Perform Read-Data. StartNewLine. Compute LineNbr = LineNbr + 1. Move LineNbr to LineNbrOut. Move LineNbrOut to Outfo(1:9). Move ": "to Outfo(10:2). Compute ox = 11. Move "N" to NewLine. Write-routine. Call procedure "write" using by value 1 by value OutfoAddress by value ox returning ReturnCode. Compute ox = zero. Read-Data. Call procedure "read" using by value 0 by value InfoAddress by value 4096 returning DataLen.

Figure 19.12: This COBOL module binds to the Unix-type APIs.

The Read and Write APIs each require three parameters. The first is the file descriptor, which is 0, 1, and 2 for stdin, stdout , and stderr, respectively. The second parameter is a pointer to the variable that contains the data. The third parameter is the number of bytes to be read or written. Note that all three parameters are passed by value. Both procedures return an integer value. For a read, the value is the number of bytes that were retrieved. A negative number indicates that an error occurred on the read.

Figure 19.12 shows how to read from stdin and write to stdout, but does not show how to write to stderr and set the exit status. Figure 19.13 provides an illustration of binding to those two functions.

process nomonoprc nostdtrunc Identification division. Program-ID. Exitc. * Write to stderr and set the exit status * To compile: * CRTCBLMOD MODULE(xxx/EXITC) * SRCFILE(xxx/SRC) * SRCMBR(EXITC) * CRTPGM PGM(EXITC) * MODULE(EXITC) * BNDDIR(QC2LE) Environment division. Data division. Working-storage section. 01 LineFeed pic x value x"25". 01 Outfo pic x(4096). 01 OutfoAddress pointer. 01 ReturnCode pic s9(9) binary value zero. Procedure division. Main-Paragraph. Set OutfoAddress to address of Outfo. * write to stderr String "Program EXITC ended abnormally." LineFeed delimited by size into Outfo. Call procedure "write" using by value 2 by value OutfoAddress by value 32 returning ReturnCode. * set exit status to 9 Call procedure "exit" using by value 9. Stop run.

Figure 19.13: The Write API sends the error message to stderr. The Exit API sets the exit status to nine.

Figure 19.14 shows the results of running the EXITC program.

/qsys.lib/jsmith.lib/exitc.pgm Program EXITC ended abnormally. /home/jsmith $ echo $? 9 /home/jsmith $

Figure 19.14: COBOL programs can write to stderr and set the exit status.

Writing C and C++ Programs for Qshell

As mentioned earlier, C and C++ are good choices for writing Qshell programs, since they directly address the four requirements listed at the beginning of this chapter. C and C++ were used to implement the majority of existing Qshell utilities. In addition, the C and C++ compilers and development tools are provided for use from within the Qshell environment. Chapter 24 provides additional information about C and C++ application-development in Qshell.

Types of Available APIs

In a Qshell environment, a utility receives file-descriptors 0, 1, and 2, which represent the stdin, stdout , and stderr files. Any C or C++ application that uses those descriptors directly (using the Unix-type APIs like Read or Write) will interact with the Qshell environment. Use descriptors 0, 1, and 2 safely for standard I/O only from within a Qshell environment. In other environments (like the CL command line), those file descriptors do not represent the standard I/O files.

You can safely use all of the C and C++ standard I/O mechanisms to do input in a Qshell program. The C-standard I/O APIs are any of those that work with the stdin, stdout, and stderr standard files (either implicitly or explicitly). In C++, use standard I/O via the cin , cout , and cerr objects.

Qshell uses the environment variable QIBM_USE_DESCRIPTOR_STDIO to indicate that the standard I/O files should, internally, always read and write data to the file descriptors 0, 1, and 2 instead of the typical iSeries C runtime terminal. Qshell sets the QIBM_USE_DESCRIPTOR_STDIO environment variable to affect the C/C++ runtime I/O APIs.

Be careful when mixing standard I/O APIs and Unix-type APIs in the same program. The standard I/O APIs perform extensive buffering in the C and C++ runtime to optimize I/O performance. The Unix-type APIs perform buffering at a much lower layer in the operating system. Mixing the APIs on the same output streams could cause data to be intermixed because of the buffering at different layers of the APIs.

Set the exit status from a C or C++ utility using the exit() function or by returning the exit status from the main() routine.

Standard Input Output Functions

Figure 19.15 lists the source code of a C program, reverse , which reverses the characters in each line read from standard input. This program is similar to the RPG program REVERSESTR, shown previously in this chapter. This example uses the fgets(), putc(), and exit() functions to access the standard I/O streams.

cat reverse.c #include #include int main(int argc, char **argv) { char buffer[4096]; // Line Buffer int len = 0; // Length // Read a line of input. Use fgets so buffer overrun // does not occur if more than 4096 bytes of data is available while (fgets(buffer, sizeof(buffer), stdin) != NULL) { len = strlen(buffer); // Length of the line we read in buffer[len-1] = 0; // Remove the newline from the end --len; // Length is now one less // Write out each character of the string in reverse. while (len >= 0) { // Process until we read the beginning. putc(buffer[len], stdout); // Write the character --len; // Go to the previous character. } putc(' ', stdout); // Write the newline that we removed. } return 0; // Set a Qshell exit status of 0 } /home/jsmith $ ixlc reverse.c Program REVERSE was created in library C on 06/14/03 at 13:39:43. /home/jsmith $ ln -s /qsys.lib/c.lib/reverse.pgm reverse /home/jsmith $ ls -l *.[cC] reverse c.ColleH 01:22 8 nuJ 701 0 HTIMSJ 1 xwrxwrxwr- C.ppColleH 01:228 nuJ 811 0 HTIMSJ 1 xwrxwrxwr- c.gnitsiL 01:22 8 nuJ 443 0 HTIMSJ 1 xwrxwrxwr- c.esrever 93:31 41 nuJ 689 0 HTIMSJ 1 xwrxwrxwr- /home/jsmith $

Figure 19.15: The reverse program reads standard input (in this case, the output of the ls command) and reverses each line before writing it to standard output.

Here are some things to note about this program:

Figure 19.16 contains a short C++ version of the line-numbering program.

cat /qsys.lib/jsmith.lib/src.file/eko5.mbr // Number lines // To compile: // // CRTBNDCPP PGM(xxx/EKO5) // SRCFILE(xxx/QCPPSRC) // SRCMBR(EKO5) #include int main (int argc, char **argv) { char s[4096]; int ct = 0; while (1) { cin.getline(s,4096); if (cin.eof()) { break; } cout << ++ct << "=" << s << "= "; } return 0; } ls b* /qsys.lib/jsmith.lib/eko5.pgm 1 =b2d.qsh= 2 =b2d2.qsh= 3 =backup.qsh= 4 =batch01.qsh= 5 =bscript.qsh= 6 == 7 =bin:= 8 =arglist.qsh= 9 =ftprun.qsh= 10 =showargs.qsh= /home/jsmith $

Figure 19.16: This C++ reads the output of the ls command and writes numbered lines to stdout.

IFS File descriptor APIs

The standard stream I/O functions are often easier to use than processing data directly. However, there are some situations where it will be useful to access the file descriptors 0, 1, and 2 directly. In these situations, use the IFS file descriptor-based APIs.

Like the NBRLINES RPG example shown earlier in this chapter, Figure 19.17 outputs all input lines with line numbers . This example uses the Read and Write APIs to access the descriptors 0, 1, and 2 directly.

cat lines.c #include #include #include #include #define BUF 4096 int main(int argc, char **argv) { char buffer[BUF]; // Input buffer char outBuffer[BUF]; // Output buffer int len = 0; // Length of data read int outLen = 0; // Length of current out string. char lineLabel[16] = {0}; // Line number label char currentLine = 1; // Current line number int ndx = 0; // Buffer index sprintf(lineLabel, "%.5d: ", currentLine); // Current label len = read(0, buffer, sizeof(buffer)); // read a buffer while (len > 0) { // Process any data. for (ndx=0; ndx outBuffer[outLen] = buffer[ndx]; // Copy input char to output ++outLen; if (outLen >= BUF // Buffer is full OR buffer[ndx] == ' ') { // Reached a new line if (lineLabel[0] != 0) { // A pending line number? write(1, lineLabel, // Write it out. strlen(lineLabel)); } write(1, outBuffer, outLen); // Output the current line. if (buffer[ndx] == ' ') { // New line. Create a label for ++currentLine; // the next line. sprintf(lineLabel, "%.5d: ", // Pend a line number for when currentLine); // a line is written out. } outLen = 0; // Empty the output buffer. } } len = read(0, buffer, sizeof(buffer)); // Read the next input buffer } // End of all input data. Write any remaining output data if (outLen > 0) { if (lineLabel[0] != 0) { // A pending line number? write(1, lineLabel, // Write it out. strlen(lineLabel)); } write(1, outBuffer, outLen);// Write the remaining data outLen = 0; } if (len < 0) { sprintf(outBuffer, "Error on read buffer, errno value=%d ", errno); write(2, outBuffer, strlen(outBuffer)); } return 0; } /home/jsmith $ ixlc -qifsio=64 lines.c Program LINES was created in library C on 06/14/03 at 15:25:34. /home/jsmith $ ls -1 lines 00001: HelloC.c 00002: HelloCpp.C 00003: Listing.c 00004: lines 00005: lines.c 00006: newline 00007: newline.c 00008: reverse 00009: reverse.c /home/jsmith $

Figure 19.17: This program uses the Unix-type APIs to process its standard input and output. Acting as a filter, the program adds line numbers to the input data before outputting it.

You Can t Teach a Newline New Tricks

When using the Unix type APIs like Read and Write, be aware of a problem that might occur with regard to the newline values. Because of a historical problem with the newline character in C and C++ programs, the hexadecimal value of a newline character changes depending on compilation options. At certain times, this behavior can cause significant confusion.

Using the IFS I/O compilation option on the C or C++ compiler allows C or C++ programs that use the standard C I/O routines to access IFS files instead of iSeries record-based files in the QSYS file system. In C and C++ programs that don't use IFS I/O, the newline character is EBCDIC 0x15 (hexadecimal 15). In C and C++ programs that use the IFS I/O option, the newline character is changed to EBCDIC 0x25 to maintain compatibility with data files in IFS and on other systems.

In an effort to reduce the confusion and impact on applications caused by this discrepancy, APIs and the Qshell utilities typically honor both values as a newline character. In your applications, however, you must choose to honor one or the other. In most cases, simply using the default value generated by the compilation options of the compiler will be sufficient. The IFS I/O options are recommended.

Figure 19.18 contains source code for a C program that illustrates the effect of the compiler options. A symbolic link is created that points to the location where the compiled program will be created. The C program is compiled twice ”once with the default options and a second time with the -qifsio option. Finally, the example uses the -+ option to compile the program as a C++ program with the default options.

cat newline.c #include #include // Compile this program using the IFS I/O or // non IFS I/O to see the different values of // the ' ' character and the support of large // files (greater than 2 Gigabytes). // // CRTCMOD or CRTCPPMOD: // Use SYSIFCOPT(*IFSIO) or SYSIFCOPT(*NOIFSIO) // parameters. // *NOIFSIO is the default for C code // *IFSIO64 is the default for C++ code // // icc: Use -zIFSIO or -zNOIFSIO parameters. // -zIFSIO is the default for C code // -zIFSIO64 is the default for C++ code // // ixlc: Use -qifsio or -qifsio=64 or -noifsio // -qnoifsio is the default for C code // -qifsio=64 is the default for C++ code // // NOIFSIO Newline = 0x15 Size of offset structure = 4 // IFSIO Newline = 0x25 Size of offset structure = 4 // IFSIO64 Newline = 0x25 Size of offset structure = 8 int main(int argc, char **argv) { printf("Newline: 0x%x ", (int)' '); printf("Size of the IFS offset structure off_t: %d ", sizeof(off_t)); return 0; } /home/jsmith $ ln -s /qsys.lib/c.lib/newline.pgm newline /home/jsmith $ ixlc newline.c /home/jsmith $ Program NEWLINE was created in library C on 06/14/03 at 15:00:52. /home/jsmith $ newline Newline: 0x15 Size of the IFS offset structure off_t: 4 /home/jsmith $ ixlc -qifsio newline.c Program NEWLINE was created in library C on 06/14/03 at 15:01:56. /home/jsmith $ newline Newline: 0x25 Size of the IFS offset structure off_t: 4 /home/jsmith $ ixlc -+ newline.c Program NEWLINE was created in library C on 06/14/03 at 15:01:29. /home/jsmith $ newline Newline: 0x25 Size of the IFS offset structure off_t: 8 /home/jsmith $

Figure 19.18: This program demonstrates the possible confusion that can result from the historical feature of an EBCDIC newline value at code point 0x15, when the application is not using the IFS I/O compilation options.

 

 

Writing Java Programs for Qshell

The Java language serves as another excellent mechanism for writing Qshell programs. Like the C and C++ languages, Java has built-in support for the main requirements of Qshell applications.

The first users of the Qshell environment were Java developers using the Java runtime environment. Command- line-based utilities for Java development and command-line Java applications have a common user interface across all of the platforms that they run on; Qshell is no exception.

IBM implements the iSeries Java product directly in OS/400. Not all Java development tools and utilities are available from the CL command line, however. Implementing some of the Java development and runtime utilities only within the Qshell environment provides a cost savings and a consistent command-line interface to Java application developers.

In Java, Qshell integration with your application happens automatically and is intrinsic to the Java runtime. The iSeries Java runtime environment creates and manages a Qshell terminal for the Java application if that application uses the standard mechanisms to access the input/output terminal.

Use the standard I/O objects (directly or indirectly) to access the Qshell standard input, output, and error streams for the I/O processing in your Java program:

You can also use any I/O object created using one of the above objects.

The System.exit() API updates the Qshell exit status with the return code of the Java program.

By default, Java source code must be in an ASCII file, and Java programs read and write ASCII files. Therefore, extra steps are taken to change the expected file encoding to EBCDIC. Chapter 23 provides more information about these steps.

Figure 19.19 lists the source code of a Java program, com.mcpress.qshell.Reverse , which reverses the characters in each line read from standard input. This program is similar to the RPG and C examples earlier in this chapter. It uses the System.in and System.out objects in conjunction with some of the other standard file I/O mechanisms. This example is written in a procedural fashion to aid in comparison with the C and RPG examples.

cat com/mcpress/qshell/Reverse.java package com.mcpress.qshell; import java.io.LineNumberReader; import java.io.InputStreamReader; import java.io.IOException; class Reverse { public static void main(String args[]) { LineNumberReader in = null; String line = null; StringBuffer reverser = new StringBuffer(); try { in = new LineNumberReader(new InputStreamReader(System.in)); line = in.readLine(); // Read all lines from input while (line != null) { // Until the end of the input reverser.setLength(0); // Reset the string buffer. reverser.append(line); reverser.reverse(); System.out.println(reverser.toString()); line = in.readLine(); // Read the next line of input } in.close(); } catch (IOException e) { // Write the exception message and the call stack describing // where it happened to standard error. System.err.println("An IO Error occurred"); System.err.println(e.getMessage()); e.printStackTrace(); System.exit(1); } System.exit(0); } } /home/jsmith $ javac com/mcpress/qshell/Reverse.java /home/jsmith $ ls -l total: 56 kilobytes drwxrwsrwx 2 JSMITH 0 8192 Jun 14 15:18 c -rw-rw-rw- 1 JSMITH 0 6961 Jun 6 01:40 classes.jar drwxrwsrwx 3 JSMITH 0 8192 Jun 4 21:22 com -rwxrwxrwx 1 JSMITH 0 1073 Jun 4 21:21 goodoleboys.sql -rw-rw-rw- 1 JSMITH 0 845 Jun 4 21:21 goodoleboys.txt drwxrwsrwx 2 JSMITH 0 8192 Jun 5 23:05 perl /home/jsmith $ ls -l java com.mcpress.qshell.Reverse setybolik 65 :latot c 81:51 41 nuJ 2918 0 HTIMSJ 2 xwrswrxwrd raj.sessalc 04:10 6 nuJ 1696 0 HTIMSJ 1 -wr-wr-wr- moc 22:12 4 nuJ 2918 0 HTIMSJ 3 xwrswrxwrd lqs.syobelodoog 12:12 4 nuJ 3701 0 HTIMSJ 1 xwrxwrxwr- txt.syobelodoog 12:12 4 nuJ 548 0 HTIMSJ 1 -wr-wr-wr- lrep 50:32 5 nuJ 2918 0 HTIMSJ 2 xwrswrxwrd /home/jsmith $

Figure 19.19: The com.mcpress.qshell.Reverse program reads its standard input (in this case, the output of the ls command) and reverses each line before writing it to standard output.

Here are some things to note about this program:

It is seldom useful to process data directly from descriptors 0, 1, and 2 in a Java program. When it is, those file descriptors should still be accessed in standard I/O objects to provide a better API interface. In Java, the resulting program looks almost identical to one that does not use the file descriptors.

Like the lines C program and the NBRLINES RPG example shown earlier, Figure 19.20 outputs all input lines with line numbers . This example, however, uses objects constructed over the top of the file descriptors 0, 1, and 2 directly. As noted, this exercise is rather fruitless; in most cases, the System.in, System.out, and System.err objects can be used to construct whichever objects are desired.

cat com/mcpress/qshell/LineCounter.java package com.mcpress.qshell; import java.io.LineNumberReader; import java.io.IOException; import java.io.FileDescriptor; import java.io.FileReader; import java.io.FileWriter; import java.io.PrintWriter; class LineCounter { public static void main(String args[]) { // FileDescriptor.in, FileDescriptor.out, and FileDescriptor.err // represent descriptors 0, 1 and 2 respectively. LineNumberReader in = null; PrintWriter out = null; PrintWriter err = null; String line = null; StringBuffer LineCounterr = new StringBuffer(); try { err = new PrintWriter(new FileWriter(FileDescriptor.err)); in = new LineNumberReader(new FileReader(FileDescriptor.in)); out = new PrintWriter(new FileWriter(FileDescriptor.out)); line = in.readLine(); // Read all lines from input while (line != null) { // Until the end of the input int lineNumber = in.getLineNumber(); out.print(lineNumber); out.print(":"); out.println(line); line = in.readLine(); } in.close(); out.flush(); // flush the output, ensuring its written. } catch (IOException e) { // Write the exception message and the call stack describing // where it happened to standard error. err.println("An IO Error occurred"); err.println(e.getMessage()); e.printStackTrace(err); System.exit(1); } System.exit(0); } } /home/jsmith $ javac com/mcpress/qshell/LineCounter.java /home/jsmith $ ls -1 c classes.jar com goodoleboys.sql goodoleboys.txt perl /home/jsmith $ ls -1 java com.mcpress.qshell.LineCounter 1: c 2: classes.jar 3: com 4: goodoleboys.sql 5: goodoleboys.txt 6: perl /home/jsmith $

Figure 19.20: The com.mcpress.qshell.LineCounter application uses the FileDescriptor classes to assign I/O objects to the descriptors 0, 1, and 2. Acting as a filter, the program adds line numbers to the input data before outputting it.

 

 

Option Parameters

Chapter 11 presents the convention used by Unix shells for passing command-line arguments: option arguments come first, followed by non-option arguments. If an option requires an argument of its own, the argument immediately follows the option.

This stands in contrast to the way the CALL commands of CL, RPG, and COBOL pass parameters to other routines. In those languages, the order of a parameter, not its value, determines its meaning. For example, the following two CALL commands are not equivalent:

CALL PGM(MYPGM) PARM(&CUSTNBR &INVOICE) CALL PGM(MYPGM) PARM(&INVOICE &CUSTOMER)

When you write programs for the purpose of running them within Qshell, you must decide whether you will use one of these two conventions, or some other convention for processing parameters.

Passing Parameters to C Programs

The C language is better for processing parameters according to the Unix convention. Qshell passes two arguments to the main function: the number of arguments and an array of pointers to the arguments. By convention, many C programmers refer to these as argc and argv , respectively. Programmers typically process the array of parameter pointers in a For loop, handling one parameter with each iteration. This technique is illustrated in Figure 19.21.

#include main(int argc, char *argv[]) { int ndx; for (ndx = 1; ndx < argc; ndx++) { printf("Arg %2d is "%s" ", ndx, argv[ndx]); } return(0); }

Figure 19.21: C handles parameters as an array, regardless of the number of arguments that are passed to the program. This program lists the arguments to stdout .

Passing Parameters to Java Programs

The Java language provides slightly more convenient support for processing parameters than C. Java gets a single array argument that contains all of the parameters and indicates the number and length of those arguments.

By convention, many Java programmers refer to this as the args array. Programmers typically process the array of String objects in a for loop, handling one parameter with each iteration. This technique is illustrated in Figure 19.22.

package com.mcpress.qshell; class Args { public static void main(String args[]) { for (int i=0; i

Figure 19.22: Java handles parameters as an array, regardless of the number of arguments that are passed to the program. This program lists the arguments to System.out.

Passing Parameters to RPG Programs

RPG and COBOL programs receive parameters in a parameter list. Each passed parameter value is a null- terminated string. Unpassed parameters cannot be addressed. Additional parameters, beyond the number declared, are ignored.

To extract the value of a parameter, use the %STR built-in function. Figure 19.23 lists an RPG program that accepts up to 12 parameters and extracts their values. The %STR function is used in the ExtractParm subprocedure. ExtractParm accepts a pointer to a parameter and passes that pointer to the %STR built-in function to access a null-terminated parameter value. If a parameter value begins with a hyphen, the remainder of the parameter is processed as a string of options. A parameter value that does not begin with a hyphen is assumed to be the argument of the last option that was found.

H option(*srcstmt: *nodebugio) H dftactgrp(*no) actgrp(*new) Fqsysprt o f 132 printer oflind(*inof) D Main pr extpgm('PRINTARGS') D Parm01 48a D Parm02 48a D Parm03 48a D Parm04 48a D Parm05 48a D Parm06 48a D Parm07 48a D Parm08 48a D Parm09 48a D Parm10 48a D Parm11 48a D Parm12 48a D Main pi D Parm01 48a D Parm02 48a D Parm03 48a D Parm04 48a D Parm05 48a D Parm06 48a D Parm07 48a D Parm08 48a D Parm09 48a D Parm10 48a D Parm11 48a D Parm12 48a D ParmCount s 10i 0 D PreviousOption s 1a D Option_lc_b s n D Option_lc_c s n D Option_uc_c s n D Option_lc_f s n D Option_2 s n D option_b_val s 48 D option_f_val s 48 D ExtractParm pr D ParmPtr * value /free ParmCount = %parms; if ParmCount >= 1; ExtractParm (%addr(Parm01)); endif; if ParmCount >= 2; ExtractParm (%addr(Parm02)); endif; if ParmCount >= 3; ExtractParm (%addr(Parm03)); endif; if ParmCount >= 4; ExtractParm (%addr(Parm04)); endif; if ParmCount >= 5; ExtractParm (%addr(Parm05)); endif; if ParmCount >= 6; ExtractParm (%addr(Parm06)); endif; if ParmCount >= 7; ExtractParm (%addr(Parm07)); endif; if ParmCount >= 8; ExtractParm (%addr(Parm08)); endif; if ParmCount >= 9; ExtractParm (%addr(Parm09)); endif; if ParmCount >= 10; ExtractParm (%addr(Parm10)); endif; if ParmCount >= 11; ExtractParm (%addr(Parm11)); endif; if ParmCount >= 12; ExtractParm (%addr(Parm12)); endif; except OptionList; *inlr = *on; /end-free Oqsysprt e OptionList 2 1 O 'Option selection' O e OptionList 1 O 'b:' O option_lc_b +0001 O +0001 '"' O option_b_val +0000 O +0000 '"' O e OptionList 1 O 'c:' O option_lc_c +0001 O e OptionList 1 O 'C:' O option_uc_c +0001 O e OptionList 1 O 'f:' O option_lc_f +0001 O +0001 '"' O option_f_val +0000 O +0000 '"' O e OptionList 1 O '2:' O option_2 +0001 P ExtractParm b D pi D ParmPtr * value D index s 10i 0 D ParmValue s 48a varying D option s 1a /free ParmValue = %str(ParmPtr: 48); if %subst(ParmValue:1:1) = '-'; for index = 2 to %len(ParmValue); option = %subst(ParmValue: index: 1); select; when option = 'b'; Option_lc_b = *on; when option = '2'; Option_2 = *on; when option = 'f'; Option_lc_f = *on; when option = 'c'; Option_lc_c = *on; when option = 'C'; Option_uc_c = *on; endsl; PreviousOption = Option; endfor; else; select; when PreviousOption = 'b'; option_b_val = ParmValue; when PreviousOption = 'f'; option_f_val = ParmValue; endsl; endif; /end-free P e

Figure 19.23: Use the %STR built-in function to access a null-terminated parameter value.

In this example, the options are printed to a spooled file. In a real-world situation, you would use the extracted options to control processing, of course.

Since the options may be passed into the program in any sequence, all of the following commands are equivalent and will produce the same output:

/qsys.lib/jsmith.lib/printargs.pgm -b bval -c -f fval -2 /qsys.lib/jsmith.lib/printargs.pgm -2cb bval -f fval /qsys.lib/jsmith.lib/printargs.pgm -f fval -c2b bval

The output of the program in Figure 19.23 is shown in Figure 19.24.

Option selection b: 1 "bval " c: 1 " C: 0 " f: 1 "fval " 2: 1 "

Figure 19.24: The program derives the same option settings, regardless of how options are ordered in the command line.

Passing Parameters to COBOL Programs

Like RPG programs, COBOL programs receive parameters as null-terminated strings in a parameter list, and unpassed parameters cannot be addressed. Use the "address of" function to determine whether or not a parameter was passed to the program. This function returns a value of null if a parameter is not passed.

Figure 19.25 is a roughly equivalent COBOL version of the RPG program in Example 19.23.

Identification division. Program-ID. ArgsC. Environment division. Data division. Working-storage section. 01 Ndx pic s9(9) binary. 01 ParmValue pic x(48). 01 Option pic x. 01 PreviousOption pic x. 01 Option-lc-b pic x value "N". 01 Option-lc-c pic x value "N". 01 Option-uc-C pic x value "N". 01 Option-lc-f pic x value "N". 01 Option-2 pic x value "N". 01 Option-b-value pic x(48). 01 Option-f-value pic x(48). Linkage section. 01 Parm01 pic x(48). 01 Parm02 like Parm01. 01 Parm03 like Parm01. 01 Parm04 like Parm01. 01 Parm05 like Parm01. 01 Parm06 like Parm01. 01 Parm07 like Parm01. 01 Parm08 like Parm01. 01 Parm09 like Parm01. 01 Parm10 like Parm01. 01 Parm11 like Parm01. 01 Parm12 like Parm01. Procedure division using Parm01 Parm02 Parm03 Parm04 Parm05 Parm06 Parm07 Parm08 Parm09 Parm10 Parm11 Parm12. Main-paragraph. if address of Parm01 not equal null move Parm01 to ParmValue perform Extract-Options end-if. if address of Parm02 not equal null move Parm02 to ParmValue perform Extract-Options end-if. if address of Parm03 not equal null move Parm03 to ParmValue perform Extract-Options end-if. if address of Parm04 not equal null move Parm04 to ParmValue perform Extract-Options end-if. if address of Parm05 not equal null move Parm05 to ParmValue perform Extract-Options end-if. if address of Parm06 not equal null move Parm06 to ParmValue perform Extract-Options end-if. if address of Parm07 not equal null move Parm07 to ParmValue perform Extract-Options end-if. if address of Parm08 not equal null move Parm08 to ParmValue perform Extract-Options end-if. if address of Parm09 not equal null move Parm09 to ParmValue perform Extract-Options end-if. if address of Parm10 not equal null move Parm10 to ParmValue perform Extract-Options end-if. if address of Parm11 not equal null move Parm11 to ParmValue perform Extract-Options end-if. if address of Parm12 not equal null move Parm12 to ParmValue perform Extract-Options end-if. Stop run. Extract-options. If ParmValue (1:1) = "-" perform varying ndx from 2 by 1 until ndx > 48 or ParmValue (ndx:1) = x"00" move ParmValue(ndx:1) to Option evaluate Option when "b" move "Y" to Option-lc-b when "2" move "Y" to Option-2 when "f" move "Y" to Option-lc-f when "c" move "Y" to Option-lc-c when "C" move "Y" to Option-uc-C end-evaluate move Option to PreviousOption end-perform else evaluate PreviousOption when "b" unstring ParmValue delimited by x"00" into Option-b-value when "f" unstring ParmValue delimited by x"00" into Option-f-value end-evaluate end-if.

Figure 19.25: The COBOL program uses the "address of" expression to determine whether or not a parameter has been passed to it.

Summary

Although existing Qshell utilities and scripting are powerful enough for most tasks, you can write programs to handle tasks that Qshell can't. The language processors included with the iSeries are powerful and easy to use in a Qshell environment.

Chapter 20 Accessing OS 400 Specific Objects

Категории