So I'm trying to ping a range of addresses in parallel in a bash script, count the ones that are alive, and print them out. The script works to ping the addresses and print the live ones out but it always outputs:
"There were 0 online hosts and 254 offline hosts"
It's not incrementing the ALIVE variable in my code, maybe because it's in a subshell? How could I get around this? Here's what I have right now:
#!/bin/bash
TOTAL=0
ALIVE=0
if [ $# -eq 3 ]
then
echo -n 'Live hosts:'
for ((i = $2; i <= $3 && i <= 254; ++i))
do
((++TOTAL))
ping -c 1 -i 0.2 -w 1 -W 1 $1.$i > /dev/null && ((++ALIVE)) && echo " $1.$i" &
done
echo "There were $ALIVE online hosts and $((($TOTAL - $ALIVE))) offline hosts"
else
echo "USAGE: pingRange.sh <first 3 octets of ip> <last octet start> <last octet end>"
echo " Ex: pingRange.sh 192.168.0 1 254
Note: An example input for the script is shown in the "else" part.
Note 2: Yes I know nmap is easier, I've already wrote a working script with nmap, trying to do one for ping now.
Note 3: I used a temporary file and it worked, updated code has #NEW comment:
#!/bin/bash
if [ $# -eq 3 ]
then
TOTAL=0 #NEW
TEMP=mktemp #NEW
echo -n 'Live hosts:'
for ((i = $2; i <= $3 && i <= 254; ++i))
do
((++TOTAL))
ping -c 1 -i 0.2 -w 1 -W 1 $1.$i > /dev/null && echo " $1.$i" >> $TEMP & #NEW
done
wait #NEW
cat $TEMP
ALIVE=$(cat $TEMP | wc -l) #NEW
echo "There were $ALIVE online hosts and $((($TOTAL - $ALIVE))) offline hosts"
rm $TEMP #NEW
else
echo "USAGE: pingRange.sh <first 3 octets of ip> <last octet start> <last octet end>"
echo " Ex: pingRange.sh 192.168.0 1 254
Using GNU Parallel it looks like this:
It runs correctly even if your system cannot run all
ping
s in parallel at the same time (e.g. if your process table is near full).This expression is run inside inside another process, so the changes are not visible in your parent process
So, we want to run
(( $3 - $2 ))
processes. Each of these processes will be run concurrently. We need to get output from all these processes on the end. We arrive at what we call "synchronization". We need to synchronize all values from all the processes to one point.We can imagine using ex. 255 files, one file unique for each process. Then after childs execute we can query the files.
The easiest is to use stdout or other line-buffered stream:
Because stdout should be line buffered, the multiple
echo "$1.$i"
shouldn't intercept the write, so we should arrive at just a variable with lines. Then you can just:But we could do this with a temporary directory:
And just to make it interesting and because we love oneliners, here's a solution using
xargs
andseq
:I believe you can do this just using
wait
.Amending your code, something like (untested):
unset
/declare
- just to be safeping ... &
still runs the comand in the backgroundpids[$i]=$!
saves its pidfor ...
loops over the keyswait ${pids[$i]}
returns the exit status after cmd has completed&& ...
does the same as before