Check for IP validity - linux

How do I check the validity of an IP address in a shell script, that is within the range 0.0.0.0 to 255.255.255.255?

If you're using bash, you can do a simple regex match for the pattern, without validating the quads:
#!/usr/bin/env bash
ip=1.2.3.4
if [[ $ip =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "success"
else
echo "fail"
fi
If you're stuck with a POSIX shell, then you can use expr to do basically the same thing, using BRE instead of ERE:
#!/bin/sh
ip=1.2.3.4
if expr "$ip" : '[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*$' >/dev/null; then
echo "success"
else
echo "fail"
fi
Note that expr assumes that your regex is anchored to the left-hand-side of the string, so the initial ^ is unnecessary.
If it's important to verify that each quad is less than 256, you'll obviously require more code:
#!/bin/sh
ip=${1:-1.2.3.4}
if expr "$ip" : '[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*$' >/dev/null; then
for i in 1 2 3 4; do
if [ $(echo "$ip" | cut -d. -f$i) -gt 255 ]; then
echo "fail ($ip)"
exit 1
fi
done
echo "success ($ip)"
exit 0
else
echo "fail ($ip)"
exit 1
fi
Or perhaps even with fewer pipes:
#!/bin/sh
ip=${1:-1.2.3.4}
if expr "$ip" : '[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*$' >/dev/null; then
IFS=.
set $ip
for quad in 1 2 3 4; do
if eval [ \$$quad -gt 255 ]; then
echo "fail ($ip)"
exit 1
fi
done
echo "success ($ip)"
exit 0
else
echo "fail ($ip)"
exit 1
fi
Or again, if your shell is bash, you could use a cumbersome regular expression for quad validation if you're not fond of arithmetic:
#!/usr/bin/env bash
ip=${1:-1.2.3.4}
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 =~ $re ]]; then
echo "success"
else
echo "fail"
fi
This could also be expressed in BRE, but that's more typing than I have in my fingers.
And lastly, if you like the idea of putting this functionality ... in a function:
#!/usr/bin/env bash
ip=${1:-1.2.3.4}
ipvalid() {
# Set up local variables
local ip=${1:-NO_IP_PROVIDED}
local IFS=.; local -a a=($ip)
# Start with a regex format test
[[ $ip =~ ^[0-9]+(\.[0-9]+){3}$ ]] || return 1
# Test values of quads
local quad
for quad in {0..3}; do
[[ "${a[$quad]}" -gt 255 ]] && return 1
done
return 0
}
if ipvalid "$ip"; then
echo "success ($ip)"
exit 0
else
echo "fail ($ip)"
exit 1
fi
There are many ways you could do this. I've shown you just a few.

This single regex should validate only those addresses between 0.0.0.0 and 255.255.255.255:
#!/bin/bash
ip="1.2.3.4"
if [[ "$ip" =~ ^(([1-9]?[0-9]|1[0-9][0-9]|2([0-4][0-9]|5[0-5]))\.){3}([1-9]?[0-9]|1[0-9][0-9]|2([0-4][0-9]|5[0-5]))$ ]]; then
echo "success"
else
echo "fail"
fi

Use ipcalc ( tested with the version package in RPM initscripts-9.49.49-1)
$ ipcalc -cs 10.10.10.257 && echo vaild_ip || echo invalid_ip
invalid_ip

