Given that you are already using grep -P
, you can simply add a negative assertion:
ipa=$(ifconfig | grep -Po 'inet addr:\K(?!127\.)\d{1,3}.\d{1,3}\.\d{1,3}\.\d{1,3}')
Your original question's regex (hence edited) would also accept zero numbers between the dots; I fixed that as well and simplified the result for hopefully slightly improved legibility.
The \K
is a Perl innovation which says "if you match through to here, forget the text which got to this point" which means the match on inet addr:
will not be included in the "matched text" printed by grep -o
.
The expression (?!127\.)
is a negative lookahead assertion. In brief, it says "if this regex would match now, this is not a match". In other words, the regex engine takes a brief pause, takes a note of where it is in the text, and "peeks ahead" and attempts to match 127\.
. If that succeeds, it gives up on attempting to match at this point, and proceeds to attempt to match the entire expression at a later point in the string (so if it were to find a second occurrence of inet addr:
later on in the same line, you could still get a match from there).
Finally, I switched the quoting to single quotes. It doesn't really matter a lot here, but I recommend single quotes around all regular expressions unless you specifically require the shell to perform variable replacements in the regex or something like that.
As for explaining what you saw, there is no space in the output really. The grep
outputs two lines because it finds two matches (which of course we now prevent with the negative lookahead; but if you have multiple interfaces configured, you could still get more than one result). If you are seeing a space, that's because you didn't use double quotes when echoing, as in echo "$ipa"
.
As noted in comments, if you get bash: !127: event not found
, you need to set +H
or put the commands in a script; or, use single quotes like I recommend in the previous paragraph. Unless you are addicted to the legacy Csh-style history management features in Bash (and seriously, who is, these days?), I recommend you make this change permanent by putting the command set +H
in your .bash_profile
or similar.
Optional: Refactor the Regex
You could refactor your regex to make it more compact but perhaps slightly less legible:
ipa=$(ifconfig | grep -Po 'inet addr:\K(?!127\.)\d{1,3}(?:.\d{1,3}){3}')
An even shorter way would be this:
ipa=$(ifconfig | grep -Po 'inet addr:\K(?!127\.)[.\d]+')
Note the same \K
and (?!127\.)
patterns, but also the new [.\d]+
which replaces the \d{1,3}.\d{1,3}\.\d{1,3}\.\d{1,3}')
pattern. This is slightly less precise, but probably good enough for this scenario. If your input comes from ifconfig
and you have already seen the inet addr:
signpost, matching as many digits and dots as possible should always get you the IP address you are looking for.
Depending on what you need this for, you could still add more things to block in the lookahead. To prevent it from also matching internal networks, something like
(?!127\.|10\.|172\.(?:1[6-9]|2[0-9]|3[01])|192\.168\.)
would prevent extraction of addresses in all IANA-reserved private network blocks, including loopback.