63

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?

4

6 Answers 6

47

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))"
6
  • 1
    Can you explain why it is necessary to use $(($LINES - 2))" to get the latest entries?
    – bbaja42
    Jun 24, 2011 at 20:18
  • 7
    @bbaja42: It fills the screen, leaving a couple of lines for the output of watch itself. $LINES is an automatic variable that Bash and other shells use to contain the number of lines that the screen can display. You could tail any number you want. Jun 25, 2011 at 6:00
  • Can I use the command as is to pipe the output of multiple programs (chained using &&) to tail?
    – T0xicCode
    Feb 5, 2013 at 20:56
  • @xav0989: You would need to wrap your chain in curly braces or it will only tail the last command: { command1 && command2 && command3; } | tail - don't forget the spaces (after the opening and before closing brace) and semicolon after the last command. Remember, the whole chain will be executed each time watch reruns it. Feb 5, 2013 at 22:50
  • 3
    I'm not sure why this is the accepted answer. The OP asked "to scroll down or up" but this answer is a fixed offset from the end of the file and needs to be re-run to change the offset. Am I missing something here?
    – Hephaestus
    Dec 15, 2021 at 20:27
15

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

2
  • Wish, it was in bash! May 20, 2017 at 19:10
  • I tried it but found too much flicker on each update. Running on Ubuntu 20.
    – Hephaestus
    Dec 15, 2021 at 20:40
5

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

sudo pip install watchall
6
  • is this downvoted because the answer is not detailed enough ... or because the proposed program is not suitable?
    – RTbecard
    Dec 13, 2018 at 17:54
  • 1
    I downloaded the tar. Compared to pwatch, it looks like it supports -d for diffing like watch can. It also binds PgUp/PgDn, it also supports custom intervals.
    – Zren
    Mar 12, 2019 at 4:22
  • Works as expected. You can scroll while it is updating and the output will stay on the same line. Or it will start out on the same line as before the update. It makes it very useful with -d option, so you can monitor only some parts of the output.
    – ddofborg
    Jun 3, 2021 at 20:35
  • Tried on Ubuntu 20 with Py3.8. Line 132 has error; easy to fix (clearly some Py2.x syntax). But then: "line 121, in refresh_screen self.pad.addch('\n') _curses.error: add_wch() returned ERR". Still runs but arrow or direction keys don't seem to work.
    – Hephaestus
    Dec 15, 2021 at 20:11
  • 1
    Not usable in 2022. It even doesn't start (there is syntax error about missing parentheses). It is probably discontinued and really should be removed from the Python package repository. However, +1 for mentioning it - likely it has been useful for a while in former times.
    – Binarus
    Oct 8, 2022 at 6:55
5

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='\033[K'
ESC_reset_screen='\033c'
ESC_clr_screen='\033[2J'
ESC_cursor_pos='\033[0;0f'
ESC_cursor_home='\033[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%*s\n\n' "$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 "%s\n",$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
2
  • Alas this one fails too. After several updates the text tears with uneven updates until it is a jumbled mess.
    – Hephaestus
    Dec 15, 2021 at 20:50
  • I've updated your script to work on macOS, fixed some aspects, cleaned it up and put it here for anyone to check it out: pastebin.com/cR4KqUKr
    – ikaerom
    Nov 29, 2022 at 12:02
2

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)
1
  • Welcome to Unix! Great first question. Dec 22, 2022 at 1:47
0

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='\033[K'
ESC_reset_screen='\033c'
ESC_clr_screen='\033[2J'
ESC_cursor_pos='\033[0;0f'
ESC_cursor_home='\033[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%*s\n\n' "$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 "%s\n",$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'

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .