4

This is a very simple script but I can't seem to get it right. This code is supposed to print out class C IP addresses from 192.168.0.0 - 192.168.255.255 but all that prints out is 192.168.0.0 - 192.168.0.255. For some reason my outer loop won't loop. I'm sure it is something stupid but this could be helpful for those people learning nested looping in shell scripts. Any other advice is welcome!

#! /bin/sh
network_id="192.168."
count1=-1
count2=-1
while [ "$count1" -le 254 ]
do
    count1=$(($count1+1))
    while [ "$count2" -le 254 ]
    do
        count2=$(($count2+1))
        printf "%s%s%s%s\n" $network_id $count1 "." $count2
    done
done
exit 0
3
  • 2
    Note that $ is not required inside the double-parentheses construct. E.g. $((count1 + 1)) works perfectly. Commented Feb 4, 2016 at 4:11
  • 1
    Note that when you run scripts in POSIX compatibility mode using #!/bin/sh, it implies that they will be portable. An answer below suggested using {0..255}. While BASH will expand this, other POSIX /bin/sh implementations likely will not. If you're writing for bash, it's best to use #!/usr/bin/env bash as your shebang. If you're writing for portability, best not to use bashisms. Commented Feb 4, 2016 at 4:49
  • It's intriguing that the first two while loop answers both reset count2 after the inner while completes. I'd set it before it starts (and not before the outer loop starts). But both techniques achieve the same result. Commented Feb 4, 2016 at 4:50

6 Answers 6

3

You are not resetting count2. Your outer loop is running 256 times, but the inner loop stops after running through once.

If you add count2=-1 after closing inner loop, it will work as expected.

For clarity, I would move your increment so you are clearly iterating between 0-255. Here's how I'd write it:

#! /bin/sh
network_id="192.168."
count1=0
count2=0
while [ "$count1" -le 255 ]; do
    while [ "$count2" -le 255 ]; do
        printf "%s%s%s%s\n" $network_id $count1 "." $count2
        count2=$(($count2+1))
    done
    count2=0
    count1=$(($count1+1))
done
Sign up to request clarification or add additional context in comments.

2 Comments

Wow, I knew it was something like that. That's just a programming thing not even a shell script thing. I feel dumb, but thank you!
@Colin93 Please remember to choose this as the correct answer if it solved your problem. :)
2

Nothing but a small mistake. You didnot set $count=-1

#! /bin/sh
network_id="192.168."
count1=-1
count2=-1
while [ "$count1" -le 254 ]
do
    count1=$(($count1+1))
    while [ "$count2" -le 254 ]
    do
        count2=$(($count2+1))
        printf "%s%s%s%s\n" $network_id $count1 "." $count2
    done
    count2=-1
done
exit 0

This should work.

Comments

2

Well, @uint128_t caught the error.

Your script appears to attempt to print out all the IP addresses within 192.168.0.0/16, rather than all the class C networks with that prefix, so I'll assume that's your code is a better description of the result you're looking for.

And I'll submit the following as a "better use of bash":

#!/usr/bin/env bash

# Declare an array
ip=(192 168 -1 -1)

# Step through the third quad in the array
while [ $((ip[2]++)) -lt 255 ]; do
    # Step through the fourth quad in the array
    while [ $((ip[3]++)) -lt 255 ]; do
        # Print the array, with dots as a field separator
        IFS=. eval echo '"${ip[*]}"'
    done
    # Reset the last quad once we're done with this subnet
    ip[3]=-1
done

There will be those who say that eval is evil, but it's perfectly safe in this context, where input data are known and you're protecting things with single quotes.

This solution avoids extra counters, and perhaps gives you flexibility to do other things with your IPs if you desire.

I should mention another subtlety, which is [ $((ip[2]++)) -lt 255 ]. This increments the array element, but because the ++ is AFTER the variable, the value that is used for comparison (-le) is the one before the increment occurs. So we stop the loop when the compared number was less than 255, because that means the last run of the loop will occur when the variable incremented one higher, to 255. If for some reason you wanted to compare the value after the increment instead of before, you could prepend the variable with ++ instead of appending it: $((++ip[2])).


Another fun approach might be to capitalize on the fact that IP addresses are simply numbers, and that dotted quads are a translation of that number:

#!/usr/bin/env bash

# Set your start and maximum IPs as integers
ip=$(( 192*2**24 + 168*2**16 ))
max=$(( ip + 255*2**8 + 255 ))

# Convert each integer into dotted quad notation
while [ $ip -le $max ]; do
  echo $(( ip/2**24 )).$(( ip/2**16 %256 )).$(( ip/2**8 % 256 )).$(( ip % 256 ))
  ((ip++))
done

Comments

1

You should initialize count2 at the beginning of the outer loop, not before the outer loop. Otherwise, the test in the inner loop fails immdiately after the first time through the outer loop.

#! /bin/sh
network_id="192.168."
count1=-1
while [ "$count1" -le 254 ]
do
    count1=$(($count1+1))
    count2=-1
    while [ "$count2" -le 254 ]
    do
        count2=$(($count2+1))
        printf "%s%s%s%s\n" $network_id $count1 "." $count2
    done
done
exit 0

Comments

1

You can use 1 loop:

i=0
((max=256 * 256))
while [ $i -lt ${max} ]; do
        (( major=i/256))
        (( minor=i%256))
        printf "%s.%s.%s.%s\n" "192" "168"  ${major} ${minor}
        ((++i))
done

Comments

1

For loop is better here than while loop. Your implementation can be simplified as:

#! /bin/bash
network_id="192.168."
for i in {0..255}; do
    for j in {0..255}; do
        printf "%s%s%s%s\n" $network_id $i "." $j
    done
done
exit 0

Edit: Thanks for ghoti's suggestion. Please be aware that you may need to customize the shebang line to suit your needs. More discussions can be found in:What is difference between #!/bin/sh and #!/bin/bash? and Why is it better to use “#!/usr/bin/env NAME” instead of ....

7 Comments

Per my comment on the question, I recommend against using {0..255} if you've specified a shebang of #!/bin/sh. Most obviously, not all /bin/shes are bash. (I have an Ubuntu box in which /bin/sh is dash instead of bash.) If you're going for portability and POSIX compliance, don't use bashisms.
Replacing the shebang is a less than ideal fix. Replacing the loop constructs with something POSIX portable would arguably be a better fix.
@ccf, the reason I mentioned the #!/usr/bin/env bash shebang was because the question was about bash, without specifying an operating system. In FreeBSD, bash usually lives in /usr/local/bin/bash. In OSX, the system comes with bash version 3, and if you've installed bash 4 from macports you may have it in /opt/local/bin/bash. If you can't be sure of the location of the bash binary, you can trust /usr/bin/env to find it for you. Portability++.
@ghoti: The question has #! /bin/sh specified though. So I just assumed that would work for this question. Thanks for pointing it out.
It most likely would be sufficient for the person who posted the question, but in the interest of building a collection of useful questions-and-answers on StackOverflow, it's preferred if you can provide answers that can be used in other environments as well. Someone searching the site a year from now might be trying to do the same thing in FreeBSD, and might not know about the different location of bash. As I said, portability++.
|

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.