Read and Validate IP address from user config file with BASH sed? - bash

I have an application where I want the user to be able to enter ip addresses that are saved to a conf file. The addresses need to be checked to ensure they are valid ip addresses (xxx.xxx.xxx.xxx)
Given that this is a user set persistent value running on a user application (ie. not root), the conf file must reside in a user folder. I have chosen the user home directory (Raspbian).
The conf file test sample looks like this:
interface=eth0
ip_address=172.30.21.40
routers=172.30.21.1
domain_name_server_1=199.85.126.30
damaim_name_server_2=8.8.8.8
If the user saves a valid ip_address, I want to read and store this in a variable .
If the user saves an invalid ip_address, then I want to read and discard the ip address and return an empty string.
I have looked at range of options to do this.
I looked at using source, but I found this requires the conf to be executable. That would add the risk of a user injecting executable code into the conf file.
I think I should be able to read, check and store the ip_address value in a one line sed command, but I just can't get it to work.
The test script is:
!/bin/bash
conf_file='/home/user/ip.conf'
v1="$(sed -n 's/\b(?:ip_address=)(((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))\b/\1/p' $conf_file)"
echo "The ip address is : $v1"
exit
To break this down into parts:
\b(?:ip_address=) # match the string "ip_address=" starting with a word separator \b
(((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))
^ ^
# This section checks the format and number range of the ip address. This is made up of three
# groups that are all contained with a set of brackets (marked with ^) to create a group 1 with
# the whole ip address. This is what I want to capture. This ends with a word separator \b
/\1/p # This is the substitution section where I specify group 1 and print to save to $v1.
When I run this command I get the error
sed: -e expression #1, char 110: invalid reference \1 on `s' command's RHS
When I enter:
\b(?:ip_address=)(((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))\b
into the online regex tester it works without error. It identifies the full ip address as group 1.
The sed command doesn't seem to recognise the back reference \1 and I can't figure out what I am doing wrong.
Edit
I tried a simple command:
v1="$(sed -n -E 's/^\s*(interface=)(.*)\b/\2/p' $conf_file)"
This only worked correctly with the -E option added. This is based on an answer found here. I can't find any documentation on -E but it appears to enable extended regular expressions.

Too much. Big problems are sum of small problems - just take one little problem one at a time.
# Filter lines with ip_address. Allow leading spaces.
if ! ip_address=$(grep '^[[:space:]]*ip_address=' "$conf_file")l then
echo "ERROR: no line ip_Address found in config file" >&2
exit 2
fi
# dissallow two ip_address= lines
if [[ "$(printf "%s\n" "$ip_address" | wc -l)" -gt 1 ]]; then
echo "ERROR: There are two lines with ip_address in config file!" >&2
exit 2
fi
# remove the string before `=`
ip_address=${ip_address##*=}
# check if it's a valid address
re='^(0*(1?[0-9]{1,2}|2([0-4][0-9]|5[0-5]))\.){3}'
re+='0*(1?[0-9]{1,2}|2([‌​0-4][0-9]|5[0-5]))$'
if [[ ! $ip_address =~ $re ]]; then
echo "ERROR: ip_Address option is not valid ip address" >&2
exit 2
fi
echo "found ip_Address=$ip_address"
But sure, you can do it all in GNU sed, including error handling:
if ! ip_address=$(sed -n '
# if its the last line i should have ip_address in hold space
${
x
# if I dont, that means error
/^$/{
s/.*/ERROR: no ip_Address found in the file/
p
q 1
}
# print the ip address
p
}
# remove lines that are not ip_Addresses
/^[[:space:]]*ip_address=/!{d;b}
# remove ip_address= strnig
s///
# if there is something in hold space, means we already found ip_address
x
/^$/!{
s/.*/ERROR: two lines with ipaddress found/
p
q 1
}
x
# check if the rest is a valid ip addresss
/^\(0*\(1\?[0-9]\{1,2\}\|2\([0-4][0-9]\|5[0-5]\)\)\.\)\{3\}0*\(1\?[0-9]\{1,2\}\|2\([0-4][0-9]\|5[0-5]\)\)$/!{
s/.*/ERROR: Invalid ip address: &/
p
q 1
}
# hold the valid ip_address in hold space
h
' "$conf_file"); then
echo "$ip_address" >&2
exit 2
fi
echo "Found ip_address=$ip_address"
I believe your idea was to do it just like:
sed -n -E 's/^ip_address=(((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))$/\1/p' "$conf_file"
which would be "good enough", but will be silent if user makes a mistake.

Thanks to assistance in the comments, the problem was found to be the ?: term in the regex. sed couldn't process that. Here is a demo script of the solution I was looking for:
#!/bin/bash
conf_file='/home/user/ip.conf'
v1=$(sed -n -E 's/^\s*(ip_address=)(((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))\s(.*)$/\2/p' $conf_file)
echo "The ip address is : $v1"
v2="$(sed -n -E 's/^\s*(interface=)(.*)\s(.*)/\2/p' $conf_file)"
echo "The interface is : $v2"
v3=$(sed -n -E 's/^\s*(routers=)(((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))\b(.*)/\2/p' $conf_file)
echo "The router ip is : $v3"
exit 0
The test config file, which intentionally includes errors, is:
interface=eth0 #comment
ip_address=172.30.21.40 # comment
ip_address=123.30.21.40 comment
ip_address=1234.123.30.21.40
ip_address=ab3.dd30.21.40
routers=172.30.21.1 172.123.456.234
domain_name_server_1=199.85.126.30
damaim_name_server_2=8.8.8.8
The output is:
The ip address is : 172.30.21.40
123.30.21.40
The interface is : eth0
The router ip is : 172.30.21.1
The error detection isn't perfect (it doesn't gracefully handle duplicate lines), and for my application it doesn't need to be. It is good enough. In my application, users will not have direct access to the command line or their home directory so this script is intended to be a second line of defense against bad input. Your requirements might vary.
This one line of code completes a number of tasks.
open file,
searches for a option name,
reads the option value,
checks the option value format/content against the regex filter,
ignores other text and comments on the same line,
returns the option value if valid, or "" if not and,
saves to a variable.
If the entry is missing or invalid, the return value will be "". In that case, a default value will be used (code not shown).
Getting it to work took way too much time but I learnt a lot about regex and sed. Now that it works, it is easy to adapt to read other than ip addresses, as shown in the test files.

Related

it only display a number "2" when using while read line to redirect the content of one file to another

The content of the script is:
#!/bin/bash
tempconf="/tmp/test.file"
while read line
do
echo $line
done < test.conf > $tempconf
The content of the test.conf is:
[PORT]
tcp_ports=7000-7200
udp_ports=7000-8000, 40000-49999
[H323H]
maxSendThreads=10
maxRecvThreads=10
[SDK]
appPwd=1111111
amsAddress=192.168.222.208:8888
The content of the output file "/tmp/test.file" is:
[PORT]
tcp_ports=7000-7200
udp_ports=7000-8000, 40000-49999
2
maxSendThreads=10
maxRecvThreads=10
[SDK]
appPwd=1111111
amsAddress=192.168.222.208:8888
The question is,why [H323H] turns out to be 2. I'll be appreciated if anyone can explain it to me.
[] has a special meaning for the shell, it just means "a single character taken from any of the characters between the brackets". So when you run
echo [H323H]
the shell looks for a file named or H, or 2, or 3... If at least one file matches, [H323H] is replaced with all the matching file names in the output; otherwise it's reproduced as is.
source: https://unix.stackexchange.com/a/259385
Using quotes around $line would solve your problem without the need to check for files matching those characters (which would make the script not very robust)
#!/bin/bash
tempconf="/tmp/test.file"
while read -r line
do
echo "$line"
done < test.conf > "$tempconf"

Bash: Extract user path (/home/userID) from read line containing full path and replace with "~"

I'm constructing a bash script file a bit at a time. I'm learning as I
go. But I can't find anything online to help me at this point: I need to
extract a substring from a large string, and the two methods I found using ${} (curly brackets) just won't work.
The first, ${x#y}, doesn't do what it should.
The second, ${x:p} or ${x:p:n}, keeps reporting bad substitution.
It only seems to work with constants.
The ${#x} returns a string length as text, not as a number, meaning it does not work with either ${x:p} or ${x:p:n}.
Fact is, it's seems really hard to get bash to do much math at all. Except for the for statements. But that is just counting. And this isn't a task for a for loop.
I've consolidated my script file here as a means of helping you all understand what it is that I am doing. It's for working with PureBasic source files, but you only have to change the grep's "--include=" argument, and it can search other types of text files instead.
#!/bin/bash
home=$(echo ~) # Copy the user's path to a variable named home
len=${#home} # Showing how to find the length. Problem is, this is treated
# as a string, not a number. Can't find a way to make over into
# into a number.
echo $home "has length of" $len "characters."
read -p "Find what: " what # Intended to search PureBasic (*.pb?) source files for text matches
grep -rHn $what $home --include="*.pb*" --exclude-dir=".cache" --exclude-dir=".gvfs" > 1.tmp
while read line # this checks for and reads the next line
do # the closing 'done' has the file to be read appended with "<"
a0=$line # this is each line as read
a1=$(echo "$a0" | awk -F: '{print $1}') # this gets the full path before the first ':'
echo $a0 # Shows full line
echo $a1 # Shows just full path
q1=${line#a1}
echo $q1 # FAILED! No reported problem, but failed to extract $a1 from $line.
q1=${a0#a1}
echo $q1 # FAILED! No reported problem, but failed to extract $a1 from $a0.
break # Can't do a 'read -n 1', as it just reads 1 char from the next line.
# Can't do a pause, because it doesn't exist. So just run from the
# terminal so that after break we can see what's on the screen .
len=${#a1} # Can get the length of $a1, but only as a string
# q1=${line:len} # Right command, wrong variable
# q1=${line:$len} # Right command, right variable, but wrong variable type
# q1=${line:14} # Constants work, but all $home's aren't 14 characters long
done < 1.tmp
The following works:
x="/home/user/rest/of/path"
y="~${x#/home/user}"
echo $y
Will output
~/rest/of/path
If you want to use "/home/user" inside a variable, say prefix, you need to use $ after the #, i.e., ${x#$prefix}, which I think is your issue.
The hejp I got was most appreciated. I got it done, and here it is:
#!/bin/bash
len=${#HOME} # Showing how to find the length. Problem is, this is treated
# as a string, not a number. Can't find a way to make over into
# into a number.
echo $HOME "has length of" $len "characters."
while :
do
echo
read -p "Find what: " what # Intended to search PureBasic (*.pb?) source files for text matches
a0=""; > 0.tmp; > 1.tmp
grep -rHn $what $home --include="*.pb*" --exclude-dir=".cache" --exclude-dir=".gvfs" >> 0.tmp
while read line # this checks for and reads the next line
do # the closing 'done' has the file to be read appended with "<"
a1=$(echo $line | awk -F: '{print $1}') # this gets the full path before the first ':'
a2=${line#$a1":"} # renove path and first colon from rest of line
if [[ $a0 != $a1 ]]
then
echo >> 1.tmp
echo $a1":" >> 1.tmp
a0=$a1
fi
echo " "$a2 >> 1.tmp
done < 0.tmp
cat 1.tmp | less
done
What I don't have yet is an answer as to whether variables can be used in place of constants in the dollar-sign, curly brackets where you use colons to mark that you want a substring of that string returned, if it requires constants, then the only choice might be to generate a child scriot using the variables, which would appear to be constants in the child, execute there, then return the results in an environmental variable or temporary file. I did stuff like that with MSDOS a lot. Limitation here is that you have to then make the produced file executable as well using "chmod +x filename". Or call it using "/bin/bash filename".
Another bash limitation found it that you cannot use "sudo" in the script without discontinuing execution of the present script. I guess a way around that is use sudo to call /bin/bash to call a child script that you produced. I assume then that if the child completes, you return to the parent script where you stopped at. Unless you did "sudo -i", "sudo -su", or some other variation where you become super user. Then you likely need to do an "exit" to drop the super user overlay.
If you exit the child script still as super user, would typing "exit" but you back to completing the parent script? I suspect so, which makes for some interesting senarios.
Another question: If doing a "while read line", what can you do in bash to check for a keyboard key press? The "read" option is already taken while in this loop.

Bash script to replace or append

I'm new to Bash scripting and I'm having a bit of a hard time. I'm trying to alter the configuration values of a config file. If it finds an existing value I want it to update it, but if it doesn't exist I want it to append it. This is as far I as I got from various tutorials and snippets online:
# FUNCTION TO MODIFY CONFIG BY APPEND OR REPLACE
# $1 File
# $2 Find
# $3 Replace / Append
function replaceappend() {
grep -q '^$2' $1
sed -i 's/^$2.*/$3/' $1
echo '$3' >> $1
}
replaceappend "/etc/test.conf" "Port 20" "Port 10"
However as you might imagine this doesn't work. It seems to be with the logic behind it, I'm not sure how to capture the result of grep in order to choose either sed or echo.
Just use the return value of the command and use double-quotes instead of single quotes:
if ! sed -i "/$2/{s//$3/;h};"'${x;/./{x;q0};x;q1}' $1
then
echo "$3" >> $1
fi
SOURCE: Return code of sed for no match for the q command
This is treading outside my normal use of sed, so let me give an explanation of how this works, as I understand it:
sed "/$2/{s//$3/;h};"'${x;/./{x;q0};x;q1}' $1
The first /$2/ is an address - we will do the commands within {...} for any lines that match this. As a by-product it also sets the pattern-space to $2.
The command {s//$3/;h} says to substitute whatever is in the pattern-space with $3 and then save the pattern-space in the "hold-space", a type of buffer within sed.
The $ after the single quote is another address - it says to do this next command on the LAST line.
The command {x;/./{x;q0};x;q1} says:
x = swap the hold-space and the pattern-space
/./ = an address which matches anything
{x;q0} = swap the hold-space and the pattern-space - if this is successful (there was something in the hold-space) then q0=exit with 0 status (success)
x;q1 = swap the hold-space and the pattern-space - since this is now successful (due to the previous x) then q1=exit with 1 status (fail)
The double-quotes around the first part allow substitution for $2 and $3. The single quotes around the latter part prevents erroneous substitution for the $.
A bit complicated, but it seems to work AS LONG AS YOU HAVE SOMETHING IN THE FILE. An empty file will still succeed since you don't get any match on the last line.
To be honest, after all this complication... Unless the files you are working with are really long so that a double-pass would be really bad I would probably go back to the grep solution like this:
if grep -q "^$2" $1
then
sed -i "s/^$2.*$/$3/" $1
else
echo "$3" >>$1
fi
That's a WHOLE lot easier to understand and maintain later...

Wait for Network Interface Before Executing Command

I have a couple ideas on how I would achieve this. Not sure how I would script it.
Method 1: (probably the better choice)
Create a loop that pings a server until reply is received then execute command
if no reply is received in X amount of time/runs continue script.
Method 2:
Check if network interface has valid IP then continue script
How would one go about adding this functionality in a script. Would awk or grep be of use in a situation like this? Thank you in advance for ANY input.
This command should wait until it can contact google or it has tried 50 times:
for i in {1..50}; do ping -c1 www.google.com &> /dev/null && break; done
The for i in {1..50} loops 50 times or until a break is executed. The ping -c1 www.google.com sends 1 ping packet to google, and &> /dev/null redirects all the output to null, so nothing is outputed. && break executes break only if the previous command finished successfully, so the loop will end when ping is successful.
I've tested this on a board which is configured via DHCP.
The assumption is that if a default gateway exists on a specific interface (in this case eth0), then this is due to the fact that the board has gotten assigned an IP (and thus the default gateway) by the DHCP server, which implies that networking is up and running.
My issue was that for me networking is considered to be up as soon as machines in the LAN/intranet can be accessed, even if there exists no internet connectivity (ie 8.8.8.8 or www.google.com is not accessible). I also don't want to ping specific IPs or domain names in the intranet because I don't want to make assumptions about the subnet or which devices will definitely be up and what their IP or domain name is.
while ! ip route | grep -oP 'default via .+ dev eth0'; do
echo "interface not up, will try again in 1 second";
sleep 1;
done
How would one go about adding this functionality in a script
-- Geofferey
Following be part of how I'm going about solving a similar situation in a modular fashion...
await-ipv4-address.sh
#!/usr/bin/env bash
## Lists IP addresses for given interface name
## #returns {number|list}
## #param {string} _interface - Name of interface to monitor for IP address(es)
## #param {number} _sleep_intervel - Number of seconds to sleep between checks
## #param {number} _loop_limit - Max number of loops before function returns error code
## #author S0AndS0
## #copyright AGPL-3.0
## #exampe As an array
## _addresses_list=($(await_ipv4_address 'eth0'))
## printf 'Listening address: %s\n' "${_addresses_list[#]}"
## #> Listening address: 192.168.0.2
## #> Listening address: 192.168.0.4
## #example As a string
## _addresses_string="$(await_ipv4_address 'eth0' '1' '3')"
## printf 'Listening address(es): %s\n' "${_addresses_string}"
## #> Listening address(es): 192.168.0.2 192.168.0.4
await_ipv4_address(){
local _interface="${1:?# Parameter_Error: ${FUNCNAME[0]} not provided an interface}"
local _sleep_interval="${2:-1}"
local _loop_limit="${3:-10}"
if [ "${_sleep_interval}" -lt '0' ] || [ "${_loop_limit}" -le '0' ]; then
printf 'Parameter_Error: %s requires positive numbers for second and third parameters\n' "${FUNCNAME[0]}" >&2
return 1
fi
local _loop_count='0'
local -a _ipv4_addresses
while true; do
for _address in $({ ip addr show ${_interface} | awk '/inet /{print $2}'; } 2>/dev/null); do
_ipv4_addresses+=("${_address}")
done
if [ "${#_ipv4_addresses[#]}" -gt '0' ]; then
printf '%s\n' "${_ipv4_addresses[*]}"
break
elif [ "${_loop_count}" -gt "${_loop_limit}" ]; then
break
fi
let _loop_count+=1
sleep "${_sleep_interval}"
done
[[ "${#_ipv4_addresses[#]}" -gt '0' ]]; return "${?}"
}
Source for above are on GitHub bash-utilities/await-ipv4-address, check the ReadMe file for instructions on utilizing Git for updates and bug fixes.
To source the above function within current shell...
source "await-ipv4-address.sh"
... or within another script...
#!/usr/bin/env bash
## Enable sourcing via absolute path
__SOURCE__="${BASH_SOURCE[0]}"
while [[ -h "${__SOURCE__}" ]]; do
__SOURCE__="$(find "${__SOURCE__}" -type l -ls | sed -n 's#^.* -> \(.*\)#\1#p')"
done
__DIR__="$(cd -P "$(dirname "${__SOURCE__}")" && pwd)"
source "${__DIR__}/modules/await-ipv4-address/await-ipv4-address.sh"
Would awk or grep be of use in a situation like this?
-- Geofferey
Both may be used; though much like echoing I think greping is best done in the privacy of one's own shell... I prefer awk in public as there's a whole scripting language to facilitate feature creep, and printf because it's not as likely to have missing features on slimmed-down environments.
Here's how to awk an address regardless of IPv4 vs. IPv6 flavor....
# ... trimmed for brevity
for _address in $({ ip addr show ${_interface} | awk '/inet/{print $2}'; } 2>/dev/null); do
# ... things that get done with an address
done
... just a difference in space to get more data.
Something I can have be compatible regardless of interface name or type
Three different interface name examples and how to overwrite some of the default behavior
Wait upwards of ten seconds for an IP on eth0
_ip_addresses_list=($(await_ipv4_address 'eth0'))
Wait upwards of thirty seconds for an IP on tun0
_ip_addresses_list=($(await_ipv4_address 'tun0' '1' '29'))
Wait upwards of a minuet for an IP on wlan0 while sleeping 3 seconds between checks
_ip_addresses_list=($(await_ipv4_address 'wlan0' '3' '19'))
Note, if await_ipv4_address gets board it'll return a non-zero status, so the following...
_ip_addresses_list=($(await_ipv4_address 'wlan0' '3' '19' || true))
... may be used if you've got error traps that get tripped by such things.
Then do stuff with the IP addresses once assigned...
for _ip_address in "${_ip_addresses_list[#]}"; do
printf 'IP -> %s\n' "${_ip_address}"
done
Wait for network interface to do what?
-- user207421
to be up, but more than up lol I need an active connection where I'm sure I can connect to internet
-- Geofferey
The above will not test for an active connection to the greater Internet, only if an IP address has been assigned via the local network switch/AP or static settings; though, a local IP is a prerequisite... consider the above part of an answer that is script friendly as it's only designed to preform one thing well.
To reliably detect if connections to the rest of the world wide web are permitted check out dig and curl, because a successful ping to one's favorite DNS does not mean other protocols are allowed.
Could you explain each part of your script so I am not blindly using something without having an understanding of how it works? -- Geofferey
... Sure...
await_ipv4_address(){
local _interface="${1:?# Parameter_Error: ${FUNCNAME[0]} not provided an interface}"
local _sleep_interval="${2:-1}"
local _loop_limit="${3:-10}"
# ...
}
local assigns locally scoped variables, help local and help declare will show some useful documentation on more advanced usage
"${something:?Error message}" will print Error message if something is not assigned
"${another_thing:-1}" will default to 1 if another_thing is not assigned or assigned an null value
Hint, man --pager='less -p ^"PARAMETERS"' bash through till end of Special Parameters section as well as the man --pager='less -p "Parameter Expansion"' bash section may be helpful in finding more things that can be done with variables and stuff.
if [ "${_sleep_interval}" -lt '0' ] || [ "${_loop_limit}" -le '0' ]; then
printf 'Parameter_Error: %s requires positive numbers for second and third parameters\n' "${FUNCNAME[0]}" >&2
return 1
fi
throws errors if either _sleep_interval or _loop_count are not numbers because of less-than (-lt) and less-than or equal-to (-le) checks
throws error if either of if checks return true, the || chains multiple checks such that if the left side returns false it trips the right side for a check, where as && would only fire if the left side returned true
hint man operator will show directionality of various operators
printf 'something\n' >&2 writes something to standard error; where all well-behaved errors should be written so that logs can be made or output ignored
shows a level of paranoia about function inputs that may be excessive
while true; do
# ... stuff
done
should be used with care because if state is not checked and updated correctly the loop will never exit.
for _address in $({ ip addr show ${_interface} | awk '/inet /{print $2}'; } 2>/dev/null); do
_ipv4_addresses+=("${_address}")
done
the $({ command | parser; } 2>/dev/null) trick is something that I picked-up from around these parts
$(something) runs something within a sub-shell
{ one_thing | another_thing; } is a compound command
Hint, man --pager='less -p "Compound Commands"' bash should show relevant documentation
2>/dev/null causes standard error to be written where no input returns
_preexisting_list+=("element") appends element to _preexisting_list by way of +=
if [ "${#_ipv4_addresses[#]}" -gt '0' ]; then
printf '%s\n' "${_ipv4_addresses[*]}"
break
elif [ "${_loop_count}" -gt "${_loop_limit}" ]; then
break
fi
if part checks if the number of elements within _ipv4_addresses are greater than 0 via the # sign, ${#_list_name[#]}
elif part checks if function should be board by now
In either case a break from the while loop is taken when logic is tripped.
let _loop_count+=1
sleep "${_sleep_interval}"
let _counter+=1 will add 1 to whatever previous value was in _counter and assign it to _counter
sleep causes loop to chill out for a number of seconds so other things can be contemplated by the device
[[ "${#_ipv4_addresses[#]}" -gt '0' ]]; return "${?}"
Bash semicolons with testing brackets ([[ is_it_true ]]) instead of || or && causes return to return the status of if the number of IP addresses found where greater than 0 regardless of truthiness of test
If there's something questionable after all that feel free to post a comment so that the answer can be improved.
You can do the following
until ifconfig -l | grep ppp0 >/dev/null 2>&1; do :; done
Works on MacOS

to write a shell script to grep IP and show to whom it belongs to?

To write a shell script program to grep IP (from the list given) and search the network for the IP whom it belongs to. Then if the IP belongs to vodafone, telstra, airtel, write them in a file separately for each operator.
So i wrote the script should be like :
for ip_address in `cat ip_address_list`;
do
whois $ip_address | grep 'descr' >> final_result
done
the output looks like :
$] whois 62.87.90.54 | grep descr
descr: GLOBAL MOBILE OPERATOR
descr: AIRTEL-NETWORK
descr: VODAFONE-NETWORK
Can we modify it more ? Can we include IF statement to separate vodafone etc to include them in a file ?
Or pls mention a good script than mine ?
Pls advise !
for ip_address in $(cat ip_address_list); do
whois $ip_address | grep 'descr' \
| while read line ; do
case "${line}" in
*GLOBAL\ MOBILE\ OPERATOR* )
printf "${line}\n" >> gmo
;;
*AIRTEL-NETWORK* )
printf "${line}\n" >> air
;;
*VODAFONE-NETWORK* )
printf "${line}\n" >> voda
;;
* )
printf "${line}\n" >> all_others
;;
esac
done
done
Note that in the case statement, you need to escape any spaces in match targets, i.e. *GLOBAL\ MOBILE\ OPERATOR*.
Also, I don't have a way to test this right now, so hopefully there are not too many syntax errors :-). If you do find problems, let's try and setup a chat.
And, if you are sure whois produces only one line of output per request, you probably dont't need the while loop. whois $ip | read line ; case "${line}" .... might work.
Actually managing the constantly increasing files, air, voda, gmo may require some thought on your part.
I hope this helps.

Resources