在 Bash 脚本中并行使用 Ping

Using Ping in Parallel in a Bash Script

所以我尝试在 bash 脚本中并行 ping 一系列地址,计算存活的地址,并将它们打印出来。该脚本用于 ping 地址并打印出实时地址,但它始终输出:

"There were 0 online hosts and 254 offline hosts"

它没有在我的代码中递增 ALIVE 变量,可能是因为它在子 shell 中?我怎么能解决这个问题?这是我现在拥有的:

#!/bin/bash

TOTAL=0
ALIVE=0

if [ $# -eq 3 ]
then
    echo -n 'Live hosts:'
    for ((i = ; i <=  && i <= 254; ++i))
    do
        ((++TOTAL))
        ping -c 1 -i 0.2 -w 1 -W 1 .$i > /dev/null && ((++ALIVE)) && echo "    .$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

注意:脚本的示例输入显示在 "else" 部分。

注意 2:是的,我知道 nmap 更简单,我已经用 nmap 编写了一个工作脚本,现在尝试为 ping 做一个。

注意 3:我使用了一个临时文件并且它有效,更新后的代码有#NEW 注释:

#!/bin/bash

if [ $# -eq 3 ]
then
    TOTAL=0 #NEW
    TEMP=mktemp #NEW
    echo -n 'Live hosts:'
    for ((i = ; i <=  && i <= 254; ++i))
    do
        ((++TOTAL))
        ping -c 1 -i 0.2 -w 1 -W 1 .$i > /dev/null && echo "    .$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
... && ((++ALIVE)) && ... &

此表达式 运行 在另一个进程中,因此更改在您的父进程中不可见

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

所以,我们要 运行 (( - )) 进程。这些进程中的每一个都将 运行 同时进行。我们最终需要从所有这些过程中获得输出。我们到达了我们所说的 "synchronization"。我们需要将所有进程的所有值同步到一个点。
我们可以想象使用 ex。 255 个文件,每个进程一个文件唯一。然后在 childs 执行后我们可以查询文件。
最简单的是使用标准输出或其他行缓冲流:

live_hosts=$(
   for ((i = ; i <=  && i <= 254; ++i)); do
       # `if` is more readable then `a && b`
       (
          if ping -c 1 -i 0.2 -w 1 -W 1 ".$i" >/dev/null; then
              echo ".$i"
          fi
       ) &
   done
   wait  # remember to wait for all the childs
)

因为 stdout 应该 行缓冲,多个 echo ".$i" 不应该拦截写入,所以我们应该只得到一个带有行的变量。那么你可以:

echo "There were $(printf "$live_hosts" | wc -l) online hosts"

但是我们可以用一个临时目录来做到这一点:

tmpdir=$(mktemp -d)

for ((i = ; i <=  && i <= 254; ++i)); do
   (
      if ping -c 1 -i 0.2 -w 1 -W 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 ".$i is alive!"
done

# remember to cleanup
rm -r "$tmpdir"

只是为了让它变得有趣,因为我们喜欢单线,这里有一个使用 xargsseq 的解决方案:

live_hosts=$(seq -f ".%.0f" "" "" | xargs -n1 -P0 -- sh -c 'ping -c 1 -i 0.2 -w 1 -W 1 "" >/dev/null && echo ""' --)
alive=$(echo "$live_hosts" | wc -l)
# well, if just the count matters, add the `| wc -l` to the one liner ..

我相信您只需使用 wait 即可做到这一点。

正在修改您的代码,例如(未测试):

#!/bin/bash

TOTAL=0
ALIVE=0

if [ $# -eq 3 ]
then
    unset pids
    declare -A pids
    echo -n 'Live hosts:'
    for ((i = ; i <=  && i <= 254; ++i))
    do
        ((++TOTAL))
        ping -c 1 -i 0.2 -w 1 -W 1 .$i > /dev/null &
        pids[$i]=$!
    done

    for i in "${!pids[@]}"
    do
        wait ${pids[$i]} && ((++ALIVE)) && echo "    .$i"
    done

    echo "There were $ALIVE online hosts and $((($TOTAL - $ALIVE))) offline hosts"
else
    # ...
  • unset / declare - 为了安全起见
  • ping ... & 仍然在后台运行命令
  • pids[$i]=$! 保存其 pid
  • for ... 遍历键
  • wait ${pids[$i]} returnscmd完成后的退出状态
  • && ... 和之前一样

使用 GNU Parallel 看起来像这样:

pingrange() {
  three=
  start=
  end=
  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"
}

它 运行 是正确的,即使您的系统不能同时 运行 所有 ping s(例如,如果您的进程 table 接近满) .

或者您可以直接使用:

fping -g $three.$start $three.$end

并处理输出。有关更多选项,请参阅 man fping