0

I have to find the ipv4 in a file.The problem is if there are other words on the same line as the IP the script wont print it.Here is my script:

#!/bin/bash


if [ -e ip.txt ]
then
    grep -E '^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$' ip.txt
else
    echo "file not found"
fi

Now if I have something like this the script wont print the IP:

198.54.34.6 text
  • Is the sample input you provide representative of all permutations? i.e. will the IP address always be the first word of the line? If not, please add any other input permutations that are possible, by editing your question. – Sparhawk Sep 7 '19 at 13:30
  • Change it to grep -Eo (-o for only match) and rid the $ on the end of it, like this: grep -Eo '^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])' ip.txt Like Prvt_Yadv's solution below. – A.Danischewski Sep 8 '19 at 1:24
4

Remove ^ and $ from the command and use -o flag of grep command i.e.:

grep -Eo '(^| )(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])($|[[:space:]])'

Example:

echo 'some text 198.54.34.6 and test' | grep -Eo '(^| )(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])($|[[:space:]])'

output is:

 198.54.34.6

It will give spaces also try removing them using tr, like command1 | tr -d " ".

| improve this answer | |
  • But that would match the 1.1.168.192 in 1.1.168.192.in-addr.arpa. or 1.14.2.90 in xserver-common_1.14.2.901-2_all.deb for instance. The OP mentions trying to find words. – Stéphane Chazelas Sep 7 '19 at 13:19
  • 1
    @StéphaneChazelas I think the w flag will work. – Prvt_Yadav Sep 7 '19 at 15:44
  • It wouldn't for the first example as . is not a word character. It depends on what the OP's data may contain that we wouldn't want to be included. If there may be things like version numbers or SNMP OIDs, then -w wouldn't prevent them from being mistakenly considered as containing IP addresses. If there's not then -w might be find. grep -w '[0-9.]{7,}' may also be enough depending on what the data may contain. – Stéphane Chazelas Sep 7 '19 at 17:25
  • but how about !2.2.1.2 ? by using -w this is still a valid ip – Robert Dumbrava Sep 9 '19 at 11:14
  • @RobertDumbrava see the edit. – Prvt_Yadav Sep 9 '19 at 19:18
3

^ and $ match respectively at the beginning and end of the line, so lines with those IP addresses are only matched if the IP address is both at the beginning and end of the line, that is if the IP address is the whole line.

Now, if you want to match on lines that contain an IP address as a whole word, where words are blank-delimited, you could use:

d='[01234567890]'
n="($d|[123456789]$d|1$d$d|2[01234]$d|25[012345])"
grep -E "(^|[[:blank:]])$n\.$n\.$n\.$n([[:blank:]]|\$)" ip.txt

(here also replacing those [0-9] with [0123456789] as [0-9] often matches a lot more than just 0123456789).

Note that grep outputs the lines that match. If you want to only output parts of the lines, you'd need to use things like sed or perl instead (stream editors), or use some non-standard extensions of some grep implementations like the -o of GNU grep.

