How can I scroll within the output of my watch command?

I use the watch command to see the contents of my directory changing as a script runs on it (via watch ls dir/)

It’s a great tool, except that I can’t seem to scroll down or up to see all of the contents once the number of entries fills the vertical length of the screen.

Is there a way to do this?

Asked By: Zaid

||

watch is great, but this is one of the things it can’t do. You can use tail to show the latest entries:

watch "ls -rtx dir/ | tail -n $(($LINES - 2))"
Answered By: Dennis Williamson

I’ve created a small program that does exactly what you want in python.
Find it here, it’s called pwatch.

Answered By: Joey Yakimowich Payne

You could use watchall python package; its usage is the same as watch.

sudo pip install watchall
Answered By: Mohammad

I’ve created a bash ** swatch ** program that does exactly what you want in bash. In the gif video, you can see how to scroll through a changing mmio file.

enter image description here

#!/bin/bash
#
# watch a file and scroll 
#
# keys => arrow-up/down, page-up/down, pos1, end
#
# usages:
#           swatch -n <timeout_watch> <file>
#           swatch <file>
#
# version:          1.1
# dependencies:     awk , tput , clear, read
# published:        https://unix.stackexchange.com/questions/3842/how-can-i-scroll-within-the-output-of-my-watch-command
# gif recording:    peek , https://github.com/phw/peek

#
# =============================================
# KEYCODES
# =============================================
# https://unix.stackexchange.com/questions/294908/read-special-keys-in-bash
# showkey -a


# =============================================
# DEFAULTS
# =============================================
fname=""
line_show_begin=1
line_show_begin_last=-1
console_lines_correction=4
timeout_watch=2
timeout_read=.1


# =============================================
# DEFINE Escape-Sequences
# =============================================

# http://ascii-table.com/ansi-escape-sequences-vt-100.php

ESC_clr_line='33[K'
ESC_reset_screen='33c'
ESC_clr_screen='33[2J'
ESC_cursor_pos='33[0;0f'
ESC_cursor_home='33[H'


# =============================================
# FUNCTIONS
# =============================================


function fn_help() {
cat << EOF
Usage: ./$0 [-n <timeout>] [<file>]  ,  timeout >0.1s , default 2s
EOF
}


function get_options() {

    [[ "$1" == "" ]] && { fn_help ; exit 1 ; }

    while [ -n "$1" ]; do

        case "$1" in

        -h|--help) 
            fn_help
            ;;

        -n) 
            [[ "$2" == "" ]] && { echo "Error: option -n required <timeout>" ; exit 1 ; }
            if [[ "$(echo "$2<0.1"|bc)" == "0" ]] ; then 
                timeout_watch="$2" 
                shift
            else
                echo "Error: timeout <0.1 not allowed"
                exit 1
            fi
            ;;

        -*) 
                echo "Error: unknown option »$1«"
                exit 1
            ;;

        *)
            if [[ -f "$1" ]] ; then
                fname=$1
            else
                echo "Error: file not found »$1«"
                exit 1
            fi
            ;;

        esac

        shift

    done

    [[ "$fname" == "" ]] && { echo "Error: file required" ; exit 1 ; }

}


