2

I have some complicated logs that I try to parse. What I want is to find a string H, get column 5 from the line that matches, and then take all other lines where column 5 matches the line with the string H.

Example input

a b c d 31 1
a b c d 31 H
a b c d 31 2
a b c d 32 1
a b d d 32 2
a b c d 33 1
a b c d 33 H
a b c d 33 2

Expected output

a b c d 31 1
a b c d 31 H
a b c d 31 2
a b c d 33 1
a b c d 33 H
a b c d 33 2

So, I have been able to do so by making two separate scripts: script1 | xargs -n1 | script2

script1 searches for string H, and then prints the fifth column.
cat logfile | grep 'H' | awk '{print $5}'

script2 then prints all other lines from the logfile where column 5 matches the output of the first script: cat logfile | awk -v var="$1" '$5 == var'

I want to make the input file, the logfile, a variable, so that I can use $1 in the script, and then call script logfile. But then I must merge the two scripts to one script, because both scripts parse the same logfile. What is, generally speaking, the right approach to do this? I say generally, because I am a newby to bash scripting.

The two problems that I encouter are, firstly: the $1 of the first script (which is the logfile) is different from the $1 in the second script (the number that is the output of the first script) that I pipe to awk. Secondly, I cannot find the equivalent of xargs -n1 for use within one bash script.

  • 2
    can you provide a sample of log and expected output ? – Archemar Jun 28 '15 at 19:04
2

The second awk reads two inputs, one after the other - from the piped output of the first awk and then from the file itself. One way to identify the start of the second input is that NR (the Number of the current input Record, overall) no longer matches FNR ( the current File's record number). Note that - as a FILE arg means tells awk to get the data from stdin (via a pipe, in this case).

awk '$6=="H"{print $5}' "$1" |
awk 'NR==FNR{k[$1];next}
     $5 in k{print}' - "$1"

Note that the above method of identifying the second input is in common use, but fails to behave as expected/desired when there is nothing to read from the first input. For this job's requirements it won't matter if the first imput delivers nothing. The logic will never get past NR=FNR where it will build up a list of k index value (ie. $1 from the main file) - but nothing is ever done with them – so the script works with a null imput via the pipe, but only because of a messy side-effect of the particular logic used.

There is however, in GNU awk (gawk), a sure-fire way to identify the current file/pipe. There is a special variant to the commandline FILE arguments: "...an argument that has the form var=value, assigns value to the variable var—it does not specify a file at all." – see: Other Command Line Arguments The placement of such var=value args is significant – values needed for a specific FILE must be placed on the command line before its associated FILE – subsequent var=value are not applied within awk until the preceding files/pipe has been fully read.

Here is the var=value version.

gawk '$6=="H"{print $5}' "$1" |
gawk 'fn==1{k[$1];next}
      $5 in k{print}' fn=1 - fn=2 "$1"

output (its the same for both script versions)

a b c d 31 1
a b c d 31 H
a b c d 31 2
a b c d 33 1
a b c d 33 H
a b c d 33 2
  • Thanks a lot for the detailed explanation. I have also made improvements with while in combination with read: cat $file | awk '/H/ {print $5}' | while read -r id; do cat $file | awk -v var="$id" '$5 == var'; done – Rogier Visser Jul 2 '15 at 19:14

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.