I wanted to pass multiple parameters to a z/OS batch program and parse the data. There are several different ways of doing it – what is the best way ?
This question is complicated by
- You may have more data than fits in the PARM= in the JCL. See Defining the parameter string to JCL.
- C provides some parse routines. Although they work in Unix System Services, they do not work 100% in batch JCL. See Parsing with single character options, and getsubopt to parse keyword=value.
- It may be easier to write a “parser” yourself than work around the usability of the solutions. See My basic command line parser(101)
- Or have it table driven. Advanced – table – ize it
Checking options
Processing command line options can mean a two stage process. Reading the command line, and then checking to ensure a valid combination of options have been specified.
If you have an option -debug with a value in range 0 to 3. You can either check the range as the option is processed, or have a separate section of checks once all the parameters have been passed. If there is no order requirement on the parameters you need to have separate code to check the parameters. If you can require order to the parameters, you might be able to have code “if -b is specified, then check -a has already been specified“
I usually prefer a separate section of code at it makes the code clearer.
Command styles
On z/OS there are two styles of commands
def xx(abc) parm1(value) xyz
or the Unix way
-def -xx abc -parm1 -1 -a –value value1 -xyz.
Where you can have
- short options “-a” and “-1”
- long option with two “-“, as in “–value”,
- “option value” as is “-xx abc”
- “option and concatenated value” as in “-xyz”; option -x, value yz
I was interested in the “Unix way”.
- One Unix way is to have single character option names like -a -A -B -0. This is easy to program – but it means the end user needs to lookup the option name every time as the options are not usually memorable.
- Other platforms (but not z/OS) have parsing support for long names like – -userid value.
- You can parse a string like ro,rw,name=value, where you have keyword=value using getsubopt.
- I wrote a simple parser, and a table driven parser for when I had many options.
Defining the parameter string toJCL.
The traditional way of defining a parameter string in batch is EXEC PGM=MYPROG,PARM=’….’ but the parameter is limited in length.
I tend to use
// SET P1=COLIN.PKIICSF.C // SET P2="optional" //S1 EXEC PGM=MYPROG,PARM='parms &P1 &P2'
You can get round the parameter length limitation using
//ISTEST EXEC PGM=CGEN,REGION=0M,PARMDD=MYPARMS //MYPARMS DD * / -detail 0 -debug 0 -log "COLINZZZ" -cert d
Where the ‘/’ on its own delimits the C run time options from my program’s options.
The values are start in column 2 of the data. If it starts in column 1, the value is concatenated to the value in the previous line.
You can use JCL and System symbols
// EXPORT SYMLIST=(*) // SET LOG='LOG LOG' //ISTEST EXEC PGM=CGEN,REGION=0M,PARMDD=MYPARMS //MYPARMS DD *,SYMBOLS=EXECSYS / -log "COLINZZZ" -log "&log" ...
This produced -log COLINZZZ -log “LOG LOG”…
Parsing the data
C main programs have two parameters, a count of the number of parameter, and an array of null terminated strings.
You can process these
int main( int argc, char *argv??(??))
{
int iArg;
for (iArg = 1;iArg< argc; iArg ++ )
{
printf(".%s.\n",argv[iArg]);
}
return 0;
}
Running this job
//CPARMS EXEC CCPROC,PROG=PARMS //ISTEST EXEC PGM=PARMS,REGION=0M,PARMDD=MYPARMS //MYPARMS DD * / -debug 0 -log "COLIN ZZZ" -cert -ae colin@gmail.com
gave
.-debug. .0. .-log. .COLIN ZZZ. .-cert. .-ae. .colin@gmail.com.
and we can see the string “COLIN ZZZ” in double quotes was passed in as a single string.
Parsing with single character options
C has a routine getopt, for processing single character options like -a… and -1… (but not -name) for example
while ((opt = getopt(argc, argv, "ab:c:")) != -1)
{
switch (opt) {
case 'a':
printf("-a received\n");
break;
case 'b':
printf("-b received \n");
printf("optarg %d\n",optarg);
if (optarg)
printf("-b received value %s\n",optarg);
else
printf("-b optarg is0 \n");
break;
case 'c':
printf("-c received\n");
printf("optarg %d\n",optarg);
if (optarg)
printf("-c received value %s\n",optarg);
else
printf("-c optarg is0 \n");
break;
default: /* '?' */
printf("Unknown n");
}
}
The string “ab:c:” tells the getopt function that
- -a is expected with no option
- -b “:” says an option is expected
- -c “:” says an option is expected
I could only get this running in a Unix environment or in a BPXBATCH job. In batch, I did not get the values after the option.
When I used
//BPX EXEC PGM=BPXBATCH,REGION=0M,
// PARM='PGM /u/tmp/zos/parm.so -a -b 1 -cc1 '
the output included
-b received value b1 -c received value c1
This shows that “-b v1” and “-cc1” are both acceptable forms of input.
Other platforms have a getopt_long function where you can pass in long names such as –value abc.
getsubopt to parse keyword=value
You can use getsubopt to process an argument string like “ro,rw,name=colinpaice”.
If you had an argument like “ro, rw, name=colinpaice” this is three strings and you would have to use getsubopt on each string!
You have code like
int main( int argc, char *argv??(??))
{
enum {
RO_OPT = 0,
RW_OPT,
NAME_OPT
};
char *const token[] = {
[RO_OPT] = "ro",
[RW_OPT] = "rw",
[NAME_OPT] = "name",
NULL
};
char *subopts;
char *value;
subopts = argv[1];
while (*subopts != '\0' && !errfnd) {
switch (getsubopt(&subopts, token, &value)) {
case RO_OPT:
printf("RO_OPT specified \n");
break;
case RW_OPT:
printf("RW_OPT specified \n");
break;
case NAME_OPT:
if (value == NULL) {
printf("Missing value for "
"suboption '%s'\n", token[NAME_OPT]);
continue;
}
else
printf("NAME_OPT value:%s\n",value);
break;
default:
printf("Option not found %s\n",value);
break;
} // switch
} // while
}
Within this is code
- enum.. this defines constants RO_OPT = 0 RW_OP = 1 etc
- char const * token defines a mapping from keywords “ro”,”rw” etc to the constants defined above
- getsubopt(&subopts, token, &value) processes the string, passes the mapping, and the field to receive the value
This works, but was not trivial to program
It did not support name=”colin paice” with an imbedded blank in it.
My basic command line parser(101)
I have code
for (iArg = 1;iArg< argc; iArg ++ )
{
// -cert is a keyword with no value it is present or not
if (strcmp(argv[iArg],"-cert") == 0)
{
function_code = GENCERT ;
continue;
}
else
// debug needs an option
if (strcmp(argv[iArg],"-debug") == 0
&& iArg +1 < argc) // we have a value
{
iArg ++;
debug = atoi(argv[iArg]);
continue;
}
else
...
else
printf("Unknown parameter or problem near parameter %s\n",
argv[iArg]);
} // for outer - parameters
This logic processes keywords with no parameters such as -cert, and keyword which have a value such as -debug.
The code if (strcmp(argv[iArg],”-debug”) == 0 && iArg +1 < argc) checks to see if the keyword has been specified, and that there is a parameter following it (that is, we have not run off the end of the parameters).
Advanced – table – ize it
For a program with a large number of parameters I used a different approach. I created a table with option name, and pointer to the fields variable.
For example
getStr lookUpStr[] = {
{"-debug", &debug },
{"-type", &type },
{(char *) -1, 0}
};
You then check each parameter against the list. To add a new option – you just update the table, with the new option, and the variable.
int main( int argc, char *argv??(??))
{
char * debug = "Not specified";
char * type = "Not specified";
typedef struct getStr
{
char * name;
char ** value;
} getStr;
getStr lookUpStr[] = {
{"-debug", &debug },
{"-type", &type },
{(char *) -1, 0}
};
int iArg;
for (iArg = 1;iArg< argc; iArg ++ )
{
int found = 0;
getStr * pGetStr =&lookUpStr[0];
// iterate over the options with string values
for (; pGetStr -> name != (char *) -1; pGetStr ++)
{
// look for the arguement in the table
if (strcmp(pGetStr ->name, argv[iArg]) == 0)
{
found = 1;
iArg ++;
if (iArg < argc) // if there are enough parameters
// so save the pointer to the data
*( pGetStr -> value)= argv[iArg] ;
else
printf("Missing value for %s\n", argv[iArg]);
break; // skip the rest of the table
} // if (strcmp(pGetStr ->name, argv[iArg]) == 0)
if (found > 0) break;
} // for (; pGetStr -> name != (char *) -1; pGetStr ++)
if (found == 0)
// iterate over the options with int values
....
}
printf("Debug %s\n",debug);
printf("Type %s\n",type );
return 0;
}
This can be extended so you have
getStr lookUpStr[] = {
{"-debug", &debug, "char" },
{"-type", &type ,"int" },
{(char *) -1, 0, 0}
};
and have logic like
if (strcmp(pGetStr ->name, argv[iArg]) == 0)
{
found = 1;
iArg ++;
if (iArg < argc) // if there are enough parmameters
{
if ((strcmp(pGetStr -> type, "char") == 0
*( pGetStr -> value)= argv[iArg] ;
else
if ((strcmp(pGetStr -> type, "int ") == 0 )
*( pGetStr -> value)= atoi(argv[iArg]) ;
...
}
You can go further and have a function pointer
getStr lookUpStr[] = {
{"-debug", &debug,myint },
{"-loop", &loop ,myint },
{"-type", &type , mystring },
{"-type", &type , myspecial },
{(char *) -1, 0, 0}
};f
and you have a little function for each option. The function “myspecial(argv[iarg])” looked up values {“approved”, “rejected”…} etc and returned a number representation of the data.
This takes a bit more work to set up, but over all is cleaner and clearer.