Regex: find elements regardless of order

Viewed 62

If I have the string:

geo:FR, host:www.example.com

(In reality the string is more complicated and has more fields.)

And I want to extract the "geo" value and the "host" value, I am facing a problem when the order of the keys change, as in the following:

host:www.example.com, geo:FR

I tried this line:

sed 's/.\*geo:\([^ ]*\).\*host:\([^ ]*\).*/\1,\2/'

But it only works on the first string. Is there a way to do it in a single regex, and if not, what's the best approach?

3 Answers

I suggest extracting each text you need with a separate sed command:

s="geo:FR, host:www.example.com"
host="$(sed -n 's/.*host:\([^[:space:],]*\).*/\1/p'  <<< "$s")"
geo="$(sed -n 's/.*geo:\([^[:space:],]*\).*/\1/p'  <<< "$s")"

See the online demo, echo "$host and $geo" prints

www.example.com and FR

for both inputs.

Details

  • -n suppresses line output and p prints the matches
  • .* - matches any 0+ chars up the last...
  • host: - host: substring and then
  • \([^[:space:],]*\) - captures into Group 1 any 0 or more chars other than whitespace and a comma
  • .* - the rest of the line.

The result is just the contents of Group 1 (see \1 in the replacement pattern).

Whenever you have tag/name to value pairs in your input I find it best (clearest, simplest, most robust,, easiest to enhance, etc.) to first create an array that contains that mapping (f[] below) and then you can simply access the values by their tags:

$ cat file
geo:FR, host:www.example.com
host:www.example.com, geo:FR
foo:bar, host:www.example.com, stuff:nonsense, badgeo:uhoh, geo:FR, nastygeo:wahwahwah

$ cat tst.awk
BEGIN { FS=":|, *"; OFS="," }
{
    for (i=1; i<=NF; i+=2) {
        f[$i] = $(i+1)
    }
    print f["geo"], f["host"]
}

$ awk -f tst.awk file
FR,www.example.com
FR,www.example.com
FR,www.example.com

The above will work using any awk in any shell on every UNIX box.

Here I've used GNU Awk to convert your delimited key:value pairs to valid shell assignment. With Bash, you can load these assignments into your current shell using <(process substitution):

# source the file descriptor generated by proc sub
. < <(

  # use comma-space as field separator, literal apostrophe as variable q
  awk -F', ' -vq=\' '

    # change every foo:bar in line to foo='bar' on its own line
    {for(f=1;f<=NF;f++) print gensub(/:(.*)/, "=" q "\\1" q, 1, $f)}

  # use here-string to load text; remove everything but first quote to use standard input
  ' <<< 'host:www.example.com, geo:FR'
)
Related