#!/bin/bash # multiping.sh - V 0.4.01: Simple bash based inter process communication demo # (C) 2020 - F-Hauri.ch # Licensed by GNU GENERAL PUBLIC LICENSE Version 3 # Syntax: $0 [host or ip] [host or ip...] # Require: ping # Recommand: tput gnuplot xterm declare DOPLOT=false DOCOLORS=false prog=${0##*/} INTERVAL=1.0 declare MPDEBUG=${MPDEBUG:-false} declare -iA pids fds seqs lastread declare -A stats target tims close usage() { cat <<-EOUsage Usage: $prog [-[cdhp]] [-i N] [host or ip] [host or ip]... Options: -c colors (red for no answer, green for higher "seq" number) -d Debug this (by openning 2 xterm windows... look at script) -h help (print this) -iN Interval for ping command (min 0.2 in user mode) -p Plot dynamically (require gnuplot) All following argument will be "target" for "ping" command. When run, this will show all target's answers in a line, sorted by IP address (command line order is used, until all IP are knowns) A newline is put each time any target upstate change. User interactions: h Pring Headline p Start plotting dynamically (unsing gnuplot) q Quit. r Clear terminal s Stop plotting and storring points. EOUsage } debug() { local args=() opt OPTARG OPTIND while getopts "dcpi:" opt;do case $opt in i ) args+=("-$opt" "$OPTARG") ;; c|p ) args+=("-$opt") ;; d ) ;; * ) ;; esac done exec {log}<> >(:) xterm -geom 80x24+840+20 -T MPLog -S00/$log & XTPid=$! xterm -geom 80x24+0+20 -e "watch -n.3 ps --tty $(tty) fw" & WPid=$! echo >&$log $'\e[20h' echo >&$log "$0 ${args[*]} ${*:OPTIND}" MPDEBUG=true $0 2>&$log "${args[@]}" "${@:OPTIND}" read -rsn 1 -t 30 -p Hit\ a\ key... _ exec {log}>&- kill $XTPid $WPid echo -en '\r\e[K\r' exit 0 } doping() { # run ping once, storing target, pid and file descriptor local fd LANG=C exec {fd}< <(exec stdbuf -o0 ping -n -i $INTERVAL "$1" 2>&1) pids["$1"]=$! fds["$1"]="$fd" target["$1"]="$1" seqs["$1"]=0 lastread["$1"]=${EPOCHREALTIME/.} tims["$1"]='---' close["$1"]=false } pingInput() { # Read one line from ping output local line seq tim fd int=0 tgt arr read -u "${fds[$1]}" -r line $oldbash && getEpochRealtime lastread["$1"]=${EPOCHREALTIME/.} if [ "$line" ] ;then if [ -z "${line%%PING*}" ] ;then # First line from ping output IFS=$' \t()\r\n' read -r _ host ip _ <<<"$line" printf -v stats["$1"] '%-18s %-18s %-28s' \ "${target[$1]}:" "$ip" "$host" printf '%sStarted: %s\n' "$ret" "$line" (( LINECNT++ )) ip2int "$ip" int if [ "${byIp[int]}" ] ;then $MPDEBUG && >&2 printf 'Target %s: %s == %s is used twice.\n' \ "$ip" "${byIp[int]}" "$1" fd=${fds[$1]} exec {fd}<&- for arr in pids fds target seqs lastread tims close ;do local -n crtarr=$arr unset crtarr["$1"] done else byIp[int]="$1" ret=$'\r' $MPDEBUG && >&2 declare -p fds target byIp fi elif [ -z "${line%%64*}" ] ;then # regular ping output seq=${line#*[rs]eq=} tim=${line#*time=} seqs[$1]=${seq%% *} tims[$1]=${tim%% *} elif [ -z "${line%%--- *}" ] ;then # first of statistic lines close[$1]=true elif [ -z "${line//*packets transmitted*}" ] ;then # 2nd stat line packets=${line%% *} recv=${line#*transmitted, } err=${line#*eived, } stats[$1]+=" ${recv%% *} / $packets -> ${err%\% *}% err." elif [ -z "${line%%rtt min*}" ] || [ -z "${line%%pipe *}" ] ;then # last output line: close FD stats[$1]+=" ${line##*= }" fd=${fds[$1]} exec {fd}<&- unset fds["$1"] elif [ -z "${line/ping: *: Name or service not known}" ] ;then tgt=${line#ping: } tgt=${tgt%: *} fd=${fds[$tgt]} exec {fd}<&- for arr in pids fds target seqs lastread tims close ;do local -n crtarr=$arr unset crtarr["$tgt"] $MPDEBUG && >&2 declare -p crtarr done else # regular unanswered ping output seq=${line#*[rs]eq=} seq=${seq%% *} ((seq)) && seqs[$1]=$seq tims[$1]=--- fi else if ${close[$1]} ;then fd=${fds[$1]} exec {fd}<&- unset fds["$1"] fi fi } breakLoop() { kill -INT "${pids[@]}" if ((PNLT==3)) ;then echo ;else sleep .5 ;fi printf ' -- %s --\e[K\r' "$1" ((PNLT--))||fds=() } ip2int() { local _a _b _c _d; IFS=. read -r _a _b _c _d <<< "$1" printf -v "$2" %u $(( _a << 24 | _b << 16 | _c << 8 | _d )) ;} int2ip() { printf -v "$2" "%d.%d.%d.%d" \ $(($1>>24)) $(($1>>16&255)) $(($1>>8&255)) $(($1&255)) ;} initGnuPlot() { local -a plotlines=() local -i fld=2 local TMPDIR=/tmp host ip [ -d /dev/shm ] && [ -w /dev/shm ] && TMPDIR=/dev/shm ! { [ "$TMPDATA" ] && [ -f "$TMPDATA" ] ;} && read -r TMPDATA < <(mktemp -p $TMPDIR --suffix .dat plot-XXXXXXXXXX) [ -L "/dev/fd/$GPFD" ] && exec {GPFD}>&- exec {GPFD}> >(exec gnuplot &>/dev/null) cat <<-EOGPInit >&$GPFD set term wxt noraise persist title 'Ping $*'; set xdata time; set timefmt '%s.%N'; EOGPInit if $sortedByIp;then $MPDEBUG && >&2 declare -p byIp $MPDEBUG && >&2 echo -- "${*@Q}" for i in "${!byIp[@]}";do int2ip "$i" ip printf -v i '"%s" using 1:%d with step title "ping %s [%s]"' \ "$TMPDATA" $((fld++)) "${byIp[i]}" "$ip" plotlines+=("$i") done else for host ;do plotlines+=( "'$TMPDATA' using 1:$((fld++)) with step title 'ping $host'" ) done fi printf -v plotcmd "%s, " "${plotlines[@]}" plotcmd="plot ${plotcmd%, };" doplot=false } while getopts "cdhpi:" opt;do case $opt in h ) usage; exit 0 ;; c ) DOCOLORS=true ;; d ) debug "$@" ;; p ) DOPLOT=true ;; i ) INTERVAL=$OPTARG ;; ? ) b=$((OPTIND-1)); echo >&2 "$prog ERROR: '${!b}' not valid option."; exit 1 ;; * ) echo >&2 "$prog ERROR: opt: '$opt' ??"; usage; exit 2 ;; esac done shift $((OPTIND-1)) # OLD BASH creation of EPOCHREALTIME if [ "${BASH_VERSINFO[0]}" -lt 5 ] ;then oldbash=true coproc exec stdbuf -o0 date -f - +%s.%N DATEIN=${COPROC[1]} DATEOUT=${COPROC[0]} else oldbash=false fi getEpochRealtime() { echo now >&"$DATEIN" read -ru "$DATEOUT" EPOCHREALTIME } # Init some values printf -v fmtline '%*s' ${#} '' printf -v headline '\r x/%d '"${fmtline// / %16s}"'\e[K\n' $# "$@" fmtline=${fmtline// / %8s %-7s} printf '\e[8;;%dt' $((120>${#headline}?120:${#headline})) # resize term width [ "$LINES" ] || LINES=$(tput lines 2>/dev/null) [ "$LINES" ] || LINES=24 LINECNT=0 ret=$'\r' sortedByIp=false $DOPLOT && initGnuPlot "$@" $oldbash && getEpochRealtime for ip ;do doping "$ip" done PNLT=3 trap '$MPDEBUG && >&2 echo CHLD $$' CHLD trap '$MPDEBUG && >&2 echo PIPE $$;$doplot&&DOPLOT=false' PIPE trap 'breakLoop stop' INT while ((${#fds[@]}));do if read -rsn 1 -t .2 key ;then case $key in q ) breakLoop quit ;; r ) clear;LINECNT=0 ;; p ) DOPLOT=true doplot=false; initGnuPlot "$@" ;; s ) DOPLOT=false; [ -L /dev/fd/$GPFD ] && exec {GPFD}>&- ;; h ) printf "%s" "$headline" ;; esac fi out="" cnt=0 strline='' # strline store 1 flag for each ping $oldbash && getEpochRealtime now=$EPOCHREALTIME printf -v mseqa [%d]+=1\ "${seqs[@]}" declare -ia "mseq=($mseqa)" maxseq=("${!mseq[@]}") maxseq=("${maxseq[-1]}") nowms=${now/.} pltline='' if $sortedByIp || ((${#byIp[@]}==${#fds[@]})) ;then if ! $sortedByIp;then sortedByIp=true headline=$'\r x/'"${#byIp[@]} " printf -v i '%*s' ${#byIp[@]} '' printf -v headline "\\r%10sx/%d ${i// /%18s}\\n%15s" \ '' ${#byIp[@]} "${byIp[@]}" '' for i in "${!byIp[@]}";do int2ip "$i" ip printf -v i " %16s" "$ip" headline+="$i" done if $DOPLOT ;then [ "$GPFD" ] && [ -e "/dev/fd/$GPFD" ] && exec {GPFD}>&- [ "$TMPDATA" ] && [ -f "$TMPDATA" ] && : >"$TMPDATA" initGnuPlot "$@" fi headline+=$'\e[K\n' printf "%s" "$headline" fi ar=("${byIp[@]}") else ar=("${!fds[@]}") fi for i in "${ar[@]}" ;do # for each opened FD [ "${fds[$i]}" ] && read -rt 0 -u "${fds[$i]}" && pingInput "$i" #read input if data available if ((nowms-${lastread[$i]:-0}>2000000)) || # if last read older than 2s [ -z "${tims[$i]#---}" ] ;then # or time string == '---' if $DOCOLORS ;then # show datas with red foreground printf -v strng ' \e[31;1m%8s %-7s\e[0m' \ "${seqs[$i]}" "${tims[$i]}" else printf -v strng ' %8s %-7s' "${seqs[$i]}" "${tims[$i]}" fi [ -z "${tims[$i]#---}" ] && strline+="-1 " || strline+="0 " $DOPLOT && pltline+=" -3" else if $DOCOLORS && (( seqs[$i] == maxseq )) ;then printf -v strng ' \e[32;1m%8s %-7s\e[0m' \ "${seqs[$i]}" "${tims[$i]}" else printf -v strng ' %8s %-7s' "${seqs[$i]}" "${tims[$i]}" fi ((cnt++)) strline+="1 " $DOPLOT && pltline+=" "${tims[$i]} fi out+="$strng" done if $DOPLOT && [ "$pltline" != "$lastpltline" ] ;then [ "$TMPDATA" ] && echo >>"$TMPDATA" "$now $pltline" $doplot && { [ "$GPFD" ] && [ -e "/dev/fd/$GPFD" ] && echo >&$GPFD "$plotcmd" } || doplot=true lastpltline="$pltline" fi (( LINECNT % LINES )) || { printf "%s" "$headline" ; (( LINECNT++ )) ;} [ "$strline" != "$lastline" ] && echo && (( LINECNT++ )) (( LINECNT % LINES )) || { printf "%s" "$headline" ; (( LINECNT++ )) ;} (( ( cnt==0 ) & ( LINECNT-1 <= $# ) )) || { printf '\r%(%T)T %2d/%d %s\e[K' -1 "$cnt" "${#pids[@]}" "$out" ret=$'\n' if [ "$strline" != "$lastline" ] ;then #next when strline differs echo (( LINECNT++ )) fi lastline="$strline" } done $DOPLOT && exec {GPFD}>&- [ -f "$TMPDATA" ] && rm "$TMPDATA" for i in "${ar[@]}";do printf '\r%s\e[K\n' "${stats[$i]}" done