Search column and store inside variable using grep or awk

Viewed 131

Here is a file.txt that has the following data:

|ip: 2607:5300:0061:0eda:0000:0000:0000:0000 |abuse_score: 80 |isp: OVH Hosting Inc. |usage_type: Data Center/Web Hosting/Transit |domain: ovh.com |country_name: Canada |country_code: CA |total_report: 27 |distinct_report: 14 |last_report: 2020-10-31T16:01:49+00:00 |time: Sun Nov  1 01:49:53 +08 2020
|ip: 2001:0000:1234:0000:0000:C1C0:ABCD:0876 |abuse_score: 19 |isp: Teredo RFC4380 |usage_type: Reserved |domain: can.com |country_name: United States of America |country_code: USA |total_report: 2 |distinct_report: 0 |last_report: Unknown |time: Sun Nov  1 01:54:28 +08 2020
|ip: 1.1.1.1 |abuse_score: 6 |isp: Teredo RFC4380 |usage_type: Search Engine Spider |domain: gooday.com |country_name: China |country_code: CN |total_report: 0 |distinct_report: 6 |last_report: Unknown |time: Sun Nov  1 01:54:28 +08 2020
|ip: 1.1.1.11 |abuse_score: 7 |isp: Teredo RFC4380 |usage_type: Hacking |domain: wwww.com |country_name: Rusia |country_code: RU |total_report: 3 |distinct_report: 4 |last_report: Sun Nov  1 01:54:28 +08 2020 |time: Sun Nov  1 01:54:28 +08 2020
|ip: 1.1.1.111 |abuse_score: 8 |isp: Teredo RFC4380 |usage_type: Gaming |domain: whyme.com |country_name: Rusia |country_code: RU |total_report: 3 |distinct_report: 8 |last_report: Unknown |time: Sun Nov  1 01:54:28 +08 2020
|ip: 1.1.1.15 |abuse_score: 90 |isp: Teredo RFC4380 |usage_type: IPS |domain: youknowthat.com |country_name: Rusia |country_code: RU |total_report: 100 |distinct_report: 99 |last_report: Sun Nov  1 01:54:28 +08 2020 |time: Sun Nov  1 01:54:28 +08 2020
|ip: 1.1.1.153 |abuse_score: 19 |isp: Teredo RFC4380 |usage_type: Commercial |domain: mynic.com |country_name: Malaysia |country_code: MY |total_report: 8 |distinct_report: 12 |last_report: Unknown |time: Sun Nov  1 01:54:28 +08 2020

So, I need to assign each value after |name: into variable for example to get the ip, abuse_score value, this is what I did:

ip=$(awk '{ print $2 }') # ip is perfectly assigned
abuse_score=$(awk '{ print $4}') # abuse_score is perfectly assigned

But then, to get the other one like isp (which has string that might contain one or more spaces), I could not use the above code because it will only return one word. The ip and abuse_score will never have space. For example to assign isp for the first record from the text file, if I use this

isp=$(awk '{ print $6 }')

This will only assign isp=OVH.

The value of isp should be assigned as OVH Hosting Inc.

How to deal this data that has spaces and assign each of this data into separate variable easily ?

4 Answers

You may use this generic solution using awk:

cat srch.awk
BEGIN{ FS = "[[:blank:]]*[|]" }
{
   for (i=1; i<=NF; ++i) {
      if (match($i, /^[_[:alnum:]]+: /) && substr($i, 1, RLENGTH-2) == fld) {
          print ( substr($i, RLENGTH+1) )
          next
      }
   }
}

Then use it as:

awk -v fld='isp' -f srch.awk file.txt

OVH Hosting Inc.
Teredo RFC4380
Teredo RFC4380
Teredo RFC4380
Teredo RFC4380
Teredo RFC4380
Teredo RFC4380

Or else:

awk -v fld='usage_type' -f srch.awk file.txt

Data Center/Web Hosting/Transit
Reserved
Search Engine Spider
Hacking
Gaming
IPS
Commercial

How about using sed to isolate the field (using "|" as a delimiter):

isp=$(sed -E 's/^.*\|isp: ([^|]*)( \|.*)?$/\1/' file.txt)

One possible problem with this is that if a line doesn't match it'll leave the entire line in, rather than giving a blank result. Depending on the setup, you could use sed's -n option and the p modifier on the replacement:

sed -nE 's/^.*\|isp: ([^|]*)( \|.*)?$/\1/p'

... which will print nothing if there's no match. But then if you process the entire file, non-matching lines will vanish (rather than becoming blank). Depending on how you're using it, this might need fixing.

Alternately, if you like awk and the isp will always be the third "|"-delimited field (fourth if you count the "|" at the beginning):

awk -F'|' '{sub("^isp: ", "", $4); print $4}'

Could you please try following, written and tested with shown samples in GNU awk. We have an awk variable named search just give your string in it and it will look for it in lines and print the matched part accordingly.

awk -v search="isp" '
match($0,search ":[^|]*"){
  val=substr($0,RSTART,RLENGTH)
  sub(/[^:]*: +/,"",val)
  print val
  val=""
}' Input_file

Explanation: Adding detailed explanation for above.

awk -v search="isp" '             ##Starting awk program and setting variable search isp here.
match($0,search ":[^|]*"){        ##Using match function to look for regexp(from search variable value colon till pipe match everything).
  val=substr($0,RSTART,RLENGTH)   ##Creating val which has sub string of matched regex.
  sub(/[^:]*: +/,"",val)          ##Substituting everything till colon with NULL in val.
  print val                       ##Printing variable vale here.
  val=""                          ##Nullify val here.
}' Input_file                     ##Mentioning Input_file name here.


2nd solution: Adding more generic solution, where if anyone wants to search multiple ids in a single awk we could do it. Just provide all strings which you need to search inside variable search with ,(comma) delimiter and we will get all those matches.

awk -v search="isp,usage_type" '
BEGIN{
  num=split(search,arr,",")
  for(i=1;i<=num;i++){
    valCheck[arr[i]]
  }
}
{
  for(key in valCheck){
    match($0,key ":[^|]*")
    val=substr($0,RSTART,RLENGTH)
    sub(/[^:]*: +/,"",val)
    valPrint[key]=(valPrint[key]?valPrint[key] ORS:"")val
    val=""
  }
}
END{
  for(key in valPrint){
    print "Printing values for " key "....." ORS valPrint[key]
  }
}' Input_file

Simple Perl

$ perl -lne ' /isp:\s*([^|]+)/ and print $1 ' azimah.txt
OVH Hosting Inc.
Teredo RFC4380
Teredo RFC4380
Teredo RFC4380
Teredo RFC4380
Teredo RFC4380
Teredo RFC4380
$
Related