2

If any of you are familiar with mainframe JCL.
I'm trying to match the last line of the job card.

Basically the first line that starts with // and ends without a comma. In the example I need the 3rd line or up to the 3rd line matched.

I'm using Ansible's lineinfile to dynamically insert a route card after the job card.

For example:

//SPOOL1   JOB (UU999999999,1103),'Programmer',CLASS=0, <--- start of job card
//         REGION=0M,MSGCLASS=R,TIME=5, LINES=(999999,WARNING),
//         NOTIFY=&SYSUID  <--- end of job card
//STEPNAME EXEC PGM=BPXBATCH 
//STDERR   DD   SYSOUT=*
//STDOUT   DD   SYSOUT=*
//STDPARM  DD   *
SH cat /dev/urandom

So far I got this, which matches the start of // and anything after, but, I cant figure out the last part

^(\Q//\E(.)*)
Mr.White
  • 31
  • 6
  • Does the JOB card always start with `//SPOOL`? Or is it identified by `//XXX JOB`? – Bohemian Dec 10 '21 at 23:26
  • 1
    @Bohemian `SPOOL1` is a user defined job name: https://www.mainframestechhelp.com/tutorials/jcl/job-card.htm – β.εηοιτ.βε Dec 10 '21 at 23:36
  • do you have to worry about line numbers or other grick in columns 73 - 80 ? must you account for a comment on the last line of the jobcard that ends in a comma? – cschneid Dec 11 '21 at 00:08
  • 1
    @Mr.White Not necessarily true: *Basically the first line that starts with // and ends without a comma.* You can continue a quoted parameter onto the next line. You code the opening quote, type text up to pos. 71. The continuation line starts with //, followed by spaces up to pos 15. The parameter text then continues starting with pos. 16. There you have a line staring with // *not* ending in a comma, but still not the last line of the JOB statement. (comment deleted and added, but with typos corrected.) – phunsoft Dec 11 '21 at 15:07

4 Answers4

2

Parsing JCL in the general case is hard. As noted in the comments, the rules are full of caveats.

I have an ANTLR4 grammar for JCL, it's MIT licensed. Possibly of use. It reflects the beauty of JCL.

cschneid
  • 10,237
  • 1
  • 28
  • 39
1

To match the whole job card (in this case 3 lines):

(?sm)\A.*?\/\/[^*]((?!\/\*)[^\n])*[^,]$

See live demo.

Breaking this down:

  • (?sm)
    • s enables the DOTALL flag (meaning . matches new lines too)
    • m enables the MUTLILINE flag (meaning ^ and $ match start and end of lines
  • \A means start of input (so it only matches at the very start)
  • .*? means anything, but as little as possible
  • //[^*]
  • ((?!\/\*)[^\n])* means non-new lines, except the sequence /* (so don't match when a comment is put in line)
  • [^,] not a comma
  • $ end of line

In English: "match from the start until there's a non-comma at the end of a line that is not a comment, or does not end with a comment"

You would then replace with $0 (group zero is the entire match) followed by your injected content:

$0\\n*ROUTE statement
Bohemian
  • 412,405
  • 93
  • 575
  • 722
  • Thanks for the live demo link. I'm not good enough in regex to question your answer, but a quick test on rubular.com showed it doesn't work reliably. Note that in JCL, a comment may follow on every line after a space. If I add such a comment, say, on the first line, the regex matches only the first line. – phunsoft Dec 11 '21 at 09:40
  • @phunsoft regex adjusted to handle comment lines. See new demo too. – Bohemian Dec 11 '21 at 13:12
  • Still does not handle case where comment is at end of line. Non-complete and simplified rules are: Every record (line) in JCL starts with `//` in pos. 1, and has up to four *fields*, **separated by one or more spaces**. If pos.3 is `*`, then whole line is comment. If not `*`, but non-blank, it is the *name* field (max. 8 chars). Single word *operation* field, e.g `JOB`, `DD`, follows name (or blank if no name). Variable length *parameter* field follows *operation*. End of *parameters* recognized when last char is *not* `,`, else continued on next line. *comment* field follows *parameters* – phunsoft Dec 11 '21 at 13:25
  • (continuation) JCL is an ugly beast. There are more exotic rules that make parsing non-trivial. – phunsoft Dec 11 '21 at 13:29
  • @phunsoft the regex now handles comment at end of line. Linking now to a python regex tester, since ansible uses python regex engine. – Bohemian Dec 11 '21 at 20:35
  • @phunsoft I have no idea what "test cases" you're talking about, and I've already gone well beyond the scope of the original question. If you want more, please ask it in a [new question](https://stackoverflow.com/questions/ask). – Bohemian Dec 11 '21 at 21:30
  • Thank you, @Bohemian this actually may work for my case. Our JCL is not going to be super complicated and so far this looks to be working with all of the jcl I'm using for a project. – Mr.White Dec 16 '21 at 22:57
0

You can use a negative lookbehind for this: (?<!,).
But you'll also need to insert after the firstmatch and use backrefs.

Given the task:

- lineinfile:
    path: file.jcl
    regexp: '^(\/\/.*)(?<!,)$'
    line: "\\1\\n//*ROUTE statement"
    firstmatch: true
    backrefs: true

You would end up, from your example, with:

//SPOOL1   JOB (UU999999999,1103),'Programmer',CLASS=0,
//         REGION=0M,MSGCLASS=R,TIME=5, LINES=(999999,WARNING),
//         NOTIFY=&SYSUID
//*ROUTE statement
//STEPNAME EXEC PGM=BPXBATCH 
//STDERR   DD   SYSOUT=*
//STDOUT   DD   SYSOUT=*
//STDPARM  DD   *
SH cat /dev/urandom
β.εηοιτ.βε
  • 33,893
  • 13
  • 69
  • 83
0

For the general case this is tougher than you think because of comments allowed within the scope of the JOB card.

    //SPOOL1   JOB (UU999999999,1103),'Programmer',CLASS=0, <--- start of job card
    //         REGION=0M,MSGCLASS=R,TIME=5, LINES=(999999,WARNING),
    //         NOTIFY=&SYSUID  <--- end of job card

The strings you show:

  • <--- start of job card
  • LINES=(999999,WARNING),
  • <--- end of job card

are all valid as comments in JCL because they follow a space.

You can even have whole comment lines within the JOB card. For example:

//name    JOB (accounting info),'data capture ___',     
//*            TYPRUN=SCAN,                                               
//             NOTIFY=&SYSUID,                                            
//             CLASS=A,MSGCLASS=T,MSGLEVEL=(1,1),TIME=(5,00),             
//             REGION=5M  

So you're not necessarily looking for the first card that doesn't end in a comma unless you can restrict the JCL you're looking at.

Your JOB card starts with //name JOB and ends just before the next //name card. *** edit *** As was correctly pointed out, the JOB card could be followed by a card which does not require a name field, like // SET for example. See https://www.ibm.com/docs/en/zos/2.4.0?topic=statements-jcl-statement-fields *** end of edit ***

It starts with ^(\Q//\E)[A-Z0-9]+\s+\QJOB\E.+ and ends just before the next named card ^(\Q//\E)[A-Z0-9]+\s+

But I don't know regular expressions well enough to find the "just before" point to insert your new line. Hopefully someone else can add that.

Roger
  • 1
  • 2
  • Not quite true: *Your JOB card starts with //name JOB and ends just before the next //name card.*. The *name* is optional on all JCL statements, except for the `JOB` statement. E.g. on `SET` statements there usually is no name; it is not needed because you can't refer to it. – phunsoft Dec 11 '21 at 09:29