I'm fairly new to Bash and I'm running into an issue. I've got the following output:
159.180.30.159 BVI19987
159.180.30.160 BVI19987
159.180.30.161 BVI19987
159.180.30.162 BVI19987
159.180.30.163 BVI19987
159.180.30.164 BVI19987
159.180.30.165 BVI19987
159.180.30.166 BVI19987
159.180.30.167 BVI19987
159.180.30.168 BVI19987
159.180.30.170 BVI19987
159.180.30.171 BVI19987
159.180.30.172 BVI19987
159.180.30.173 BVI19987
159.180.30.174 BVI19987
159.180.30.175 BVI19987
159.180.30.176 BVI19987
159.180.30.177 BVI19987
159.180.30.178 BVI19987
159.180.30.179 BVI19987
159.180.30.180 BVI19987
159.180.30.181 BVI19987
159.180.30.182 BVI19987
159.180.30.183 BVI19987
159.180.30.184 BVI19987
159.180.30.185 BVI19987
159.180.30.186 BVI19987
159.180.30.187 BVI19987
What would be the easiest way to only select ip's from 167 to 180 and get this output:
159.180.30.167 BVI19987
159.180.30.168 BVI19987
159.180.30.170 BVI19987
159.180.30.171 BVI19987
159.180.30.172 BVI19987
159.180.30.173 BVI19987
159.180.30.174 BVI19987
159.180.30.175 BVI19987
159.180.30.176 BVI19987
159.180.30.177 BVI19987
159.180.30.178 BVI19987
159.180.30.179 BVI19987
159.180.30.180 BVI19987
CodePudding user response:
Like this, the straightforward way:
awk -F'[ .]' '167 <= $4 && $4 <= 180' file
-F[ .]
split columns on the.
and$4
is the 4th field
CodePudding user response:
Regular expressions are not very good for ranges. You can hardcode one like
grep -E '^159.180.30.1(6[7-9]|7[0-9]|80)' file
A better approach in this case is to use Awk with a custom separator.
awk -F '.' '($4 0) >= 167 && ($4 0) <= 180' file
Adding 0 to the field forces it to be a pure number, to avoid the behavior where Awk applies lexical sort order to strings (where 17 is "bigger than" 167).
You can obviously add more conditions, like
awk -F '.' '$1 == 159 && $2 == 180 && $3 == 30 && ($4 0) >= 167 && ($4 0) <= 180' file
or add a regex like
awk -F '.' '/^159\.180\.30\./ && ($4 0) >= 167 && ($4 0) <= 180' file
When you have a more complex range, like for example a /21 which starts in a single-digit network but straddles a boundary to a dal-digit one, you might be better off with a tool which understands the notation. Quick Duck Duck Going finds e.g. IPAddress or CIDR block matching regex with some simple Python scripts.
CodePudding user response:
In pure Bash:
while IFS= read -r line; do
IFS=$'. \t\n' read -ra fields <<< "$line"
((fields[3] >= 167 && fields[3] <= 180)) && printf '%s\n' "$line"
done < input_file
In awk
(shorter and with “more universal” whitespace matching):
awk -F'[[:space:].]' '$4 >= 167 && $4 <= 180' < /tmp/input
(Side note: This approach hurts the internet and should be avoided; if IPv4 cannot be avoided, consider IPv4-mapped IPv6 addresses in their hexadecimal-only form, for broader compatibility.)
CodePudding user response:
To properly parse IPv4, better use a proper parser, here in Perl
:
perl -F'\s|\.' -MRegexp::Common -lane '
print if /^$RE{net}{IPv4}/ && $F[3] >= 157 && $F[3] <= 180
' file
Check perldoc Regexp::Common
CodePudding user response:
A simple sed
program can do it:
sed -n -E '/^([0-9]{1,3}\.){3}167 /,/^([0-9]{1,3}\.){3}180 /p'
It prints (p
) each line starting with the one that matches the first regex (([0-9]{1,3}\.){3}167
) until and including the one that matches the second regex.
The regular expressions match three groups ({3}
) of 1 to 3 digits ([0-9]{1,3}
) each of them followed by a dot (\.
), followed by 167
(or 180
) followed by a space. Use \t
instead of space (
) if your input uses tabs to separate the columns. Or drop it altogether.
The code above does not care if the groups of digits and dots represent valid addresses or anything else. It only relies on the fact that the lines to be output are consecutive. It does not do any numeric comparison and it does not understand that 170
is between 167
and 180
. All it cares about are the start line and the end line.
If the input contains IP addresses from multiple ranges and you want only those starting with 159.180.30.
then replace ([0-9]{1,3}\.){3}
with a more specific expression:
sed -n -E '/^159\.180\.30\.167 /,/^159\.180\.30\.180 /p'
The -n
option tells sed
to not print each input line (as it does by default). The p
command is the one that prints the lines that match.
The -E
option tells sed
to use modern regular expressions. By default sed
uses basic regular expressions; the program can be expressed using them too but it would be more verbose because the basic regular expressions are more simple and do not support some new additions to the regular expressions syntax (well, they were "new" 30 years ago but sed
is 20 years older than that.)