The script Validating an IP Address in a Bash Script
by Mitch Frazier does what you want to do:
function valid_ip()
{
local ip=$1
local stat=1
if [[ $ip =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
OIFS=$IFS
IFS='.'
ip=($ip)
IFS=$OIFS
[[ ${ip[0]} -le 255 && ${ip[1]} -le 255 \
&& ${ip[2]} -le 255 && ${ip[3]} -le 255 ]]
stat=$?
fi
return $stat
}

The typical solutions for this all seem to use regular expressions, but it occurs to me that it might be a better approach to do something like:
if echo "$ip" | { IFS=. read a b c d e;
test "$a" -ge 0 && test "$a" -le 255 &&
test "$b" -ge 0 && test "$b" -le 255 &&
test "$c" -ge 0 && test "$c" -le 255 &&
test "$d" -ge 0 && test "$d" -le 255 &&
test -z "$e"; }; then echo is valid; fi

i tweaked all the codes and found this to be helpful.
#!/bin/bash
ip="256.10.10.100"
if [[ "$ip" =~ (([01]{,1}[0-9]{1,2}|2[0-4][0-9]|25[0-5])\.([01]{,1}[0-9]{1,2}|2[0-4][0-9]|25[0-5])\.([01]{,1}[0-9]{1,2}|2[0-4][0-9]|25[0-5])\.([01]{,1}[0-9]{1,2}|2[0-4][0-9]|25[0-5]))$ ]]; then
echo "success"
else
echo "fail"
fi

I prefer to use ipcalc to do this, as long as my script doesn't have to be portable.
ipcalc 1.1.1.355
INVALID ADDRESS: 1.1.1.355
Address: 192.168.1.1 11000000.10101000.00000001. 00000001
Netmask: 255.255.255.0 = 24 11111111.11111111.11111111. 00000000
Wildcard: 0.0.0.255 00000000.00000000.00000000. 11111111
=>
Network: 192.168.1.0/24 11000000.10101000.00000001. 00000000
HostMin: 192.168.1.1 11000000.10101000.00000001. 00000001
HostMax: 192.168.1.254 11000000.10101000.00000001. 11111110
Broadcast: 192.168.1.255 11000000.10101000.00000001. 11111111
Hosts/Net: 254 Class C, Private Internet
There is a great page showing how to use it in scripting, etc, here:
SleeplessBeastie's Notes

If someone still looking for an answer just by using regex, below would work -
echo "<sample ip address>"|egrep "(^[0-2][0-5]{1,2}?\.|^[3-9][0-9]?\.)([0-2][0-5]{1,2}?\.|[3-9][0-9]?\.)([0-2][0-5]{1,2}?\.|[3-9][0-9]?\.)([0-2][0-5]{1,2}?$|[3-9][0-9]?$)"

Perl has a great module Regexp::Common for validating various things:
perl -MRegexp::Common=net -e 'exit(shift() !~ /^$RE{net}{IPv4}$/)' $ipaddr
You may need to sudo cpan install Regexp::Common first
I'd wrap it in a function:
valid_ip() {
perl -MRegexp::Common=net -e 'exit(shift() !~ /^$RE{net}{IPv4}$/)' "$1"
}
if valid_ip 123.234.345.456; then
echo OK
else
echo INVALID
fi

Alternate version that still does a thorough validation (meaning that it requires both a properly formatted IP address AND that each quadrant is within the range of allowed values aka 0-255). Works fine on GNU bash 4.4.20 (Linux Mint 19.3); no promises elsewhere but will prolly be fine as long as you have bash 4.
The initial format check regex is borrowed from the shannonman / Mitch Frazier answer above; the rest is my own.
function isValidIpAddr() {
# return code only version
local ipaddr="$1";
[[ ! $ipaddr =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]] && return 1;
for quad in $(echo "${ipaddr//./ }"); do
(( $quad >= 0 && $quad <= 255 )) && continue;
return 1;
done
}
function validateIpAddr() {
# return code + output version
local ipaddr="$1";
local errmsg="ERROR: $1 is not a valid IP address";
[[ ! $ipaddr =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]] && echo "$errmsg" && return 1;
for quad in $(echo "${ipaddr//./ }"); do
(( $quad >= 0 && $quad <= 255 )) && continue;
echo "$errmsg";
return 1;
done
echo "SUCCESS: $1 is a valid IP address";
}
$ isValidIpAddr '192.168.0.1'
$ echo "$?"
0
$ isValidIpAddr '192.168.0.256'
$ echo "$?"
1
$ validateIpAddr '12.1.10.191'
SUCCESS: 12.1.10.191 is a valid IP address
$ validateIpAddr '1.1.1.127'
SUCCESS: 1.1.1.127 is a valid IP address
$ validateIpAddr '1.1.1.1337'
ERROR: 1.1.1.1337 is not a valid IP address

We can use "ip route save" to do the check.
valid_addrmask()
{
ip -4 route save match $1 > /dev/null 2>&1
}
$ valid_addrmask 255.255.255.255 && echo "is valid" || echo "is not valid"
is valid
$ valid_addrmask 255.255.255.355 && echo "is valid" || echo "is not valid"
is not valid

#!/bin/bash
read -p " ip: " req_ipadr
#
ip_full=$(echo $req_ipadr | sed -n 's/^\(\(\([1-9][0-9]\?\|[1][0-9]\{0,2\}\|[2][0-4][0-9]\|[2][5][0-4]\)\.\)\{3\}\([1-9][0-9]\?\|[1][0-9]\{0,2\}\|[2][0-4][0-9]\|[2][5][0-4]\)\)$/\1/p')
#
[ "$ip_full" != "" ] && echo "$req_ipadr vaild ip" || echo "$req_ipadr invaild ip"

You can just copy the following code and change body of if else control as per your need
function checkIP(){
echo "Checking IP Integrity"
ip=$1
byte1=`echo "$ip"|xargs|cut -d "." -f1`
byte2=`echo "$ip"|xargs|cut -d "." -f2`
byte3=`echo "$ip"|xargs|cut -d "." -f3`
byte4=`echo "$ip"|xargs|cut -d "." -f4`
if [[ $ip =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ && $byte1 -ge 0 && $byte1 -le 255 && $byte2 -ge 0 && $byte2 -le 255 && $byte3 -ge 0 && $byte3 -le 255 && $byte4 -ge 0 && $byte4 -le 255 ]]
then
echo "IP is correct"
else
echo "This Doesn't look like a valid IP Address : $ip"
fi
}
checkIP $myIP
Calling the method with IP Address stored in a variable named myIP.
$ip =~ ^[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}$ - This part makes sure that IP consists of 4 blocks separated by a dot(.) but every block here is allowed to range from 0 - 999
Since desired range of every block would be 0 - 255, to make sure of that below line can be used.
$byte1 -ge 0 && $byte1 -le 255 && $byte2 -ge 0 && $byte2 -le 255 && $byte3 -ge 0 && $byte3 -le 255 && $byte4 -ge 0 && $byte4 -le 255

In the most simple form:-
#!/bin/bash
while true;
do
read -p "Enter a ip: " IP
echo "${IP}" > ip.txt
OCT1=$(cat ip.txt | awk -F "." '{print $1}')
OCT2=$(cat ip.txt | awk -F "." '{print $2}')
OCT3=$(cat ip.txt | awk -F "." '{print $3}')
OCT4=$(cat ip.txt | awk -F "." '{print $4}')
REGEX_IP='^[0-9]{1,3}[.][0-9]{1,3}[.][0-9]{1,3}[.][0-9]{1,3}$'
if [[ ${IP} =~ ${REGEX_IP} ]]
then
if [[ ${OCT1} -gt 255 || ${OCT2} -gt 255 || ${OCT3} -gt 255 || ${OCT4} -gt 255 ]]
then
echo "Please enter a valid ip"
continue
fi
break
else
echo "Please enter a valid ip"
continue
fi
done
This will cover all the scenarios.

May be it is usefull
#this script verify either a ip address is valid or not as well as public or local ip
#$1 means supplied first argument
ip=$(echo $1 | gawk '/^[0-9]{1,3}\.[0-9]{1,3}+\.[0-9]{1,3}+\.[0-9]{1,3}$/{print $0}')
#regular expression to match pattarn from 0.0.0.0 to 999.999.999.999 address
ip1=$(echo $ip | gawk -F. '{print $1}')
ip2=$(echo $ip | gawk -F. '{print $2}')
ip3=$(echo $ip | gawk -F. '{print $3}')
ip4=$(echo $ip | gawk -F. '{print $4}')
echo "Your ip is : $ip1.$ip2.$ip3.$ip4" #extract four number from the address
#To rectify original ip range 0-255
if [[ $ip1 -le 255 && $ip1 -ne 0 && $ip2 -ne 0 && $ip2 -le 255 && $ip3 -ne 0 && $ip3 -le 255 && $ip4 -ne 0 && $ip4 -le 255 ]]
then
echo "This is a valid ip address"
else
echo "This is not a valid ip address"
fi
if [[ $ip1 -eq 198 ]]
then
echo "It may be a local ip address"
else
echo "It may be a public ip address"
fi

#!/bin/bash
IP="172.200.22.33.88"
p=`echo $IP | tr '.' '\n' | wc -l`
echo $p
IFS=.
set $IP
echo $IP
a=$1
b=$2
c=$3
d=$4
if [[ $p == 4 && $a -lt 255 && $b -lt 255 && $c -lt 255 && $d -lt 255 ]]
then
echo " THIS is Valid IP "
else
echo "THIS IS NOT VALID IP ADDRESS"
fi

Validating IPv4 if is local
valid_ip(){
local ip=$IP
local stat=1
if [[ $ip =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
OIFS=$IFS
IFS='.'
ip=($ip)
IFS=$OIFS
[[ ${ip[0]} -le 255 && ${ip[1]} -le 255 \
&& ${ip[2]} -le 255 && ${ip[3]} -le 255 ]]
stat=$?
fi
if [[ "$stat" = "0" ]];
then
echo "IPv4 Valid"
if [[ "${ip[0]}" = 192 || "${ip[0]}" = 10 || "${ip[0]}" = 172 ]];
then
echo "IPv4 is local"
stat=1
fi
else
echo "IPv4 not valid"
fi
return $stat
}
IP=10.10.10.1
valid_ip

Check out my solution if you like it. Simple, readable, no extra variables.
function valid_ip () {
[[ ${1} =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]] \
|| return 1
for i in ${1//./ }; do
[[ ${i} -le 255 ]] \
|| return 1
done
}
Usage:
ips='192.168.1.1 192.168.1.333
8.8.8.8 8.8.8 a.b.c.d blabla'
for ip in ${ips}; do
valid_ip "${ip}" \
&& echo "${ip} is valid" \
|| echo "${ip} is INVALID"
done
Output:
192.168.1.1 is valid
192.168.1.333 is INVALID
8.8.8.8 is valid
8.8.8 is INVALID
a.b.c.d is INVALID
blabla is INVALID

I use the following on my router, running the Ash shell. This scripts has a very small footprint, as it only uses builtin commands, and no forking or subshells. It implements a checkIP() function, that returns false if the IP is invalid, and true if valid.
#
# basic validation on the IPv4 address
checkIPv4()
{
local IP="$1"
local N
local OIFS
# only numbers and dots in the entire IP address, no empty quads, and no
# leading or trailing dots
case "${IP}" in
*[!0-9.]* | *..* | .* | *. ) #
return 1
;;
esac
OIFS="${IFS}"
IFS=.
set -- $IP
IFS="${OIFS}"
if [ $# -ne 4 ]; then
return 1
fi
for N in "$#"; do
if [ "${#N}" -lt 1 -o "${#N}" -gt 3 ]; then
return 1
fi
# at this point, we are guaranteed it is a positive number
# of reasonable length
if [ "$N" -gt 255 ]; then
return 1
fi
done
return 0
}

I like the answer posted by Neo.
For clarity, I would add a variable for the duplicate portion of the regex.
#!/bin/bash
ip="1.2.3.4"
regex0to255='([1-9]?[0-9]|1[0-9][0-9]|2([0-4][0-9]|5[0-5]))'
if [[ "${ip}" =~ ^(${regex0to255}\.){3}${regex0to255}$ ]]; then
echo "success"
else
echo "fail"
fi

How about this?
# ip route get 10.10.10.100 > /dev/null 2>&1 ; echo $?
0
# ip route get 10.10.10.300 > /dev/null 2>&1 ; echo $?
1
Since the "ip" command checks the validity of IP in itself.
(2022/9/17) When the IP is not reachable i.e. network interface is down,
$ ip route get 10.10.10.100 > /dev/null 2>&1 ; echo $?
2
$ ip route get 10.10.10.300 > /dev/null 2>&1 ; echo $?
1
This means one can still distinguish if the IP is valid or not.
However, a better solution would be to write a small program, for example using inet_pton.
My comment in another thread,
https://unix.stackexchange.com/a/581081/65646

Related

Write a Bash script that can print if the number in the last column is odd or even?

I wanna Write a Bash script that can print if the number in the last column is odd or even or if no numbers in the line from a text file, the data is looking like this in a db.txt file :
sdn sddjk#gmail 123
ksd 234
sddd sddsd#gmail
i tried this :
#!/bin/bash
input="db.txt"
while IFS=" " read -r rec_column3
do
if [ $((number % 2)) -eq 0 ]; then
echo even
elif [ $((number % 2)) -eq 1 ]; then
echo odd
elif [[ "$rec_column3" != "number" ]]; then
echo not number
else
echo not found
fi
done
output is :
even
even
so can anyone helps me ? tnx
#!/bin/bash
input="db.txt"
#########################
# check third field
#########################
echo "check third field"
while read -r _ _ rec_column3
do
if [[ -z "$rec_column3" ]]; then
echo "not found" >&2;
elif ! [[ $rec_column3 =~ ^[0-9]+$ ]] ; then
echo "'$rec_column3' is not a number" >&2;
elif [[ $((rec_column3 % 2)) -eq 0 ]]; then
echo "'$rec_column3' is even" >&2
else
echo "'$rec_column3' is odd" >&2
fi
done < $input
echo "-----------------------"
#########################
# or check last field
#########################
echo "check last field"
while IFS=' ' read -r -a array
do
last_column=""
[[ ${#array[#]} -ne 0 ]] && last_column=${array[-1]}
if [[ -z "$last_column" ]]; then
echo "not found" >&2
elif ! [[ $last_column =~ ^[0-9]+$ ]] ; then
echo "'$last_column' is not a number" >&2
elif [[ $((last_column % 2)) -eq 0 ]]; then
echo "'$last_column' is even" >&2
else
echo "'$last_column' is odd" >&2
fi
done < $input
$ cat db.txt
sdn sddjk#gmail 123
ksd 234
ksd
12345
sddd sddsd#gmail 234
sddd sddsd#gmail 111
sddd sddsd#gmail aaa
$ ./script.sh
check third field
'123' is odd
not found
not found
not found
not found
'234' is even
'111' is odd
'aaa' is not a number
-----------------------
check last field
'123' is odd
'234' is even
'ksd' is not a number
'12345' is odd
not found
'234' is even
'111' is odd
'aaa' is not a number
awk is probably a better tool for this job. You can do something like this
awk 'BEGIN {split("even odd", a)} $NF ~ /^[0-9]+$/ {print a[$NF%2+1]; next} {print "NAN"}' db.txt
Checks if the last field is odd or even (the +1 is because the array a is 1-based).

Adding exceptions during cut returns unwanted results

I have a file that is being generated (sort of an audit file) with who have accessed said file. Looks as follows:
I need to write an alarming system that enters said report and checks for all users. however bash for some reason interprets the "------" as an input.
for i in $(cut -c 8-13 report_file.csv)
do
if [[ $i -eq 'suser' ]] || [[ $i -eq '--------' ]] || [[ $i -eq 'login' ]] || $i -eq 'root']]
then
break
else
echo "email text"+ $i | mailx -s "email subject" $EMAILS_LIST
done
the output for this is:
./script_name.sh: line 26: [[: --------: syntax error: operand
expected (error token is "-")
So as I understand it takes the exception "------" and still sees it as sort of input.
So, what am I missing?
-eq in test (same in extended test [[...]]) is an operator for integers. '---------' is not an integer. Use = to compare strings.
... [[ "$i" = 'suser' ]] || [[ "$i" = '--------' ]] || [[ "$i" = 'login' ]] || [[ "$i" = 'root' ]]
or simpler
... [[ "$i" = 'suser' || "$i" = '--------' || "$i" = 'login' || "$i" = 'root' ]]
or simpler:
case "$i" in
suser|--------|login|root) ;;
*) echo "email text"+ $i | mailx -s "email subject" $EMAILS_LIST; ;;
esac
Side note:
Reading lines from file using for i in $(...) is bad. It's better to use while read -r line; do .... done < <(cut -c 8-13 report_file.csv) or cut -c 8-13 report_file.csv | while read -r line; do ... done see here.

checking if a string is a palindrome

I am trying to check if a string is a palindrome in bash. Here is what I came up with:
#!/bin/bash
read -p "Enter a string: " string
if [[ $string|rev == $string ]]; then
echo "Palindrome"
fi
Now, echo $string|rev gives reversed string. My logic was to use it in the condition for if. That did not work out so well.
So, how can I store the "returned value" from rev into a variable? or use it directly in a condition?
Another variation without echo and unnecessary quoting within [[ ... ]]:
#!/bin/bash
read -p "Enter a string: " string
if [[ $(rev <<< "$string") == "$string" ]]; then
echo Palindrome
fi
A bash-only implementation:
is_palindrome () {
local word=$1
local len=$((${#word} - 1))
local i
for ((i=0; i <= (len/2); i++)); do
[[ ${word:i:1} == ${word:len-i:1} ]] || return 1
done
return 0
}
for word in hello kayak; do
if is_palindrome $word; then
echo $word is a palindrome
else
echo $word is NOT a palindrome
fi
done
Inspired by gniourf_gniourf:
is_palindrome() {
(( ${#1} <= 1 )) && return 0
[[ ${1:0:1} != ${1: -1} ]] && return 1
is_palindrome ${1:1: 1}
}
I bet the performance of this truly recursive call really sucks.
Use $(command substitution):
#!/bin/bash
read -p "Enter a string: " string
if [[ "$(echo "$string" | rev)" == "$string" ]]; then
echo "Palindrome"
fi
Maybe it is not the best implementation, but if you need something with pure sh
#!/bin/sh
#get character <str> <num_of_char>. Please, remember that indexing is from 1
get_character() {
echo "$1" | cut -c "$2"
}
for i in $(seq $((${#1} / 2))); do
if [ "$(get_character "$1" "$i")" != "$(get_character "$1" $((${#1} - i + 1)))" ]; then
echo "NO"
exit 0
fi
done
echo "YES"
and canonical way with bash as well
for i in $(seq 0 $((${#1} / 2 - 1))); do
if [ "${1:$i:1}" != "${1:$((${#1} - i - 1)):1}" ]; then
echo "NO"
exit 0
fi
done
echo "YES"
Skipping all punctuation marks and letter case.
input:He lived as a devil, eh?
output:Palindrome
input:Madam, I am Adam.
output:Not Palindrome
#!/bin/bash
#set -x
read -p "Enter a sentence" message
message=$(echo "$message" | \
sed -e '
s/[[:space:]]//g
s/[[:punct:]]//g
s/\!//g
y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/
' )
i=0
while read -n 1 letter
do
tempArray[i]="$letter"
((i++))
done < <(echo "$message")
i=0
counter=$((${#message}-1))
while [ "$i" -ne $((${#message}/2)) ]
do
if [ "${tempArray[$i]}" = "${tempArray[$counter]}" ]
then
((i++))
((counter--))
else echo -n "Not ";break
fi
done
echo "Palindrome"
exit

probabely "loop" and "output" doesn't work properly in my bash script

I wrote the following script:
#!/bin/bash
echo "Reading data - headers - both"
if [ $# -ne 3 ]; then
echo "Usage: ./nmap <port-range> <ip-list> <d || h || b>"
exit 1
fi
rm -f /tmp/right.txt 1>/dev/null 2>/dev/null
rm -f /tmp/wrong.txt 1>/dev/null 2>/dev/null
output=""
if [ $3 == h ]; then
while read -r -u3 port; do
while read -r -u4 ip; do
# echo -en "\n$ip $port: "
OUT=$( nmap -p "$port" --script=http-headers.nse "$ip" | awk 'NR>=7 && NR<=10')
# [[ $OUT == *Apache* ]] && $(echo -en "$ip $port\n" >> /tmp/right.txt) || $(echo -en "$ip $port\n" >> /tmp/wrong.txt)
[[ $OUT == *Apache* ]] && output="$output `echo -en "\n$ip -------------------- $port "`" && echo -e "$output" | column -t >> /tmp/right.txt || output="$output `echo -en "\n$ip -------------------- $port "`" && echo -e "$output" | column -t >> /tmp/wrong.txt
done 4< "$2"
done 3< "$1"
echo -e "$output" | column -t
elif [ $3 == d ]; then
echo data
elif [ $3 == b ]; then
echo both
fi
I expect my output have two files:
cat right.txt
ip1 ..... port1
ip2 ..... port1
ip2 ..... port2
ip3 ..... port3
.
.
.
cat wrong.txt
ip1 ..... port1
ip2 ..... port1
ip2 ..... port2
ip3 ..... port3
.
.
.
but it doesn't work properly...
any idea?
Thank you in advance
please find updated answer as i modified the BMW's answer for you please check it.
#!/bin/bash
echo "Reading data - headers - both"
if [ $# -ne 3 ]; then
echo "Usage: ./nmap <port-range> <ip-list> <d || h || b>"
exit 1
fi
join -j 2 $2 $1 > temp.txt
headers()
{
while read -r ip port
do
printf "ip: %s port:%d \n" $ip $port
OUT=$(nmap -p "$port" --script=http-headers.nse "$ip" | tac | awk -F: 'NR<=13&&/Apache/{print $2; exit}')
if [[ "$OUT" == *Apache* ]]; then
echo $ip $port >> /tmp/right.txt
else
echo $ip $port >> /tmp/wrong.txt
fi
done < temp.txt
}
case $3 in
"h") headers ;;
"d") echo data;;
"b") echo both;;
"*") echo "wrong input"
exit;;
esac
Your short-circuit logic is flawed. true && false || true && true will execute all four statements.
It's not clear why you think the output status of echo would indicate anything except success anyway.
Is this closer to what you mean?
output="$output `echo -en "\n$ip -------------------- $port "`"
[[ $OUT == *Apache* ]] && file=/tmp/right.txt || file=/tmp/wrong.txt
echo -e "$output" | column -t >>"$file"
This is still wrong because it will echo the accumulated output multiple times, but at least it should show you what needs to be changed (and also how to refactor your code to avoid repetitions).
I guess you actually want something like
[[ $OUT == *Apache* ]] && file=/tmp/right.txt || file=/tmp/wrong.txt
output="$output `echo -en "\n$ip -------------------- $port " | tee -a "$file"`"
except this doesn't run the copy in the file through column -t. But you can do that later, or add it here and avoid it later (you seem to be running it for all instances of the output in the end anyway).

Replace IPs with Hostnames in a log

I am looking for a bash script that reads a log and replaces IP addresses with a hostname. Does anyone have any idea of how to do this?
Following script should work. You can use it like this:
save it to ip_to_hostname.sh and then:
./ip_to_hostname.sh your_logfile > resolved_ip
#!/bin/bash
logFile=$1
while read line
do
for word in $line
do
# if word is ip address change to hostname
if [[ $word =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]
then
# check if ip address is correct
OIFS=$IFS
IFS="."
ip=($word)
IFS=$OIFS
if [[ ${ip[0]} -le 255 && ${ip[1]} -le 255 && ${ip[2]} -le 255 && ${ip[3]} -le 255 ]]
then
echo -n `host $word | cut -d' ' -f 5`
echo -n " "
else
echo -n "$word"
echo -n " "
fi
# else print word
else
echo -n $word
echo -n " "
fi
done
# new line
echo
done < "$logFile"
Talking about IPv4: You may generate a list of sed-commands from your hosts file:
sed -rn 's/^(([0-9]{1,3}\.){3}([0-9]{1,3}))[ \t]([^ \t]+)[ \t].*/s#\1#\4#/p' /etc/hosts > hosts.sed
Then apply it on your logfile:
sed -f hosts.sed LOGFILE
Of course your hostsfilenames have to be listed in the hostfile.
Another, inverse approach would be to use logresolve.
From the manpage:
NAME
logresolve - Resolve IP-addresses to hostnames in Apache log files
SYNOPSIS
logresolve [ -s filename ] [ -c ] < access_log > access_log.new
SUMMARY
logresolve is a post-processing program to resolve IP-addresses in Apache's access logfiles. To minimize
impact on your nameserver, logresolve has its very own internal hash-table cache. This means that each
IP number will only be looked up the first time it is found in the log file.
Takes an Apache log file on standard input. The IP addresses must be the first thing on each line and
must be separated from the remainder of the line by a space.
So you could use REGEX's to extract all IPs, put them 2 times into a new file, once into the first column, and convert it with logresolve. Then use this table for generating such a sedfile as above.
The resolving can be done like this:
ip=72.30.38.140
hostname=nslookup $ip | grep name
hostname=${hostname#*name = }
hostname=${hostname%.}
This way IPs do not have to be in /etc/hosts.
The script itself depends on how your log looks like. Can you post an example?
This is the modified version of wisent's script I ended up using:
#!/bin/bash
logFile=$1
while read line
do
for word in $line
do
# if word is ip address change to hostname
if [[ $word =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\:[0-9]{1,5}$ ]]
then
port=$(echo "$word" | sed -e "s/.*://")
word=$(echo "$word" | sed -e "s/:.*//")
OIFS=$IFS
IFS="."
ip=($word)
IFS=$OIFS
# check if ip address is correct and not 192.168.*
if [[ ${ip[0]} -le 255 && ${ip[1]} -le 255 && ${ip[2]} -le 255 && ${ip[3]} -le 255 && ${ip[0]}${ip[1]} -ne 192168 ]]
then
host=$(host $word | cut -d' ' -f 5)
if [[ $host =~ ^[0-9]{1,3}\(.*\)$ ]] # check for resolver errors
then
# if the resolver failed
echo -n "$word"
echo -n ":$port"
echo -n " "
else
# if the resolver worked
host=$(echo "$host'" | sed -e "s/\.'//" | sed ':a;N;$!ba;s/.*\n//g') # clean up cut's output
echo -n "$host"
echo -n ":$port"
echo -n " "
fi
else
# if the ip address isn't correct
echo -n "$word"
echo -n ":$port"
echo -n " "
fi
# else print word
else
echo -n $word
echo -n " "
fi
done
# new line
echo
done < "$logFile"
I added this to my .bashrc some time ago...
function resolve-hostname-from-ip()
{
if [ ! $1 ]
then
echo -e "${red}Please provide an ip address...${no_color}"
return 1
fi
echo "" | traceroute $1|grep " 1 "|cut -d ' ' -f4|cut -d '.' -f1
}
I have pre-defined terminal colors, so you can omit those if you like. =D
[root#somehostname ~ 08:50 AM] $ resolve-hostname-from-ip 111.22.33.444
someotherhostname
I have tested this on RHEL and SUSE successfully. I haven't tested it on IP's outside of my domain though, so I'm not 100% sure it will work in all cases...hope this helps =)

Resources