Bash arrays that compare ip addresses.

I've been trying to have an array of ip addresses go through a loop one at a time. Then compare if the current element is in another array of ip addresses. I've traced my error with /bin/bash -x

+ for c in '"${ip[@]}"'
./netk5: line 65: 50.17.231.23 23.64.146.110 23.64.159.139 107.14.36.129 31.13.74.7 173.194.77.95: syntax error: invalid arithmetic operator (error token is ".17.231.23 23.64.146.110 23.64.159.139 107.14.36.129 31.13.74.7 173.194.77.95")

From what I found on Google it seems Bash isn't great at comparing floating point numbers. I saw some suggestions on piping through bc first, but I'm unfamiliar with how that would work here. Perhaps there's also a simpler way.

Here is the code I'm using:

 #!/bin/bash -x

fox="firefox"

declare -a white

while true 
do

  stat=`netstat -antup | awk '{ print $7 }' | grep firefox | sed -ne "s/^[^\/]\+\///p" | awk '!x[$0]++';`
  b=$stat

     if [ "$b" = "$fox" ];
     then
     
     getip=`netstat -antup | grep firefox | awk '{ print $5 }' | sed -e 's/:[^:]*$//' | awk '!x[$0]++';`
     ip+=$getip

        for c in "${ip[@]}"
        do
            if [ $c == "${white[$c]}" ]
            then
                echo -e "${white[$c]} in array\n"

            else

                echo -e "Not in array"

                pass=${ip[$c]}
                white+=$pass
           
           # clear array to avoid duplicates
             unset ip

        fi

   done
fi

sleep 2

done

Right now it looks to see if an ip Firefox is connected to is in the "white" array. You may have to refresh your browser to get an ip.

UPDATE: It doesn't seem to be an issue with bc. I tried using the following to get the first ip's and the error persists:

timeout 1 bc | netstat -antup | grep firefox | awk '{ print $5 }' | sed -e 's/:[^:]*$//' | awk '!x[$0]++'

It doesn't need to compare floats, it needs to compare strings. As I can't see line 65 (the one with the syntax error) in your code, pls. post entire script and/or log of execution.

Could you tell us what you are trying to do. Not how you want to do it. You have some greatly over-complex code to do something simple - it seems to me.

an awk one-liner and two inputs can identify or remove duplicates, for example.

Sorry, but this is the dichotomy I face in every forum. Posting 200 lines of code and being told that is too much information. Or just putting the problematic code and being told its not enough. I also feared that since some of this will only run on KDE that many would ignore this post, but here goes:

#!/bin/bash -x

# Interupt and Exit Function
control_c()
{
setterm -cursor on
exit 0
}

# Make sure user is logged in as root
if [[ $EUID -ne 0 ]]; then
   echo "This script must be run as root" 1>&2
   exit 1
fi

clear
setterm -cursor off

# Define some paranoid stuff
fox="firefox"

# Define whitelist array
declare -a white

while true 
do

  stat=`netstat -antup | awk '{ print $7 }' | grep firefox | sed -ne "s/^[^\/]\+\///p" | awk '!x[$0]++';`
  b=$stat

     if [ "$b" = "$fox" ];
     then

     #timeout 1 ngrep -d any -O -T port http >> log
     getip=`netstat -antup | grep firefox | awk '{ print $5 }' | sed -e 's/:[^:]*$//' | awk '!x[$0]++';`
     ip+=$getip
     #ipchk=`iptables -L | egrep '($ip|ALLOW)'`
     #ipck=$ipchk


#       c=0
#       len=${#ip[@]}
#       for (( c = 0; c < $len; c++ ));
        for c in "${ip[@]}"
        do
            if [ $c == "${white[$c]}" ]
            then
                echo -e "${white[$c]} in  whitelist\n"

            else

                echo -e "Not in whitelist"

                     #else
                             # Whitespace specific like python... 
                             sudo -u ph33r kdialog --title "KNetWatch" --warningyesno "Wanna block   
${ip[$c]}"


#echo ${ip[1]}

            #fi
        #done

        #${#ip[@]}

        #echo "${ip[@]}"


        # Log what happened in background
         #echo "" >> log | date >> log | echo "" >> log;  netstat -antpu | awk '!x[$0]++' >> log; echo "" >> log; echo  "" >> log;
                                                                                                                                         
  rc=$?


        if [ "${rc}" == "0" ]; then

           getpid=`netstat -antup | awk '{ print $7 }' | grep firefox | cut -f1 -d"/" | awk '!x[$0]++'`

           # Kill the appiclation
           #pid=$getpid
           #kill -9 $pid 
           sudo -u ph33r kdialog --title "Firefox was killed by KNetWatch" --passivepopup "Examine log file for details" 7

           # Block the ip address
           #ip=$getip
           /sbin/iptables -I OUTPUT -s ${ip[$c]} -j DROP

        elif [ "${rc}" == "1" ]; then

           # Use array as iptables uses the hostname here.

                pass=${ip[$c]}
                white+=$pass

           sudo -u ph33r kdialog --title "KNetWatch" --passivepopup "
${ip[$c]} 
whitelisted." 7

           # clear array to avoid duplicates
           unset ip

        else

            echo "You shudna done that he's just a boy!"

        fi

     fi
   done
fi

sleep 2

done

Much of this is commented out as I've made changes where some is now irrelevant, or to not run every part to make testing easier.

What was said about "awk '!x[$0]++'" removing duplicates is true, but this was only for where netstat was being checked. "unset ip" ip was to clear the current ips listed in the ip array to so that the command that checks netstat would not enter the same ips again and cause duplicates in that array.

Its true this does not need to check floats, but strings. I should have specified that. However, I've never told this code to interpret ips as floats or strings as Bash is not a strong typed language. So I don't know how to set or correct this.

I have also tried to resolve checking the elements of the arrays one at a time with a nested for loop. It seemed to be better logic but I run into problems with no output.

        for c in "${ip[@]}"
        do
         for d in "${white[@]}"
         do
           if [ "$c" -eq "$d"]
           then
               echo -e "${white[$d]} in array\n"

           else

               echo -e "$c is not in array"

               pass=${ip[$c]}
               white+=$pass

Hopefully that is not an overkill of information.

Post contents of this variable:

getip=`netstat -antup | grep firefox | awk '{ print $5 }' | sed -e 's/:[^:]*$//' | awk '!x[$0]++';`
echo $getip

Unfortunately the error msg you posted did not relate to the problematic code snippet you gave. So - where to start looking?
Anyhow, seems like bash doesn't really like the dots in the ip numbers, even when comparing strings, in the if [ "$c" == ...] . Try replacing them with a non-bash-irritating separator, which is not that easy to find ( I used a comma):

 . . .
     getip=( $(netstat -antup | awk '/firefox/ {sub (/:.*$/, "", $5); gsub ("\.", ",", $5); print $5}') )
        for c in ${getip[@]}
        do  something
        done
1 Like

Sorry if that remark came off sarcastic. It was meant as a sigh...

An error message was provided, but I can see how that might not have been enough information.

It appears that the code below still produced an error when used with mine:

getip=( $(netstat -antup | awk '/firefox/ {sub (/:.*$/, "", $5); gsub ("\.", ",", $5); print $5}') )

Error:

awk: cmd. line:1: warning: escape sequence `\.' treated as plain `.'
+ ip+=,,,,,,,,,,,,
+ for c in '"${ip[@]}"'
./netk6: line 66: ,,,,,,,,,,,,: syntax error: operand expected (error token is ",,,,,,,,,,,,")

However, this rang a bell in my head that escaping the . characters may resolve this problem.

I found a link on how to do this with ip addresses here:

http://stackoverflow.com/questions/12427892/auto-escaping-an-ip-address-in-bash-alias-argv

Perhaps I'll give this a shot after I've had adequate sleep in a couple hours.

Thank you for your suggestion.

Working perfectly for me:

# netstat -antup | awk '/firefox/ {sub (/:.*$/, "", $5); gsub ("\.", ",", $5); print $5}'
68,232,35,121
173,194,70,139
108,161,188,213

and then the loop/condition works well.
Perhaps your awk version? Which one do you have? Give gsub ("\134\056", ",", $5) a shot ...

$ awk --version
GNU Awk 4.0.1

This works from the command-line:

# netstat -antup | grep firefox | awk '{ print $5 }' | sed -e 's/:[^:]*$//' | sed "s/\./\\\\./g" | awk '!x[$0]++'
173\.194\.64\.95
74\.125\.227\.158

