30

Under a UNIX shell, how can I get a similar effect to the watch command, but with paging so that I can scroll around in the output if it takes up more than one screen?

In other words, I want a program that is to watch what less is to cat.

As an example, lets say I wanted to watch the output of qstat, I could use

watch qstat

to watch the output of qstat, but this can only shows the first screenful.

With a paging version of watch, I would be able to move around in the output as it is still continuously updated by watch. Is there any way to do this at the moment with existing utilities?

5
  • This probably belongs on SuperUser or maybe ServerFault.
    – Dav
    Aug 6, 2009 at 4:59
  • You are probably right, how can I move it?
    – David Dean
    Aug 6, 2009 at 5:14
  • Three people have voted to move it so far; if two more people (or a moderator) vote to move it, then it will happen automatically. Aug 6, 2009 at 5:27
  • Why not just open a bigger window and go away with paging altogether?
    – Marcin
    Aug 7, 2009 at 0:22
  • 3
    because my screen is already full? why use less when you can just use cat and a bigger window?
    – David Dean
    Aug 7, 2009 at 0:34

13 Answers 13

21
+50

Rather than modifying the 'watch' command, use screen!

For example, let's say that you need to be able to see 300 lines of height and 100 characters of width and move around that. After starting screen, force the size thus:

C-a :height -w 300
C-a :width -w 100

Now start your watch command. You can then use C-a <ESC> to page around the display.

Unfortunately, the display doesn't refresh while in copy mode. But if you want to adjust which section of the window you're viewing, the easiest way may be to rerun the height/width commands as by default your terminal shows the lower-right of the virtual window.

5
  • the only issue then is how to continually repeat the command, while blanking the screen between each go
    – David Dean
    Aug 18, 2009 at 1:12
  • Oops, I meant run the watch command inside screen. Fixed.
    – MikeyB
    Aug 18, 2009 at 13:19
  • yartls - yet another reason to love screen :)
    – warren
    Sep 7, 2009 at 7:30
  • note that C- is aka ctrl-
    – henry
    May 9, 2018 at 14:23
  • What is the tmux version of this? Sep 13, 2022 at 5:09
8

You can try this:

$ while vmstat; do sleep 1; done | less

replace vmstat with qstat and adjust the sleep to your needs.

3
  • all this does is keep repeating the command into less, which means that you need to keep scrolling to see the latest output.
    – David Dean
    Aug 14, 2009 at 6:32
  • 1
    Yes, but you can scroll back. You can't have both at once. Pressing shift f, that is capital 'F' will work like tail. Aug 14, 2009 at 14:06
  • 1
    Append +F to "pre-press" F and automatically follow: while vmstat; do sleep 1; done | less +F
    – rymo
    Jan 4, 2020 at 3:06
4

Multitail: http://www.vanheusden.com/multitail/

Example:

 vmstat 1 |multitail -j

Scroll back by press 'b' and page/arrow up/down.

1
  • Ok, this is for vmstat - but how would it work for watch? if I try watch -n1 ls -la | multitail -j, I get just an empty screen? EDIT: I can do something like multitail -R 1 -l "ls -la" (that would be watch -n1 - but I cannot scroll in that window? Ah, networkworld.com/article/3445228/using-multitail-on-linux.html - for scrolling you need to press b, but then the window that gets shown there, gets deleted on next 1 sec refresh, so its useless for that)
    – sdbbs
    Mar 29, 2023 at 11:32
2

OK, I've had a little go at a watchless function. It's a bit rough, and it doesn't yet appear to completely work, but here goes:

#!/bin/bash -u
out=$(mktemp)
(while [ 1 ]; do
    "$@" > $out;
    sleep 2;
done) &
less $out
kill $!

You have to manually use the R key in less to get the display to update.

It appears to work for watchless date but not for watchless qstat or watchless pstree, which both show blank. Any ideas?

1
  • You should use a second file to write the output of the next run of the command and then mv this second file to the file read by less as mv is atomic. Otherwise less could think that the file has disappeared if a refresh is requested at the same time as the file is being written to with "$@" > $out. You should also write to the output file once before starting the loop.
    – Mmmh mmh
    Aug 28, 2016 at 19:28
1

I implement a simple python script to satisfy this request, named "watchall"

get it by: pip install watchall

replace watch with watchall and enjoy scrollable screen. now it only supports -n and -d flags.

1
0

I don't see how this could be implemented as the row contents change, and watch would reset back to first line every 2 seconds even if you could scroll down.

Some workarounds are:

watch 'qstat | tail -n40' to show output of qstat beginning from 40th line from bottom

watch 'qstat | grep jsmith' to grep the output so the lines you are interested in are always in the first screen.

Note that you need to put the commands around the pipe in single quotes - otherwise you will be piping the output of watch, not the output of qstat.

0

To continue on enkrs's answer,

watch 'qstat | head -300 | tail -15'

will get you arbitrary pages into the qstat's output.

0

Here's a rather crude script that seems to work for several commands that I threw at it

#!/bin/bash
# ---- mywatch.sh ----

if [ $# -lt 1 && $# -gt 2 ]; then
    echo "Usage: $0 <command> <delay>" 
    exit 1
fi

CMD=$1
if [ $# -eq 2 ]; then
  DELAY=$2
else
  DELAY=2 # default
fi

while : ; do
  ( (echo -e "Every ${DELAY}s: $CMD\n"; $CMD) | less )&
  PID=$!
  sleep $DELAY
  kill -9 $PID &> /dev/null
  clear
done

Used as such:

alias mywatch="~/bin/mywatch.sh"

mywatch vmstat
mywatch "ps aux" # commands in options need to be quoted
mywaych pstree 10 # change delays

Being rather pedantic, the transition between refreshes aren't as smooth as I would like it to be. Naturally, being a simple script it doesn't support highlight of diff (watch -d). Also, the parsing of input arguments can be done better.

0

How about just: tail -f

0

I am using this:

while cat /etc/wgetrc; do sleep 10; done | pv -q -L 150 | tail -n +0 -f
0

I edited the script here 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'
0

In case this could be useful to others, this is how I solved the issue on my side. In the "cmd" function, plug any command you want to monitor.

#!/bin/bash

function cmd {
  sudo lsof -i -n -P | grep 11.12.13.14:8883 
}

echo "" >previous.log
while true; do 
  cmd >out.log  
  diff out.log previous.log >/dev/null 
  cc="$?"
  if [[ "$cc" != "0" ]]; then
    date=`date --rfc-3339=seconds`
    epoch=`date +%s`
    echo ""
    echo "Changes on $date ($epoch)" 
    cat out.log
    cp out.log previous.log
  fi
  sleep 0.5
done    
-1

you can try:

watch command > file

then in your file you should see the appendend output (I don't have a linux box rigth now to test this)

3
  • 1
    That isn't going to do what you intend, really, it will just fill up file with the same output over and over. It certainly doesn't address the original question. Aug 6, 2009 at 4:55
  • then I dunno :)
    – dfa
    Aug 6, 2009 at 5:08
  • 1
    Change the ">" to ">>" to make it append the data to to the file? Aug 12, 2009 at 10:32

You must log in to answer this question.

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