Initially I got the solution already in this post.

However those are only for 2 patterns. It turns out I need more than 2 patterns now, possibly the 3rd pattern and even 4th pattern.

I tried using the following solution by adding more patterns that I got from @cas

awk -v OFS=, '
match($0,/\<[[:digit:]]{1,2}\/[[:digit:]]{1,2}\/[[:digit:]]{4} [[:digit:]]{1,2}:[[:digit:]]{1,2}\>/,a) {dt = a[0]; next};
match($0,/3rd pattern/,b);
match($0,/INC-[[:digit:]]+-[[:digit:]]+/,a) {print a[0], dt; print b[0]}' filename.log

and I'm getting the following output:

INC-210305-00000426,3/6/2021 5:19

INC-20210304-00006690,3/5/2021 5:24

only adding more space. Any other suggestion how I can grep for the 3rd or 4th pattern? they are indeed located in the different lines. Below is the input:

unwantedtext unwantedtext unwantedtext unwantedtext 8/1/2022 6:15 (1st Pattern)

unwantedtext unwantedtext unwantedtext unwantedtext 
unwantedtext unwantedtext Report_A (3rd Pattern)
unwantedtext unwantedtext INC-220721-00007628 (2nd required pattern)
unwantedtext unwantedtext unwantedtext unwantedtext 
unwantedtext unwantedtext Report_B (4th) Pattern)

The desired output is as follows:

INC-220721-00007628, 8/1/2022 6:15, Report_A

And later I might need the following output as well:

INC-220721-00007628, 8/1/2022 6:15, Report_A, Report_B

I'm using Cygwin and CentOS 7 environments.

Thank you in advance for the helpful solution.

  • Notice how in the first match, it captures any matches into array a and (if the match was successful) store the first element of a into another variable (dt = a[0]; next) for use in the print statement later? Then notice how with the "third pattern" you added, you're NOT doing that. There's a reason why I used another variable - because every time match() is run, it overwrites the array (a or b) whether there were any matches or not....so the array doesn't survive past the current input line.
    – cas
    Aug 30 at 5:09
  • BTW, given that your 4th pattern can occur after the INC- line (where the output is printed), I'd be inclined to use perl (as I did in my original answer) but use a Hash of Arrays data structure where the hash key is the 2nd pattern ("INC-*") and the values are the matches from the other patterns. Don't print anything until the entire input has been read, then (in an END { } block), iterate over the hash and print it.
    – cas
    Aug 30 at 5:19
  • Hi Cas, thanks for the feedback. I tried to save 3rd pattern into variable b and adding this in the end '{b[0]; next};' and then print it using this statement '{print a[0], b[0], dt}' however still failed... Aug 30 at 6:14
  • yes, you're using an array called b. which will be reset the next time match($0,/3rd pattern/,b) is run, which will be the next time an input line is read (the awk code is executed, in order, for each input line). that's why i said copy b[0] to another variable.
    – cas
    Aug 30 at 9:34
  • BTW, one important detail that isn't clear: can there be more than one date line and one "INC-" line in the input file? i.e. can it contain more than one record that needs to be printed? I have been assuming there could be, but Stéphane's answer below assumes that there isn't - which assumption is correct?
    – cas
    Aug 30 at 9:39

3 Answers 3


I'd use perl instead of awk (even gawk; the code you use is gawk-specific):

perl -l -0777 -ne '
  $time = $& if m{\b\d{1,2}/\d{1,2}/\d{4} \d{1,2}:\d{1,2}\b};
  $inc = $& if /\bINC-\d+-\d+\b/;
  @reports = /\bReport_\S+/g;
  print join ", ", $inc, $time, @reports
    if defined($time) && defined($inc)' your-file


$ txr extract.txr input
INC-220721-00007628, 8/1/2022 6:15, Report_A, Report_B

Where extract.txr is:

@(skip)@{date /[^ ]+/} @{time /[^ ]+/} (1st Pattern)
@(skip)@{rep1 /[^ ]+/} (3rd Pattern)
@(skip)INC-@inccode (2nd required pattern)
@(skip)@{rep2 /[^ ]+/} (4th) Pattern)
INC-@inccode, @date @time, @rep1, @rep2

We need a modicum of regex here because we are matching patterns at the ends of lines. A variable like @date capture text that may contain spaces, whereas @{date /[^ ]+/} captures a sequence of non-space characters.

Since the output is just a one-liner, we can replace the @(output)...@(end) that using a Lisp function call wrapped in @(do ...):

@(do (put-line `INC-@inccode, @date @time, @rep1, @rep2`))
  • Hi Kaz, thank you for the solution. would you please snapshot what is the real information under the extract.txr? Aug 31 at 8:34
  • I'm not sure what you mean. extract.txr is given above. input contains the sample data from the question.
    – Kaz
    Aug 31 at 9:10

Using any POSIX awk:

$ cat tst.awk
match($0,/([0-9]{1,2}\/){2}[0-9]{4} [0-9]{1,2}:[0-9]{1,2}/) { dt = substr($0,RSTART,RLENGTH) }
match($0,/INC(-[0-9]+){2}/) { inc  = substr($0,RSTART,RLENGTH) }
match($0,/Report_A/)        { repa = substr($0,RSTART,RLENGTH) }
match($0,/Report_B/)        { repb = substr($0,RSTART,RLENGTH) }
END { OFS=", "; print inc, dt, repa, repb }

$ awk -f tst.awk file
INC-220721-00007628, 8/1/2022 6:15, Report_A, Report_B

The above would match the target regexps mid-word. You don't show that as being a possibility in the example in your question but if that's a concern for your real data then just change it to:

$ cat tst.awk
function set(val) {
    val = substr($0,RSTART,RLENGTH)
    gsub(/^ | $/,"",val)
    return val
match($0,/(^| )([0-9]{1,2}\/){2}[0-9]{4} [0-9]{1,2}:[0-9]{1,2}( |$)/) { dt = set() }
match($0,/(^| )INC(-[0-9]+){2}( |$)/) { inc  = set() }
match($0,/(^| )Report_A( |$)/)        { repa = set() }
match($0,/(^| )Report_B( |$)/)        { repb = set() }
END { OFS=", "; print inc, dt, repa, repb }

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .