Bash counting on variable

Viewed 48

I have a variable called ip that contains a list of IPs.

$ echo $ip
218.25.208.92 218.25.208.92 53.170.26.175 
8.135.72.164 244.105.203.71 211.118.89.4 
100.29.91.148 187.225.13.147 48.204.157.1452 182.237.138.26 182.237.138.26 182.237.138.26
211.118.89.4 100.29.91.148 100.29.91.148

I am trying to iterate over this string and find the IPs which are repeated more than 2 times. If the IP is repeated more than two times, show the amount of times the IP is found.

I thought this could be managed with a for loop, but I am quite lost.

I tried using grep -o. It shows how many times that IP appears, but I have to specify each IP manually...

echo $ip | grep -o 218.25.208.92 | wc -l 

Newer code:

for i in $ip; do
    echo $ip | grep -o $i | wc -l
done
3 Answers

To count, in my case, how many times an IP appears on a string is made with the following command:

(IFS=""; sort <<< "$ip") | uniq -c

With:

ip='218.25.208.92 218.25.208.92 53.170.26.175
8.135.72.164 244.105.203.71 211.118.89.4
100.29.91.148 187.225.13.147 48.204.157.1452 182.237.138.26
182.237.138.26 182.237.138.26
211.118.89.4 100.29.91.148 100.29.91.148'

The critical part you are missing is how bash handles separating words. Bash provides an Internal Field Separator which defaults to IFS=$' \t\n' (space, tab, newline). When you need to access individual words, you simply need to allow default word-splitting to occur.

Quoting prevents word splitting. So to allow default word splitting to take place, you must use $ip unquoted. There is no need to spawn separate subshell calling additional Linux utilities, bash provides all you need as built-ins.

To simply iterate over each separate IP, you can just loop over the contents of $ip treating it as a list, e.g.

for i in $ip; do
    # make use of $i any way you like
    echo $i
done

You can simply create an array from the contents of $ip by declaring an array and initializing it with $ip, e.g.

array=( $ip )

If you just want to separate each IP on its own line, you can use the printf trick, e.g.

printf "%s\n" $ip

Where the printf format string, despite having a single string conversion specifier, will exhaust all input outputting each IP on a separate line.

Don't do this, use uniq, but bash only:

IFS=$' \n' read -r -a ips -d '' <<<"$ip"
declare -A count
for i in "${ips[@]}"; do
    (( count["$i"]++ ))
done
for i in "${!count[@]}"; do
    (( count["$i"] > 2 )) && printf "%s\t%d\n" "$i" "${count[$i]}"
done
100.29.91.148   3
182.237.138.26  3

The one advantage to this approach is that the variables are always quoted so there will be no surprises with filename expansion.

Related