function fn_print_headline() {

    hdl_txt_right="${HOSTNAME}: $(date "+%Y-%m-%d %H:%M:%S")"
    hdl_txt_left="$fname , ${timeout_watch}s , $line_show_begin"

    hdl_txt_left_length=${#hdl_txt_left}

    printf '%s%*snn' "$hdl_txt_left" "$(($console_columns-$hdl_txt_left_length))" "$hdl_txt_right"

}


function fn_print_file() {

    # ---------------------------------------------------
    # file lenght can change while watch
    # ---------------------------------------------------

    lines_fname=$(awk 'END {print NR}' $fname)

    line_last=$(($lines_fname-$console_lines))

    (( "$line_last" < "1" )) && { line_last=1; clear; }

    (( "$line_show_begin" > "$line_last" )) && { line_show_begin=$line_last; clear; }


    # ---------------------------------------------------
    # print postion 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
    awk -v var1="$line_show_begin" -v var2="$console_lines" 'NR>=var1 {if (NR>var1+var2) {exit 0} else {printf "%sn",$0 } }' $fname

}


function fn_console_size_change() {

    console_columns=$(tput cols)
    console_lines=$(($(tput lines)-$console_lines_correction))
    line_show_begin_last=-1

}


function fn_quit() {

    echo "quit" $0 , $?

    setterm -cursor on ; exit 0

}


# =============================================
# GET OPTIONS
# =============================================

get_options "$@"    # pass all arguments with double-quotes



# =============================================
# INIT TRAP
# =============================================

trap "fn_console_size_change" SIGWINCH # https://en.wikipedia.org/wiki/Signal_(IPC)#SIGWINCH
trap "fn_quit" INT TERM EXIT


# =============================================
# MAIN
# =============================================

fn_console_size_change
setterm -cursor off


while true ; do

    fn_print_file

    read -rsn1 -t $timeout_watch k # char 1

    case "$k" in

    [[:graph:]])
        # Normal input handling
        ;;
    $'x09') # TAB
        # Routine for selecting current item
        ;;
    $'x7f') # Back-Space
        # Routine for back-space
        ;;
    $'x01') # Ctrl+A
        # 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)  # Arrow-Up-Key
            (( "$line_show_begin" > "1" )) && line_show_begin=$(($line_show_begin-1))
            ;;

        B)  # Arrow-Down-Key
            (( "$line_show_begin" < "$line_last" )) && line_show_begin=$(($line_show_begin+1))
            ;;

        H)  # Pos1-Key
            line_show_begin=1
            ;;

        F)  # End-Key
            line_show_begin=$line_last
            ;;

        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
Answered By: bashprogger

I edited the above script to work with command line

#!/bin/bash
#
# watch a file and scroll
#
# keys => arrow-up/down, page-up/down, pos1, end
#
# usages:
#           swatch -n <timeout_watch> <file>
#           swatch <file>
#
# version:          1.1
# dependencies:     awk , tput , clear, read
# published:        https://unix.stackexchange.com/questions/3842/how-can-i-scroll-within-the-output-of-my-watch-command
# gif recording:    peek , https://github.com/phw/peek

#
# =============================================
# KEYCODES
# =============================================
# https://unix.stackexchange.com/questions/294908/read-special-keys-in-bash
# showkey -a


# =============================================
# DEFAULTS
# =============================================
command=""
TMPFILE=$(mktemp)
line_show_begin=1
line_show_begin_last=-1
console_lines_correction=4
timeout_watch=5
timeout_read=.1


# =============================================
# DEFINE Escape-Sequences
# =============================================

# http://ascii-table.com/ansi-escape-sequences-vt-100.php

ESC_clr_line='33[K'
ESC_reset_screen='33c'
ESC_clr_screen='33[2J'
ESC_cursor_pos='33[0;0f'
ESC_cursor_home='33[H'


# =============================================
# FUNCTIONS
# =============================================


function fn_help() {
cat << EOF
Usage: ./$0 [-n <timeout>] [<command>]  ,  timeout >0.1s , default 5s
EOF
}


function get_options() {
    [[ "$1" == "" ]] && { fn_help ; exit 1 ; }
    while [ -n "$1" ]; do
        case "$1" in
            -h|--help)
                fn_help
            ;;
            -n)
                [[ "$2" == "" ]] && { echo "Error: option -n required <timeout>" ; exit 1 ; }
                if [[ "$(echo "$2<0.1"|bc)" == "0" ]] ; then
                    timeout_watch="$2"
                    shift
                else
                    echo "Error: timeout <0.1 not allowed"
                    exit 1
                fi
            ;;
            -*)
                echo "Error: unknown option »$1«"
                exit 1
            ;;
            *)
                #if [[ -f "$1" ]] ; then
                command=$1
                #else
                #    echo "Error: file not found »$1«"
                #    exit 1
                #fi
            ;;
        esac
        shift
    done
    [[ "$command" == "" ]] && { echo "Error: command required" ; exit 1 ; }
}