Here using negative look-around operators ((?<!\H) meaning "provided it's not preceded by a non-blank", (?!\H) same but looking forward instead of behind, (?1) recalls the RE in the first (...) group, all perl-like operators enabled with -P:

grep -Po '(?<!\H)(25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)\.(?1)\.(?1)\.(?1)(?!\H)' ip.txt

Which would be an equivalent of:

perl -lne 'print for
  /(?<!\H)(25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)\.(?1)\.(?1)\.(?1)(?!\H)/g'
| improve this answer | |
  • You should try testing your answers, none of these ideas worked for me. – A.Danischewski Sep 8 '19 at 10:39
  • @A.Danischewski you're right, it was pretty sloppy of me. It should be better now. – Stéphane Chazelas Sep 8 '19 at 14:14
2

To print only the IPv4's you could extract what is matched with the -o option to grep.

As a simpler example, you can do:

$ echo "this is a simple test to extract 123.234.34.5 as an IP" | 
       grep -o '[0-9.]*'

123.234.34.5

But that will fail to precisely match one IPv4.

To match an IP is somewhat complex with a regex. A regex do not understand numeric ranges, only text. We can match one numeric 0-255 value with (a regex that ignore spaces and comments):

25[012345]                       | # the numers 250 - 255 or
2[01234](?P<digit>[0123456789])  | # 200 - 249 or
1(?&digit){2}                    | # 100 - 199 or
#0?                                 # Allow leading zero
[1-9](?&digit)                   | #  10 -  99 or
#0{0,2}                             # Allow leading zeros
(?&digit)                          #   0 - 9

Remove the first column comments to include leading zeros.

To avoid the repetition of [0-9], to use explicit numeric ranges (avoid matching numbers in other languages) and to give a name to each matching group we can use "Named Capture Groups" from PCRE's:

(?P<byte>                             # Define this as one full byte value.
25[012345]                          | # the numers 250 - 255 or
2[01234](?P<digit>[0123456789])     | # 200 - 249 or
1(?&digit){2}                       | # 100 - 199 or
#0?                                    # Allow leading zero
[1-9](?&digit)                      | #  10 -  99 or
#0{0,2}                               # Allow leading zeros
(?&digit)                             #   0 - 9
)                                     # close one full byte definition

Then we just need to repeat the use of the byte definition with a leading dot (\.(?&byte)){3} three more times and some leading and trailing markers as shown in this link

The leading and trailing markers could be simpler "word boundaries" (\b) if that is what is needed as shown in this other link

In the shell, with grep PCRE regex, the command could be written as:

$ grep -oP '(?xm)(?<=^|[^01234567890.])(?P<byte>25[012345]|2[01234](?P<digit>[0123456789])|[01]?(?&digit){1,2})(\.(?&byte)){3}(?=[^01234567890.]|$)' <<<"$a"
1.2.3.4
11.22.33.44
123.234.34.5
1.1.192.168
123.234.34.123
123.234.34.123
1.2.3.255
255.255.255.255
1.1.168.192
1.14.2.90
1.2.3.4

Given that the test string contains:

$ a='1.2.3.4
11.22.33.44
123.234.34.5
1.1.192.168
text 123.234.34.123 more text
text123.234.34.123more text
1.2.3.255
1.2.3.256
255.255.255.255
256.2.3.4
1123.234.34.123
123.234.34.1235
.123.234.34.123
123.234.34.123.
not 1.1.168.192 in 1.1.168.192.in-addr.arpa.
not 1.14.2.90 in xserver-common_1.14.2.901-2_all.deb
1.2.3.4'
| improve this answer | |
1

This regex will grab the ip address and is a fairly accurate regex for IPv4 addresses, grep -P turns on PCRE regex engine and -o only returns the matching text:

grep -Po '\b((?:25[0-5]|[2][0-4][0-9]|[1][0-9]{2}|[1-9][0-9]|[0-9])\.){3}(?:25[0-5]|[2][0-4][0-9]|[1][0-9]{2}|[1-9][0-9]|[0-9])\b' ip.txt

For an explanation of this regex see this: https://regexr.com/4kjg4

25[0-5]          | # 250 - 255 
[2][0-4][0-9]    | # 200 - 249 
[1][0-9]{2}      | # 100 - 199 
[1-9][0-9]       | #  10 - 99
[0-9]            | #   0 - 9
| improve this answer | |
  • The OP already has a regexp to match quad-decimal IPv4 addresses (yours also include some octal-based ones like 010.010.010.010, another wording for google's 8.8.8.8 but doesn't allow 0377.0377.0377.0377 as an equivalent of 255.255.255.255). They're asking about how to find IP addresses as words in the input. – Stéphane Chazelas Sep 7 '19 at 17:28
  • Updated, now it grabs only IPv4 words. – A.Danischewski Sep 8 '19 at 1:52
  • @StéphaneChazelas It is known that interpreting IPv4 dotted addresses as octal leads to problems and there is known use of full 3 digits (with leading zeros) in RFCs – Isaac Sep 8 '19 at 4:08
  • @Isaac [Page 7] A 32-bit IPv4 address is divided into four octets. Each octet is represented numerically in decimal, using the minimum possible number of digits (leading zeroes are not used, except in the case of 0 itself). The four encoded octets are given most-significant first, separated by period characters. – A.Danischewski Sep 8 '19 at 10:32
  • @Isaac IPv4address = d8 "." d8 "." d8 "." d8 d8 = DIGIT ; 0-9 / %x31-39 DIGIT ; 10-99 / "1" 2DIGIT ; 100-199 / "2" %x30-34 DIGIT ; 200-249 / "25" %x30-35 ; 250-255 – A.Danischewski Sep 8 '19 at 10:33

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.