From 0f056815c17585756ed81aed7e712b823ba02080 Mon Sep 17 00:00:00 2001 From: Xu Wang <59418106+xwang1498@users.noreply.github.com> Date: Thu, 26 Jun 2025 11:13:05 -0400 Subject: [PATCH 1/2] better handling of SIGINT / Ctrl-C This change ignores SIGINT only for the pipeline elements downstream of ping. This way ping will correctly receive the SIGINT, print its summary and cleanly exit, and then downstream elements will cleanly close afterwards. --- prettyping | 54 +++++++++++++----------------------------------------- 1 file changed, 13 insertions(+), 41 deletions(-) diff --git a/prettyping b/prettyping index b6e7881..386eca8 100755 --- a/prettyping +++ b/prettyping @@ -198,52 +198,23 @@ 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}" "${PING_PARAMS[@]}" 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 '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" + while read -r line; do + printf '%s\n' "$line" done -) 2>&1 | "${AWK_BIN}" "${AWK_PARAMS[@]}" ' +) 2>&1 | ( + trap '' INT + + "${AWK_BIN}" "${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 +449,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 +860,4 @@ BEGIN { # Not needed when the output is a terminal, but does not hurt either. fflush() }' +) From c799c0aa53848e61e35d2f9a4b13eaec19162d42 Mon Sep 17 00:00:00 2001 From: Xu Wang <59418106+xwang1498@users.noreply.github.com> Date: Fri, 18 Jul 2025 14:29:46 -0400 Subject: [PATCH 2/2] make prettyping work with any posix-compatible shell --- README.md | 6 +- mockping.sh | 15 +++- prettyping | 248 +++++++++++++++++++++++++++------------------------- 3 files changed, 145 insertions(+), 124 deletions(-) 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 386eca8..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,143 +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 # Ignore SIGINT (Ctrl-C) downstream of ping, so the pipeline can cleanly finish when ping is interrupted. -"${PING_BIN}" "${PING_PARAMS[@]}" 2>&1 | ( +"${PING_BIN}" "$@" 2>&1 | ( trap '' INT if [ "${IS_TERMINAL}" = 1 ]; then # Print a message to notify the awk script about terminal size change. - trap 'echo SIGWINCH' WINCH + trap 'sigwinch=1; echo SIGWINCH' WINCH fi - while read -r line; do + 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 | ( trap '' INT - "${AWK_BIN}" "${AWK_PARAMS[@]}" ' + "${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 ) @@ -449,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] ESC_DEFAULT) + printf( BLOCK[block_index] ESC_DEFAULT ) CURR_COL++ } function print_missing_response(rtt) { - printf( ESC_RED "!" ESC_DEFAULT) + printf( ESC_RED "!" ESC_DEFAULT ) CURR_COL++ }