问题
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
回答1:
... && ((++ALIVE)) && ... &
This expression is run inside inside another process, so the changes are not visible in your parent process
a=1
((++a)) & # <- is run as another process
echo "$a" # this will print 1 ...
(
((++a))
echo "$a"
) & # this will print 2 in the background
wait
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:
live_hosts=$(
for ((i = $2; i <= $3 && i <= 254; ++i)); do
# `if` is more readable then `a && b`
(
if ping -c 1 -i 0.2 -w 1 -W 1 "$1.$i" >/dev/null; then
echo "$1.$i"
fi
) &
done
wait # remember to wait for all the childs
)
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:
echo "There were $(printf "$live_hosts" | wc -l) online hosts"
But we could do this with a temporary directory:
tmpdir=$(mktemp -d)
for ((i = $2; i <= $3 && i <= 254; ++i)); do
(
if ping -c 1 -i 0.2 -w 1 -W 1 "$1.$i" >/dev/null; then
# create a file with the name "$i" inside tmpdir
# I don't think content matters (just the name of file)
touch "$tmpdir"/"$i"
fi
) &
done
wait
# ex. the count of alives are the count of files inside out tmpdir
alive=$(find "$tmpdir" -type f -print . | wc -c)
# this is funny
for i in "$tmpdir"/*; do
echo "$1.$i is alive!"
done
# remember to cleanup
rm -r "$tmpdir"
And just to make it interesting and because we love oneliners, here's a solution using xargs
and seq
:
live_hosts=$(seq -f "$1.%.0f" "$2" "$3" | xargs -n1 -P0 -- sh -c 'ping -c 1 -i 0.2 -w 1 -W 1 "$1" >/dev/null && echo "$1"' --)
alive=$(echo "$live_hosts" | wc -l)
# well, if just the count matters, add the `| wc -l` to the one liner ..
回答2:
I believe you can do this just using wait
.
Amending your code, something like (untested):
#!/bin/bash
TOTAL=0
ALIVE=0
if [ $# -eq 3 ]
then
unset pids
declare -A pids
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 &
pids[$i]=$!
done
for i in "${!pids[@]}"
do
wait ${pids[$i]} && ((++ALIVE)) && echo " $1.$i"
done
echo "There were $ALIVE online hosts and $((($TOTAL - $ALIVE))) offline hosts"
else
# ...
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
回答3:
Using GNU Parallel it looks like this:
pingrange() {
three=$1
start=$2
end=$3
total=$(($end-$start))
online="$(seq $start $end |
parallel -j0 "ping -c 1 -i 0.2 -w 1 -W 1 $three.{} > /dev/null && echo ' $three.{}'")"
alive=$(echo "$online" | wc -l)
offline=$((total-alive))
echo "$online"
echo "There were $alive online hosts and $offline offline hosts"
}
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).
来源:https://stackoverflow.com/questions/55988277/using-ping-in-parallel-in-a-bash-script