diff --git a/README.md b/README.md index df92817..dd0e8be 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ making the output prettier, more colorful, more compact, and easier to read. showing the ping responses in a *graphical* way at the terminal (by using colors and Unicode characters). -`prettyping` is written in `bash` and `awk`, and is reported to work on many +`prettyping` is written in `sh` and `awk`, and is reported to work on many different systems (Linux, Mac OS X, BSD…), as well as running on different versions of `awk` (`gawk`, `mawk`, `nawk`, `busybox awk`). @@ -20,7 +20,7 @@ screenshots, videos at: Requirements ------------ -* `bash` (tested on 4.20, should work on versions as old as 2008) +* posix-compatible shell, e.g. `bash` or `dash` * `awk` (either [gawk][], [mawk][], [nawk][] or [busybox awk][]; should work on `gawk` versions as old as 2008; should probably work on any other awk implementation) @@ -35,7 +35,7 @@ Installation 2. Make it executable: `chmod +x prettyping` That's all! No root permission is required. You can save and run it from any -directory. As long as your user can run `ping`, `bash` and `awk`, then +directory. As long as your user can run `ping`, `sh` and `awk`, then `prettyping` will work. Alternatively, you can download the latest tarball from GitHub: [![Latest release](https://img.shields.io/github/release/denilsonsa/prettyping.svg)](https://github.com/denilsonsa/prettyping/releases/latest) diff --git a/mockping.sh b/mockping.sh index 9d13bbf..a041a26 100755 --- a/mockping.sh +++ b/mockping.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/bin/sh # # This is just a mock ping program that reproduces the same output all the # time. It is used for testing/developing prettyping. @@ -8,6 +8,7 @@ sample_output() { PING registro.br (200.160.2.3) 56(84) bytes of data. Request timeout for icmp_seq 1 64 bytes from registro.br (200.160.2.3): icmp_seq=2 ttl=56 time=25.5 ms +SIGWINCH 64 bytes from registro.br (200.160.2.3): icmp_seq=3 ttl=56 time=55.7 ms 64 bytes from registro.br (200.160.2.3): icmp_seq=4 ttl=56 time=75.2 ms ping: sendto: Network is down @@ -49,7 +50,13 @@ rtt min/avg/max/mdev = 36.750/38.535/40.048/1.360 ms EOF } -sample_output | while read line; do - echo -E "$line" - sleep 0.25s +pgid=$(ps -o pgid= $$ | tr -d '[:space:]') + +sample_output | while read -r line; do + if [ "$line" = "SIGWINCH" ]; then + kill -WINCH -- -"$pgid" + continue + fi + printf '%s\n' "$line" + sleep "${SLEEP_SECS:-0.25}" done diff --git a/prettyping b/prettyping index b6e7881..9fa2995 100755 --- a/prettyping +++ b/prettyping @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +#!/bin/sh # # Written by Denilson Figueiredo de Sá # MIT license @@ -78,172 +78,157 @@ All other parameters are passed directly to ping. EOF } +MYNAME=$(basename "$0") + # Thanks to people at #bash who pointed me at # https://web.archive.org/web/20100301171512/https://bash-hackers.org/wiki/doku.php/scripting/posparams -parse_arguments() { - USE_COLOR=1 - USE_MULTICOLOR=1 - USE_UNICODE=1 - USE_LEGEND=1 - USE_GLOBALSTATS=1 - USE_RECENTSTATS=1 - - if [ -t 1 ]; then - IS_TERMINAL=1 - else - IS_TERMINAL=0 - fi - - LAST_N=60 - OVERRIDE_COLUMNS=0 - OVERRIDE_LINES=0 - RTT_MIN=auto - RTT_MAX=auto - - PING_BIN="ping" - #PING_BIN="./mockping.awk" - PING_PARAMS=( ) - - AWK_BIN="awk" - AWK_PARAMS=( ) - - while [[ $# != 0 ]] ; do - case "$1" in - -h | -help | --help ) - print_help - exit - ;; - - # Forbidden ping parameters within prettyping: - -f ) - echo "${MYNAME}: You can't use the -f (flood) option." - exit 1 - ;; - -R ) - # -R prints extra information at each ping response. - echo "${MYNAME}: You can't use the -R (record route) option." - exit 1 - ;; - -q ) - echo "${MYNAME}: You can't use the -q (quiet) option." - exit 1 - ;; - -v ) - # -v enables verbose output. However, it seems the output with - # or without this option is the same. Anyway, prettyping will - # strip this parameter. - ;; - # Note: - # Small values for -s parameter prevents ping from being able to - # calculate RTT. - - # New parameters: - -a ) - # TODO: Implement audible ping for responses or for missing packets - ;; - - -color | --color ) USE_COLOR=1 ;; - -nocolor | --nocolor ) USE_COLOR=0 ;; - -multicolor | --multicolor ) USE_MULTICOLOR=1 ;; - -nomulticolor | --nomulticolor ) USE_MULTICOLOR=0 ;; - -unicode | --unicode ) USE_UNICODE=1 ;; - -nounicode | --nounicode ) USE_UNICODE=0 ;; - -legend | --legend ) USE_LEGEND=1 ;; - -nolegend | --nolegend ) USE_LEGEND=0 ;; - -globalstats | --globalstats ) USE_GLOBALSTATS=1 ;; - -noglobalstats | --noglobalstats ) USE_GLOBALSTATS=0 ;; - -recentstats | --recentstats ) USE_RECENTSTATS=1 ;; - -norecentstats | --norecentstats ) USE_RECENTSTATS=0 ;; - -terminal | --terminal ) IS_TERMINAL=1 ;; - -noterminal | --noterminal ) IS_TERMINAL=0 ;; - - -awkbin | --awkbin ) AWK_BIN="$2" ; shift ;; - -pingbin | --pingbin ) PING_BIN="$2" ; shift ;; - - #TODO: Check if these parameters are numbers. - -last | --last ) LAST_N="$2" ; shift ;; - -columns | --columns ) OVERRIDE_COLUMNS="$2" ; shift ;; - -lines | --lines ) OVERRIDE_LINES="$2" ; shift ;; - -rttmin | --rttmin ) RTT_MIN="$2" ; shift ;; - -rttmax | --rttmax ) RTT_MAX="$2" ; shift ;; - - * ) - PING_PARAMS+=("$1") - ;; - esac +USE_COLOR=1 +USE_MULTICOLOR=1 +USE_UNICODE=1 +USE_LEGEND=1 +USE_GLOBALSTATS=1 +USE_RECENTSTATS=1 + +if [ -t 1 ]; then + IS_TERMINAL=1 +else + IS_TERMINAL=0 +fi + +LAST_N=60 +OVERRIDE_COLUMNS=0 +OVERRIDE_LINES=0 +RTT_MIN=0 +RTT_MAX=0 + +PING_BIN="ping" +#PING_BIN="./mockping.sh" + +AWK_BIN="awk" + +skipnext=0 +for arg; do + if [ "$skipnext" -eq 1 ]; then + skipnext=0 shift - done - - if [[ "${RTT_MIN}" -gt 0 && "${RTT_MAX}" -gt 0 && "${RTT_MIN}" -ge "${RTT_MAX}" ]] ; then - echo "${MYNAME}: Invalid --rttmin and -rttmax values." - exit 1 + continue fi + case "$1" in + -h | -help | --help ) + print_help + exit + ;; + + # Forbidden ping parameters within prettyping: + -f ) + echo "${MYNAME}: You can't use the -f (flood) option." + exit 1 + ;; + -R ) + # -R prints extra information at each ping response. + echo "${MYNAME}: You can't use the -R (record route) option." + exit 1 + ;; + -q ) + echo "${MYNAME}: You can't use the -q (quiet) option." + exit 1 + ;; + -v ) + # -v enables verbose output. However, it seems the output with + # or without this option is the same. Anyway, prettyping will + # strip this parameter. + ;; + # Note: + # Small values for -s parameter prevents ping from being able to + # calculate RTT. + + # New parameters: + -a ) + # TODO: Implement audible ping for responses or for missing packets + ;; + + -color | --color ) USE_COLOR=1 ;; + -nocolor | --nocolor ) USE_COLOR=0 ;; + -multicolor | --multicolor ) USE_MULTICOLOR=1 ;; + -nomulticolor | --nomulticolor ) USE_MULTICOLOR=0 ;; + -unicode | --unicode ) USE_UNICODE=1 ;; + -nounicode | --nounicode ) USE_UNICODE=0 ;; + -legend | --legend ) USE_LEGEND=1 ;; + -nolegend | --nolegend ) USE_LEGEND=0 ;; + -globalstats | --globalstats ) USE_GLOBALSTATS=1 ;; + -noglobalstats | --noglobalstats ) USE_GLOBALSTATS=0 ;; + -recentstats | --recentstats ) USE_RECENTSTATS=1 ;; + -norecentstats | --norecentstats ) USE_RECENTSTATS=0 ;; + -terminal | --terminal ) IS_TERMINAL=1 ;; + -noterminal | --noterminal ) IS_TERMINAL=0 ;; + + -awkbin | --awkbin ) AWK_BIN="$2" ; skipnext=1 ;; + -pingbin | --pingbin ) PING_BIN="$2" ; skipnext=1 ;; + + #TODO: Check if these parameters are numbers. + -last | --last ) LAST_N="$2" ; skipnext=1 ;; + -columns | --columns ) OVERRIDE_COLUMNS="$2" ; skipnext=1 ;; + -lines | --lines ) OVERRIDE_LINES="$2" ; skipnext=1 ;; + -rttmin | --rttmin ) RTT_MIN="$2" ; skipnext=1 ;; + -rttmax | --rttmax ) RTT_MAX="$2" ; skipnext=1 ;; + + * ) + # unprocessed parameters will cycle to the front of $@ to be passed to `ping` + set -- "$@" "$1" + ;; + esac + shift +done + +if [ "${RTT_MIN}" -gt 0 ] && [ "${RTT_MAX}" -gt 0 ] && [ "${RTT_MIN}" -ge "${RTT_MAX}" ] ; then + echo "${MYNAME}: Invalid --rttmin and -rttmax values." + exit 1 +fi + +if [ $# -eq 0 ] ; then + echo "${MYNAME}: Missing parameters, use --help for instructions." + exit 1 +fi + +# Workaround for mawk: +# https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=593504 +awk_version="$(echo | "${AWK_BIN}" -W version 2>&1)" +case "$awk_version" in + mawk*) AWK_PARAMS="-W interactive" ;; +esac - if [[ "${#PING_PARAMS[@]}" = 0 ]] ; then - echo "${MYNAME}: Missing parameters, use --help for instructions." - exit 1 - fi - # Workaround for mawk: - # https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=593504 - local version="$(echo | "${AWK_BIN}" -W version 2>&1)" - if [[ "${version}" == mawk* ]] ; then - AWK_PARAMS+=(-W interactive) - fi -} - -MYNAME=`basename "$0"` -parse_arguments "$@" export LC_ALL=C -# Warning! Ugly code ahead! -# The code is so ugly that the comments explaining it are -# bigger than the code itself! -# -# Suppose this: -# -# cmd_a | cmd_b & -# -# I need the PID of cmd_a. How can I get it? -# In bash, $! will give me the PID of cmd_b. -# -# So, I came up with this ugly solution: open a subshell, like this: -# -# ( -# cmd_a & -# echo "This is the PID I want $!" -# wait -# ) | cmd_b - +# Ignore SIGINT (Ctrl-C) downstream of ping, so the pipeline can cleanly finish when ping is interrupted. -# Ignore Ctrl+C here. -# If I don't do this, this shell script is killed before -# ping and gawk can finish their work. -trap '' 2 +"${PING_BIN}" "$@" 2>&1 | ( + trap '' INT -# Now the ugly code. -( - "${PING_BIN}" "${PING_PARAMS[@]}" & - PING_PID="$!" - - # Commented out, because it looks like this line is not needed - #trap "kill -2 $PING_PID ; exit 1" 2 # Catch Ctrl+C here - - wait -) 2>&1 | ( if [ "${IS_TERMINAL}" = 1 ]; then # Print a message to notify the awk script about terminal size change. - trap "echo SIGWINCH" 28 + trap 'sigwinch=1; echo SIGWINCH' WINCH fi - # The trap must be in another subshell because otherwise it will interrupt - # the "wait" commmand. - while read line; do - echo -E "$line" + sigwinch=0 + while true; do + if ! read -r line; then + # some shells will interrupt read for the SIGWINCH, in which case we need to restart it + if [ "$sigwinch" -eq 1 ]; then + sigwinch=0 + continue + else + break + fi + fi + printf '%s\n' "$line" done -) 2>&1 | "${AWK_BIN}" "${AWK_PARAMS[@]}" ' +) 2>&1 | ( + trap '' INT + + "${AWK_BIN}" ${AWK_PARAMS:+$AWK_PARAMS} ' # Weird that awk does not come with abs(), so I need to implement it. function abs(x) { return ( (x < 0) ? -x : x ) @@ -478,12 +463,12 @@ function print_received_response(rtt, block_index) { } else { block_index = 1 + int((rtt - BLOCK_RTT_MIN) * (BLOCK_LEN - 2) / BLOCK_RTT_RANGE) } - printf( BLOCK[block_index] ) + printf( BLOCK[block_index] ESC_DEFAULT ) CURR_COL++ } function print_missing_response(rtt) { - printf( ESC_RED "!" ) + printf( ESC_RED "!" ESC_DEFAULT ) CURR_COL++ } @@ -889,3 +874,4 @@ BEGIN { # Not needed when the output is a terminal, but does not hurt either. fflush() }' +)