Extract information with a regular expression in Ruby?

Viewed 65

I have this string

+••• •••• •• •• 99 SMS (185999922)

I am looking to the regular expression to extract this 3 informations

  1. Phone number +••• •••• •• •• 99
  2. mode SMS
  3. device id 185999922

I have tried that to extract the device id

device_id = result.match(/.*\t.*\t\((.*)\)/)

but it doesn't work

3 Answers

You can use

\A([^\t]+)\t([^\t]+)\t\((\d+)\)\z
\A(?<phone>[^\t]+)\t(?<mode>[^\t]+)\t\((?<device_id>\d+)\)\z

See the Rubular demo. Details:

  • \A - start of string
  • ([^\t]+) - Capturing group 1: one or more chars other than a tab char
  • \t - a tab
  • ([^\t]+) - Capturing group 2: one or more chars other than a tab char
  • \t - a tab
  • \( - a ( char
  • (\d+) - Group 3: one or more digits
  • \) - a ) char
  • \z - end of string.

If you use named capturing groups (regex #2), a convenient hash result can be obtained with

s="+••• •••• •• •• 99\tSMS\t(185999922)"
p s.match(/\A(?<phone>[^\t]+)\t(?<mode>[^\t]+)\t\((?<device_id>\d+)\)\z/).named_captures
# => {"phone"=>"+••• •••• •• •• 99", "mode"=>"SMS", "device_id"=>"185999922"}

See the Ruby demo.

r = /\A(\+(?:\d{3} ){2}(?:\d{2} ){2}99)\s+([A-Z]{3})\s+\((\d{9})\)/
phone, mode, device_id = "+123 456 78 90 99 SMS (185999922)".match(r)&.captures
  #=> ["+123 456 78 90 99", "SMS", "185999922"]
phone, mode, device_id = "+12 456 78 90 99 SMS (185999922)".match(r)&.captures
  #=> nil                    ^
phone, mode, device_id = "+123 456 78 90 99 SMSS (185999922)".match(r)&.captures
  #=> nil                                      ^
phone, mode, device_id = "+123 456 78 90 98 SMS (185999922)".match(r)&.captures
  #=> nil                                 ^

& is the safe navigation operator.

We can write the regular expression in free-spacing mode to make it self-documenting.

/
\A             # match beginning of string
(              # begin capture group 1
  \+           # match '+'
  (?:\d{3}[ ]) # match 3 digits followed by a space in a non-capture group
  {2}          # execute non-capture group twice
  (?:\d{2}[ ]) # match 2 digits followed by a space in a non-capture group
  {2}          # execute non-capture group twice
  99           # match '99'
)              # end capture group 1
\s+            # match one or more whitespaces
([A-Z]{3})     # match three capital letters, save to capture group 2
\s+\(          # match one or more whitespaces followed by '('
(\d{9})        # match nine digits, save to capture group 3
\)             # match ')'
/x             # free-spacing regex definition mode

In free-spacing mode spaces and comments are removed before the expression is parsed. It therefore is necessary to protect spaces that are part of the expression. I've done this by putting two in a character class ([ ]). There are various other ways to do that, one being to escape the space characters ((?:\d{3}\ )).

In your example, the device id is in capture group 1 which you could get using puts device_id[1]

device_id = "+••• •••• •• •• 99 SMS (185999922)".match(/.*\t.*\t\((.*)\)/)
puts device_id[1]

Output

185999922

It is a very broad match, but instead to get all 3 parts one option is to use 3 capture groups instead of 1 group:

\A(.*)\t(.*)\t\((.*)\)\z

Rubular demo

As your pattern uses .* which is a very broad match and matches 2 tabs \t, another option could be to split on a tab and remove the outer parenthesis from the device id.

parts = "+••• •••• •• •• 99 SMS (185999922)".split("\t")
puts parts[0]
puts parts[1]
puts parts[2].gsub(/\A\(|\)\z/, "")

Output

+••• •••• •• •• 99
SMS
185999922

See a Ruby demo

Related