2

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
1
  • Use the uniq command with the -c and -d options. Commented Dec 3, 2020 at 22:25

3 Answers 3

1

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.

Sign up to request clarification or add additional context in comments.

2 Comments

Be aware that not quoting $ip means it's subject to glob expansion. If it contains *, ?, or other wildcard characters they could be expanded. It's safe with a variable that only contains IP addresses, but I would not recommend this for variables containing arbitrary text or user input.
Yes, that is a good addition. There are limitations to what contents you can safely use unquoted. Thank you.
1

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

4 Comments

Better printf "%s\n" $ip | uniq -c -- no need to change the default IFS. Good luck with your scripting!
@DavidC.Rankin IFS is changed in a subshell so there's no lasting harm. An unquoted $ip, on the other hand, is subject to word splitting (good) but also glob expansion (bad).
@JohnKugelman -- yep, I caught that, the intent was to explain no subshell was necessary -- I guess that is how I should have phased it instead of just "Better".
turn off globbing with set -f
1

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.