However, when I run the code with /bin/bash -x the there are no backslashes to escape the dots and I still get "syntax error: invalid arithmetic operator". I also tried sending this straight to the $ip array in case the formatting was lost when passing from the $getip variable. Again I had the same results....

If using sed anyhow, try this to replace dots by commas:

| sed -e 's/:[^:]*$//;s/\./,/g'

Don't ask me how or why, but this seemed to get around the problem of the dots in the ip addresses coming back as numbers instead of strings:

#!/bin/bash -x  fox="firefox"  declare -a white  while true  do  sleep 2    stat=`netstat -antup | awk '{ print $7 }' | grep firefox | sed -ne "s/^[^\/]\+\///p" | awk '!x[$0]++';`   b=$stat       if [ "$b" = "$fox" ];      then       ip+=`netstat -antup | grep firefox | awk '{ print $5 }' | sed -e 's/:[^:]*$//' | awk '!x[$0]++';`          for c in "${ip[@]}"         do           for d in "${white[@]}"           do             if [ "$c" -eq "$d"]             then                 echo -e "${white[$d]} in array\n"              else                  echo -e "$c is not in array"             # clear array to avoid duplicates            unset ip             fi          done done  fi  done 

The only problem now seems to be that all the ip addresses from this line are being thrown into the first element of the array instead of individual elements for each ip:

ip+=`netstat -antup | grep firefox | awk '{ print $5 }' | sed -e 's/:[^:]*$//' | awk '!x[$0]++';`

I confirmed this by just echoing the first element:

echo -e "${ip[0]}\n"

Anyone know how to split these into individual elements? I tried using the space between them as a delimiter with cut but it didn't seem to work.

Your ip+=... contruct is not an array assignment. In bash, try ip=( $(. . .) ) to create an array.

1 Like

Um... I believe that array+= does assign values to an array as I have successfully used it in other projects before. However, I did get this worked out. I just had to use tr to set the space in between the ip's as a delimiter making them each separate values:

getip=`netstat -antup | grep firefox | awk '{ print $5 }' | sed -e 's/:[^:]*$//;s/\./,/g;' | awk '!x[$0]++';`
ip=(`echo $getip | tr " " "\n"`)

As you can see I did have to come back and convert the dots to commas in the end. Thank you for your help!

+= sounds more like makefile than BASH syntax.

We're talking bash, notabene.
You can do that in one go: ip=( $(netstat...) ) will assign array ip . Pls note the new form $(...) for command substitution.

ip+=sth will append string "sth" to variable $ip, so $ getip+=`netstat ...` twice will yield

$ echo "$getip"
173,194,78,95
173,194,78,100
2,19,63,139173,194,78,95
173,194,78,100
2,19,63,139

Lof of noice, but was the idea to find each ip only once and then to do something ?

Need only ex. awk. Include regexp - no need to use awk - pipe - sed - pipe - awk ...

getip=$(netstat -antup 2>/dev/null | awk '
/firefox/ { 
        destip=$5
        sub(/:.*$/, "", destip)
        dest[destip]++
        }
END {
        for ( ip in dest ) print ip
        }
'
)


echo "$getip"

for ip in $getip
do
        echo "ip:$ip"
done

It is not the contents of $c that bothers bash, but it is the whitelist index containing the dots. So - try using a non-offending index scheme, e.g. removing the dots if that does not lead to ambiguities:

 if [ "$c" == "${white[${c//./}]}" ]

will work (c containing the ip with dots!):

+ for c in '"${ip[@]}"'
+ '[' 173.194.41.66 == '' ']'

while

if [ "$c" == "${white[${c}]}" ]    
./test: line 13: 173.194.41.66: syntax error: invalid arithmetic operator (error token is ".194.41.66")

crashes!

# associative array bash 4.0 or ksh93
# bash --version
declare -A white   # bash
#typeset -A white # ksh93

for c in 192.168.10.20 192.168.20.20 192.168.10.20
do
        [ "${white[$c]}" = "$c" ] && echo "it's in the white"
        [ "${white[$c]}" != "$c" ] && white[$c]=$c
done

for c in "${!white[@]}"
do
        echo "$c"
done