function 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%*snn' "$hdl_txt_left" "$(($console_columns-$hdl_txt_left_length))" "$hdl_txt_right"
}


function fn_print_file() {
    # ---------------------------------------------------
    # file lenght can change while watch
    # ---------------------------------------------------
    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 postion 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 "%sn",$0 } }' $TMPFILE
}


function fn_console_size_change() {
    console_columns=$(tput cols)
    console_lines=$(($(tput lines)-$console_lines_correction))
    line_show_begin_last=-1
}


function fn_quit() {
    echo "quit" $0 , $?
    setterm -cursor on ; exit 0
}


# =============================================
# GET OPTIONS
# =============================================

get_options "$@"    # pass all arguments with double-quotes



# =============================================
# INIT TRAP
# =============================================

trap "fn_console_size_change" SIGWINCH # https://en.wikipedia.org/wiki/Signal_(IPC)#SIGWINCH
trap "fn_quit" INT TERM EXIT


# =============================================
# MAIN
# =============================================

fn_console_size_change
setterm -cursor off


while true ; do
    fn_print_file
    read -rsn1 -t $timeout_watch k # char 1
    case "$k" in
        [[:graph:]])
            # Normal input handling
        ;;
        $'x09') # TAB
            # Routine for selecting current item
        ;;
        $'x7f') # Back-Space
            # Routine for back-space
        ;;
        $'x01') # Ctrl+A
            # 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)  # Arrow-Up-Key
                    (( "$line_show_begin" > "1" )) && line_show_begin=$(($line_show_begin-1))
                ;;
                B)  # Arrow-Down-Key
                    (( "$line_show_begin" < "$line_last" )) && line_show_begin=$(($line_show_begin+1))
                ;;
                H)  # Pos1-Key
                    line_show_begin=1
                ;;
                F)  # End-Key
                    line_show_begin=$line_last
                ;;
                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
  • create a file in ~/bin/cwatch.sh
nano ~/bin/cwatch.sh
  • change files properties to make it runnable:
chmod +x ~/bin/cwatch.sh
  • edit ~/.bashrc and add alias
alias cwatch="~/bin/cwatch.sh"

now you can try it:

cwatch 'ps aux | grep -v grep'
Answered By: Khalid M. Kahloot

You can use viddy.

It’s a binary that has the basic features of original watch command, including color output and diff highlight, but allows scroll and has a couple more cool features including text search and time machine mode, which allows one go back to the previous versions of the output.

The current one-liner to install it is

wget -O viddy.tar.gz https://github.com/sachaos/viddy/releases/download/v0.3.6/viddy_0.3.6_Linux_x86_64.tar.gz && tar xvf viddy.tar.gz && sudo mv viddy /usr/local/bin

And then you can use it like, for example

viddy -d -n 1 ls dir/

to list the dir every second and to highlight the changes. While viddy is running, press ? to get the keyboard shortcuts.

viddy cmd options:

$ viddy -h

Usage:
 viddy [options] command

Options:
  -b, --bell                 ring terminal bell changes between updates
  -d, --differences          highlight changes between updates
  -n, --interval <interval>  seconds to wait between updates (default "2s")
  -p, --precise              attempt run command in precise intervals
  -c, --clockwork            run command in precise intervals forcibly
  -t, --no-title             turn off header
  --shell                    shell (default "sh")
  --shell-options            additional shell options
  --unfold                   unfold command result
  --pty                      run on pty (experimental, not for Windows)
Answered By: Dima Mironov
Categories: Answers Tags:
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.