In the article Run QShell/PASE Commands with RPG SPECIAL Files, I presented a new open-source tool that makes it easy to run PHP scripts and Unix utilities from an RPG program.
Since that tool was first provided, I've received several questions, as well as helpful and interesting suggestions from people who have tried out the tool. In this article, I cover the most common questions, as well as share the interesting feedback.
I'd also like to hear your thoughts about this utility. Does it sound easy enough to use? I've been told that since the tool is called UNIXCMD, it'll intimidate some RPG programmers, and that I should pick a name without the word "Unix" in it. Do you agree (bearing in mind that the purpose of the tool is to run the Unix-like utilities available for PASE and QShell)? What do you think?
Leave your feedback in the System iNetwork forums or in the "comments" section at the end of this article.
Q: I'm having problems with data not translating properly (or receiving "garbage" input into my PHP script).
A:This is a weird one, but it's easy enough to fix. Let me explain.
In the sample code of the original article about UNIXCMD, I wanted to demonstrate calling PHP from RPG. My original attempt to send data to PHP, have PHP call a web service, and send data back looked something like this:
FUNIX CF F 1000 SPECIAL PGMNAME('UNIXCMD')
F PLIST(UNIXPARM) USROPN
D cmd s 5000a
D record ds 1000
D lat s 11p 7
D lon s 11p 7
C UNIXPARM PLIST
C PARM CMD
/free
cmd = 'PATH=$PATH:/usr/local/Zend/Core/bin && +
php /www/zendcore/htdocs/geocode.php';
open UNIX;
record = '1600 W Pennsylvania Av, Washington DC';
write UNIX record;
read UNIX record;
lat = %dec(record: 11: 7);
read UNIX record;
lon = %dec(record: 11: 7);
close UNIX;
dsply ('lat=' + %char(lat));
dsply ('lon=' + %char(lon));
*inlr = *on;
/end-free
I expected this code to work. The problem was, it didn't! The address (1600 W Pennsylvania Av) didn't get translated from EBCDIC to ASCII. I couldn't understand why that was the case, since all the documentation I could find states that it should be. But the PHP script always received the data as garbage. So I worked around the problem by inserting a call to the iconv utility and asked it to translate EBCDIC to ASCII like this:
cmd = 'PATH=$PATH:/usr/local/Zend/Core/bin && +
iconv -f 37 -t 819 | +
php /www/zendcore/htdocs/geocode.php';
This works brilliantly on my machine. The problem is it's not supposed to work this way! In theory, the translation will be done automatically and inserting the iconv command like that would cause a double-translation, resulting in garbage!
But on my box, the automatic translation wasn't happening. So I had to add the iconv call.
Since I published this, several people have complained about getting garbage in their PHP scripts using this technique. Apparently, the automatic translation fails only for me, and not for everyone! So the iconv tool fixed the problem on my box, but caused a problem for everyone else (or, at least several people who reported it).
The solution is easy. Try it like this first:
cmd = 'PATH=$PATH:/usr/local/Zend/Core/bin && +
php /www/zendcore/htdocs/geocode.php';
If that works, great! If not, add the iconv utility, like I had to do. Now, if only I could find someone who knows why this doesn't work consistently, I could solve the problem permanently . . . .
In the original article, I said that you could not use both READ and WRITE at the same time with a SPECIAL file. I received a prompt response from Barbara Morris, who is the chief architect of the RPG compiler for IBM Toronto. Here's her response:
Hi Scott, if you define the special file as CF, it will let you both READ and WRITE.
I don't know why special files have that limitation to not allow file addition. When I first heard of special files, I was told that they were invented to support unknown tape and printer devices which only allow for sequential processing. That doesn't really explain why special files would be restricted to only sequential processing, though. And it doesn't explain why the compilers allow CF if "they" had in mind that special files would only be for sequential processing.
- Barbara Morris
That certainly is good to know! Now instead of mucking with the UPDATE opcode, you can define the file as a combined file and use READ and WRITE on it. That feels much more natural! I have updated the sample code included with UNIXCMD to use this technique. Thanks, Barbara!
The original version of UNIXCMD had a hard-coded record length of 1000 bytes per record. Pepe Hipolito quickly asked me if that could be increased, and how high it could be increased to.
My initial response was that it would require program changes but that he could potentially make it 32764 if he only changed the UNIXCMD program, or 32766 if he changed both UNIXCMD and the underlying UNIXPIPER4 service program. I told him that 32766 was the limit because it's the highest record length allowed on an F-spec, according to the ILE RPG Reference manual. However, Pepe tried it at 65535, and it worked. So I guess the manual is wrong, at least as it relates to SPECIAL files!
With a little more research, I discovered that I could easily change UNIXCMD to support lengths of 65535 for the record size! Woohoo! But, for most purposes, you don't want a record that long. It's not efficient. It sure would be nice if the programmer could choose his or her own record length, wouldn't it? Of course, you can do that by specifying a record length on your F-spec, but how would that information get communicated to the UNIXCMD program?
The only solution I found was to add another optional parameter to UNIXCMD. If you don't pass this newly added parameter, UNIXCMD will assume you want a record length of 1000. If you do pass it, UNIXCMD will use the length you pass as the record length. For example:
FUNIX CF F65535 SPECIAL PGMNAME('UNIXCMD')
F PLIST(UNIXPARM) USROPN
D cmd s 5000a
D type s 1a inz('Q')
D reclen s 5p 0 inz(%size(Record))
IUNIX NS
I 165535 Record
C UNIXPARM PLIST
C PARM CMD
C PARM TYPE
C PARM RECLEN
/free
cmd = 'cat /tmp/filetoread.txt';
open UNIX;
read UNIX;
dow not %eof(UNIX);
// The "Record" variable contains one record from
// the "filetoread" file. You can do something
// with it here...
read UNIX;
enddo;
*inlr = *on;
/end-free
In this example, the PLIST specifies three parameters. The third one is optional, and if it is passed, it'll tell UNIXCMD what the record length should be. (Unfortunately, there's no way (that I could find) for UNIXCMD to detect this length automatically, so you do have to pass it as a parameter.)
In this example, I can read records up to 65535 bytes long without much effort. If I wanted to use a shorter record length (to improve performance), I could simply specify a number lower than 65535.
If UNIXCMD returns a record that's longer than the length you specify, it'll split it. So if I set the record length to 100, and the actual line returned by my Unix program was 450 bytes, it'll split it into five records. The first four will be filled in completely, and the last record will have data in the first 50 bytes, and the remaining 50 bytes will be blank.
Jon Paris felt that the name UNIXCMD would be intimidating to many IBM i programmers. After all, the CL command environment is the one we all learned on, and Unix is foreign to us, perhaps even scary. Jon felt that I should pick a different name that doesn't contain the word Unix.
Here are the names Jon suggested:
I sort of like the "OpenPipe" one, but I'm a fan of using names that are suggestive of what the utility does. OpenPipe isn't bad, because it does open a pipe. However, if you were looking for a utility to run a PASE program, would you think to search for "Open Pipe"?
The SPIE name I didn't like. After all, the utility works just as well (arguably better, in fact) with QShell as it does with PASE. And SPIE is not at all suggestive of the purpose of the tool.
I'm on the fence about "EasyPipe." It does make it easy to create a pipe to another program, but it has the same flaw as OpenPipe. If you were searching for a utility like this, would you look for something called EasyPipe?
I also keep thinking to myself that you wouldn't use this tool unless you wanted to run a Unix command from RPG. And if you want to do that, you probably aren't intimidated by Unix, are you? Even if you are, you already know you have to work with Unix before you download and install my tool. After all, a need to use a Unix utility is almost certainly why you used my tool to begin with! So I wouldn't think the word Unix would be too scary.
What do you think? I'd love to hear everyone's thoughts on a better name for this project! Please leave your thoughts in the comment area, below.
I'm seriously considering a CL command interface for UNIXCMD. Right now, accessing the IFS from CL is a little cumbersome, and that's a pretty big problem because CL is often the natural language to use! Furthermore, CL is often used to automate tools like SFTP, PGP, PHP, and so forth. Wouldn't it be handy to have an easy interface to utilize QShell and PASE's tools from CL?
This hasn't been written yet, but I'm thinking about a CL command interface like this:
PGM
OPENPIPE CMD('cd /tmp; ls CDC*.txt')
TYPE(*QSHELL) +
RCDLEN(1000)
RCVPIPE VAR(&LINE) EOF(&EOF)
DOWHILE COND(&EOF *EQ '0')
/* DO SOMETHING WITH DATA */
RCVPIPE VAR(&LINE) EOF(&EOF)
ENDDO
CLOPIPE
ENDPGM
Again, this functionality isn't there, but it may be available in the future. What do you think of it? What would you do differently (for the CL interface)? Let me know by leaving a comment below.
I've turned UNIXCMD into an open-source project. You can download the tool with its sample programs from the following link:
http://www.scottklement.com/unixcmd
Many of the features used in this article (the larger record lengths, in particular) are not available in the original UNIXCMD. If you haven't downloaded it recently, you may want to update your copy from the website.