#!/usr/bin/env bash # watch a file or command output and scroll within # # keys => arrow-up/down, page-up/down, pos1, end # # usages: # swatch.sh -n # swatch.sh # # version: 1.2 (macOS compatible) # dependencies: awk, tput, clear, read # published: https://unix.stackexchange.com/questions/3842/how-can-i-scroll-within-the-output-of-my-watch-command # ============================================= # DEFAULTS # ============================================= TMPFILE=$(mktemp) line_show_begin="1" line_show_begin_last="-1" console_lines_correction="4" timeout_watch="2" timeout_read=".1" # http://ascii-table.com/ansi-escape-sequences-vt-100.php ESC_cursor_home='\033[H' define(){ IFS=$'\n' read -r -d '' "${1}" || true; } define helptext <<-EOF Usage: ${0##*/} [-n ] '[]' timout : in seconds or fractions of seconds [default: ${timeout_watch}s] command: any command wished to be observed - best applied in quotes EOF __init() { trap "fn_console_size_change" SIGWINCH trap "fn_quit" INT HUP TERM QUIT EXIT fn_console_size_change tput civis } fn_help() { printf "%s\n" "${helptext}" } get_options() { [[ "$1" == "" ]] && fn_help 1 while [ -n "$1" ]; do case "$1" in -h|--help) fn_help 0 ;; -n) [[ "$2" == "" ]] && { echo "Error: option -n required " ; exit 1 ; } timeout_watch="$2" && shift ;; -*) echo "Error: unknown option »$1«"; exit 1; ;; *) command=$1;; esac shift done [[ "$command" == "" ]] && { echo "Error: command required" ; exit 1 ; } } fn_print_headline() { hdl_txt_right="${HOSTNAME}: $(date "+%Y-%m-%d %H:%M:%S")" hdl_txt_left="$command, [${timeout_watch}s, $line_show_begin]" hdl_txt_left_length=${#hdl_txt_left} printf '%s%*s\n\n' "$hdl_txt_left" "$((console_columns - hdl_txt_left_length))" "$hdl_txt_right" } fn_print_file() { # file length can change during watch execution eval "$command" > "$TMPFILE" lines_command=$(awk 'END {print NR}' "$TMPFILE") line_last=$((lines_command-console_lines)) (( "$line_last" < "1" )) && { line_last=1; clear; } (( "$line_show_begin" > "$line_last" )) && { line_show_begin=$line_last; clear; } # print position changed if (( "$line_show_begin" != "$line_show_begin_last" )); then line_show_begin_last=$line_show_begin clear else printf "$ESC_cursor_home" fi # print file section fn_print_headline eval "$command" > "$TMPFILE" awk -v var1="$line_show_begin" -v var2="$console_lines" 'NR>=var1 {if (NR>var1+var2) {exit 0} else {printf "%s\n",$0 } }' "$TMPFILE" } fn_console_size_change() { console_columns=$(tput cols) console_lines=$(($(tput lines) - console_lines_correction)) line_show_begin_last=-1 } fn_quit() { tput cnorm tput clear exit 0 } # ============================================= # MAIN # ============================================= get_options "$@" __init while :; do fn_print_file read -rsn1 -t "$timeout_watch" k # char 1 case "$k" in [[:graph:]]) # Normal input handling ;; $'\x09') # Routine for selecting current item ;; $'\x7f') # Routine for back-space ;; $'\x01') # Routine for ctrl+a ;; $'\x1b') # ESC read -rsn1 k # char 2 [[ "$k" == "" ]] && return # Esc-Key [[ "$k" == "[" ]] && read -rsn1 -t $timeout_read k # char 3 [[ "$k" == "O" ]] && read -rsn1 -t $timeout_read k # char 3 case "$k" in A) (( "$line_show_begin" > "1" )) && ((line_show_begin--)) ;; # Arrow-Up-Key B) (( "$line_show_begin" < "$line_last" )) && ((line_show_begin++)) ;; # Arrow-Down-Key H) line_show_begin=1 ;; # Pos1-Key F) line_show_begin=$line_last ;; # End-Key 5) # PgUp-Key read -rsn1 -t $timeout_read k # char 4 if [[ "$k" == "~" ]] && (( "$line_show_begin" > "$((console_lines/2))" )) ; then line_show_begin=$((line_show_begin-console_lines/2)) else line_show_begin=1 fi ;; 6) # PgDown-Key read -rsn1 -t $timeout_read k # char 4 if [[ "$k" == "~" ]] && (( "$line_show_begin" < "$((line_last-console_lines/2))" )) ; then line_show_begin=$((line_show_begin+console_lines/2)) else line_show_begin=$line_last fi ;; esac read -rsn4 -t $timeout_read # Try to flush out other sequences ... ;; esac done