for our test automation effort, I am writing a utility which is to concatenate datasets with various record lengths and write the concatenated contents to a single output file whose LRECL would be the same as the longest LRECL of the input datasets. (Reason is simple: IEBGENER abends when you concatenate datasets with differing LRECLs/RECFMs into it's SYSUT1 in the JCL.)
Since it's supposed to be universal ("because automation"), the utility needs to be very dynamic: any number of input datasets of FB or VB formats and any possible LRECL could be user-entered as input, and either FB or VB record format could be user-required as output.
I have a working prototype which determines the number of input files, the highest LRECL of all of them and expects user-specified RECFM. It does the actual copying/concatenating of any number of input files to the output file and detecting the highest LRECL encountered.
However, I've run into unsolvable problems as for trying to force the output file to have the desired LRECL and RECFM values which are being programatically auto-detected during runtime.
I've consulted the IBM PL/I Programming guide and Reference guide, and Joan Hughes's "PL/I structured programming", but to no avail.
So I was hoping you guys could give me some clue.
Here is what I've tried so far:
The ENV parameter. When I started writing the utility, I had hoped I could use this and easily solve the problem. However, I found out ENV only accepts STATIC variables, i.e. pre-set upon the program initialization, and thus I cannot pass it the LRECL computated during the actual program execution.
ENV(<RECFM> BLKSIZE(0). I tried using two variables for the output file, one for cases when user requires RECFM=FB and other for RECFM=VB, with both pointing to the same DDname using the TITLE option of the subsequent OPEN statement, knowing that Blocksize=0 should cause auto-detection of LRECL and Blocksize. However, this just results in S013/R=00000034 Abend when the JCL does't specify the DCB of outfile (and the utility cannot require the user to do that - it would beat it's purpose).
Using EXPORT DD_<DDNAME> statement with LRECL passed as non-static variable. I couldn't get this to work, the compiler keeps saying the statement is invalid. I probably don't understand the usage, the manuals aren't very verbose about this.
Using the OPEN statement's TITLE with the /filespec attribute: this looked promising, but unfortunately, the TITLE could contain EITHER alternate_ddname OR /filespec, and /filespec seems to require hard-coded DSN. I couldn't have that, for the DSN is to be user-defined in the JCL calling the utility, and I also need the TITLE to point to an "alternate_ddname", because the output file's DD needs to be always reffered to as "OUTFILE" in the JCL.
Falling back on implicit declarations (i.e. without record length or format being specified in the output file's DCL or OPEN statements). This works without abends and copies the infiles to the OUTFILE, but the output file seems to always be RECFM=VB,LRECL=255 (when testing with longest LRECL of 137 and all the "infiles" being FB) regardless of the length of the CHAR(<lrecl>) variables I am using to write to the OUTFILE.
Now, I do know two things:
such utility could be programmed, because they are commercially available
opening a file to write with it's LRECL and RECFM dynamically determined during runtime is possible, because my coworker has an assambler code which does that (I, being Test Automation, don't know HLASM)
So this is just a matter of finding how to accomplish this feat in PL/I.
Or, if you all agree this really cannot be done, finding a way how to substitute the OPEN statement with calling assembler macro to which I could pass the LRECL and RECFM variables I have and then work with the opened dataset using the standard PL/I I/O statements. (My coworker could provide the HLASM functionality itself, I just don't know how to "interface it" with my PL/I variables.)
INFILE01 has LRECL=137, the others have LRECL=80. All are FB.
My whole problem is making the OUTFILE LRECL=137 and RECFM=FB (or whatever they're found to be during program's runtime) without specifying that in the JCL (the idea being that the customer doesn't know the record lengths of all his INFILEs and the program would auto-detect that for him).
To avoid further misunderstandings, let me post relevant portions of the function in question (note there are redundant debug/hacking variables):
/* CPINOUT procedure: */
/* - input: total # of INFILE DDs, LRECL of the longes INFILE, */
/* desired RecFm of the OUTFILE, desired BlockSize of */
/* the OUTFILE */
/* - purpose: procedure allocates OUTFILE with the same LRECL */
/* as the longest INFILE's LRECL, then copies every */
/* INFILE dataset's content to the OUTFILE dataset, */
/* concatenating all the INFILE datasets in OUTFILE */
dcl $maxLrecl FIXED(7,0);
dcl $currLrecl FIXED(7,0);
dcl $padLrecl fixed(7,0) init(0);
dcl $maxFiles fixed(3);
dcl $curDsnNr fixed(3);
dcl $outRecFm char(4) var; /* TODO */
dcl $outBlksz fixed(9); /* TODO */
dcl $currFi file record;
dcl $outFiFB file record ENV(FB BLKSIZE(0));
DCL $outFiVB FILE RECORD ENV(VB BLKSIZE(0));
DCL $outFile FILE record;
dcl $fileTitl char(9) var;
dcl $tempVar char($maxLrecl) var;
dcl $tempFix char($maxLrecl);
dcl not_eof bit(1) init('1'b);
<OUT OF IDEAS ON HOW TO OPEN $OUTFILE or $OUTFIFB WITH $MAXLRECL AND $OUTRECFM HERE>
DO $curDsnNr = 1 to $maxFiles by 1;
$fileTitl = 'INFILE'||padif10(trim($curDsnNr));
call logstr('Reading '||$fileTitl||' to concatenate');
open file($currFi) title($fileTitl) input;
ON ENDFILE($currFi) NOT_EOF='0'B;
read file($currFi) into($tempFix);
write file($outFiFB) from($tempFix);
read file($currFi) into($tempFix);
END; /* end looping through all $currFi lines */
Joined: 30 Nov 2013 Posts: 590 Location: The Universe
Something like this is not terribly difficult to write in Assembler. I did something similar back in the 1970s or perhaps the early 1980s as a TSO command - TPRINT (dataset dataset ... dataset) ... to print several data sets. One issue I had that you do not have is integrating the now obsolete table reference character (specified with OPTCD=J) into the output.
As Mr. Sorichetti and Mr. Dodgers propose, rather than trying to force the language and the PL/I library to define DCB attributes on the fly, use BPXWDYN to allocate an output data set with the required DCB attributes. One thing you have not considered is how to print data sets with ANSI carriage control characters and "machine" carriage control together. This is not trivial! Fortunately, "machine" carriage control is quite rare any more
Another issue is detecting input DCB attributes using the language and the library. I do not know if this is relatively easy to do, or is even possible.
Rather than trying to produce an output data set with "optimal" DCB attributes based on the input, just use a data set with varying length records large enough for the maximum input length you plan to support. You can keep the output size reasonable by trimming any trailing blanks from input records.
thanks for your advice. The option of using RECFM=VB and long-enough LRECL is always on the table, because my prototype did just that in the "implicit allocation" scenario I wrote about in the first post, but I would consider it not-enough good job.
For a large part of the afternoon, I was playing with the BPXWDYN calls. I have never called it from PL/I before, and I run into all sorts of weird problems, probably caused by some trivial mistake in my coding.
This is what I tried, based on the sole example in BPXWDYN's documentation:
I then rewrote my OPEN FILE and WRITE INTO statements to reference the $outFi variable/ddname.
However, despite the file supposedly being allocated:
IGD101I SMS ALLOCATED TO DDNAME ($OUTFI )
DSN (xxx.xxx.CATALLOC )
STORCLAS (SCWRKD) MGMTCLAS (MCWRKD) DATACLAS (DCWRKD)
VOL SER NOS= WRKD04
I subsequently got
IBM0203S ONCODE=83 The UNDEFINEDFILE condition was raised because the BLOCKSIZE was not specified (FILE= $OUTFI).
From entry point CPINOUT at statement 230 at compile unit offset +0000082C at entry offset +0000082C at
Statement 230 being the
open file($outfi) output title('OUTFILE');
I seriously cannot grasp this: I have supplied the $outFi with Blocksize (albeit maybe not the best, but for FB-formatted datasets, LRECL*10 seems to be legit), yet it complains I didn't.
And the "XXX.XXX.CATALLOC" dataset is deleted regardless of what I do, although I did add another BPXWDYN block with "free fi($OutFi)":
"IGD105I XXX.XXX.CATALLOC DELETED"
To the end of my code, instead of the classic PL/I "close file($OutFi)" statement. Other files, which are closed in the "close file()" fashion, are RETEINED.
Also, the IBM manual says BPXWDYN returns messages in a "E15", which I suspect to be an Assembler Register. However, how do you fetch an ASM register from PL/I? Again, this is Terra incognita for me.
Could you guys shed some more light on working with BPXWDYN for me, or point me to a manual more verbose about the PL/I-BPXWDYN interaction?