-
-
Save literallylara/fb2d47789ac3cd0ff9b771f5e7dc86f3 to your computer and use it in GitHub Desktop.
watch + tail = peek: show command output in a limited scrolling region.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| DESCRIPTION='watch + tail = peek: show command output in a limited scrolling region.' | |
| VERSION=1.0.4 | |
| AUTHOR='@literallylara' | |
| LICENSE='MIT' | |
| WEBSITE='https://codeberg.org/literallylara/scripts/src/branch/main/utility/peek' | |
| # Documentation: | |
| # man console_codes | |
| # man ascii | |
| # man terminfo | |
| # man bash | |
| # ECMA-48 Select Graphic Rendition | |
| SGR_RESET=$'\033[0m' | |
| SGR_REVERSE_VIDEO_ON=$'\033[7m' | |
| SGR_REVERSE_VIDEO_OFF=$'\033[27m' | |
| SGR_COLOR_DEFAULT_FG=$'\033[39m' | |
| SGR_COLOR_RED_FG=$'\033[31m' | |
| SGR_COLOR_GREEN_FG=$'\033[32m' | |
| SGR_COLOR_YELLOW_FG=$'\033[33m' | |
| # Script settings | |
| CHAR_RUNNING='⋯' | |
| CHAR_SUCCESS='✓' | |
| CHAR_ERROR='×' | |
| OUTPUT_PREFIX="${SGR_COLOR_YELLOW_FG}${SGR_REVERSE_VIDEO_ON} ${SGR_REVERSE_VIDEO_OFF}${SGR_COLOR_DEFAULT_FG} " | |
| ######################### | |
| ### UTILITY FUNCTIONS ### | |
| ######################### | |
| trunc() { | |
| n="$1" | |
| t="$2" | |
| if [ "${#t}" -le "$n" ]; then echo "$t"; else echo "${t:0:n-1}…"; fi | |
| } | |
| # Cursor position report (CPR) | |
| # - Command : ESC[6n | |
| # - Response : ESC[y;xR | |
| # man console_codes | |
| term_get_cursor_pos() { | |
| # The `read -p` trick comes from Stack Overflow (see blow). | |
| # It ensures that `read` does not block when executed from within a script. | |
| # https://unix.stackexchange.com/questions/88296/get-vertical-cursor-position/183121#183121 | |
| # -s = silent | |
| # -dR = delimiter set to R | |
| # -p = prompt | |
| # IFS = internal field separator(s) | |
| # _ = discarded variable (will be 'ESC') | |
| IFS='[;' read -r -s -dR -p $'\e[6n' _ row col | |
| echo "$row $col" | |
| } | |
| # Equivalent to: tput cup | |
| # See: man console_codes | |
| term_set_cursor_pos() { | |
| row=$1 | |
| col=$2 | |
| # ESC[y;xH, where y = row, x = column | |
| printf '\033[%d;%dH' "$((row + 1))" "$col" >"$(tty)" | |
| # tput cup $1 $2 | |
| } | |
| # Equivalent to: tput csr | |
| # See: man console_codes | |
| term_set_scroll_region() { | |
| top=$1 | |
| bottom=$2 | |
| # ESC[t;br, where t = top, b = bottom | |
| printf '\033[%d;%dr' "$((top + 1))" "$((bottom + 1))" >"$(tty)" | |
| # tput csr $1 $2 | |
| } | |
| term_del_from_cursor_to_end() { | |
| printf "\033[J" >"$(tty)" | |
| # tput ed | |
| } | |
| # Equivalent to: stty size | |
| term_get_size() { | |
| if stty size 2>/dev/null; then | |
| : # already printed to stdout | |
| elif command -v tput >/dev/null; then | |
| echo "$(tput lines) $(tput cols)" | |
| else | |
| echo 'Missing `stty size` or `tput` command' | |
| fi | |
| } | |
| # Equivalent to tput cuu | |
| # See: man console_codes | |
| term_move_cursor_up() { | |
| # ESC[NA, where N = number of rows to move up | |
| printf '\033[%dA' "$((MAX_LINES + 1))" >"$(tty)" | |
| } | |
| # Dynamic globals | |
| cols=$(term_get_size | cut -d' ' -f2) | |
| MAX_TITLE_LENGTH=$((cols - 4)) | |
| MAX_LINE_LENGTH=$((cols - 2)) | |
| SCRIPT_NAME="$(basename "$0")" | |
| print_help_and_exit() { | |
| echo "Usage: $SCRIPT_NAME [OPTIONS] -- COMMAND" | |
| echo '' | |
| echo 'Options:' | |
| echo ' -h, --help print help and exit' | |
| echo ' -t, --title header title to display. Defaults to COMMAND' | |
| echo ' -n, --max-lines maxmimum number of COMMAND output lines to show at once' | |
| echo ' -o, --out output file to write full COMMAND output to' | |
| echo " -v, --version print version and exit" | |
| echo '' | |
| echo 'Example:' | |
| echo " $SCRIPT_NAME -t 'Counting to 100' -- sh -c 'for i in {1..100}; do echo \$i; sleep .01; done'" | |
| exit | |
| } | |
| # argument parsing | |
| for arg in "$@"; do | |
| shift | |
| case "$arg" in | |
| '-h' | '--help') print_help_and_exit ;; | |
| '-t' | '--title') TITLE="$1" ;; | |
| '-n' | '--max-lines') MAX_LINES="$1" ;; | |
| '-o' | '--out') OUTFILE="$1" ;; | |
| '-v' | '--version') | |
| echo "$SCRIPT_NAME $VERSION - $DESCRIPTION" | |
| echo "License : $LICENSE" | |
| echo "Author : $AUTHOR" | |
| echo "Website : $WEBSITE" | |
| exit | |
| ;; | |
| '--') break ;; | |
| esac | |
| done | |
| # argument defaults | |
| if [ -z "$MAX_LINES" ] || [ "$MAX_LINES" = 0 ]; then MAX_LINES=5; fi | |
| if [ -z "$TITLE" ]; then TITLE="$(trunc $MAX_TITLE_LENGTH "$*")"; fi | |
| if [ -z "$OUTFILE" ]; then OUTFILE='/dev/null'; fi | |
| print_header() { | |
| if [ -z "$1" ]; then | |
| color=$SGR_COLOR_YELLOW_FG | |
| status=$CHAR_RUNNING | |
| elif [ "$1" = 0 ]; then | |
| color=$SGR_COLOR_GREEN_FG | |
| status=$CHAR_SUCCESS | |
| else | |
| color=$SGR_COLOR_RED_FG | |
| status=$CHAR_ERROR | |
| fi | |
| printf '%s%s %s %s %s%s\n' "$color" "$SGR_REVERSE_VIDEO_ON" "$status" "$TITLE" "$SGR_REVERSE_VIDEO_OFF" "$SGR_COLOR_DEFAULT_FG" | |
| } | |
| print_header | |
| # make sure we have $ARG_MAX_LINES below us | |
| for _ in $(seq "$MAX_LINES"); do printf '\n'; done | |
| # move cursor back up | |
| term_move_cursor_up $((MAX_LINES + 1)) | |
| # get current row | |
| row=$(term_get_cursor_pos | cut -d' ' -f1) | |
| # set terminal scroll region from $row to $row + $ARG_MAX_LINES | |
| term_set_scroll_region "$row" $((row + MAX_LINES)) | |
| # move cursor into scroll region | |
| term_set_cursor_pos "$row" 0 | |
| # start command, truncate and prefix lines | |
| (if [ "${#@}" -eq 1 ]; then | |
| # evaluate pipes, redirections, etc. | |
| eval "$@" | |
| else | |
| # normal execution | |
| "$@" | |
| fi) 2>&1 | tee "$OUTFILE" | sed -r "s/^(.{$MAX_LINE_LENGTH}).*/\1/; s/^/$OUTPUT_PREFIX/" | |
| # get command output | |
| EXIT_CODE="${PIPESTATUS[0]}" | |
| # reset scroll region | |
| term_set_scroll_region 0 "$(term_get_size | cut -d' ' -f1)" | |
| # move cursor to header | |
| term_set_cursor_pos $((row - 1)) 0 | |
| # update header | |
| print_header "$EXIT_CODE" | |
| # remove output | |
| term_del_from_cursor_to_end | |
| # forward exit code | |
| exit "$EXIT_CODE" |
Author
literallylara
commented
Apr 14, 2026

Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment