Search code examples
bashwatchansi-escape

Bash Script: Updating Top Half Of The Screen Separately Using Watch


I'm working on a bash script that displays a random ANSI art banner on the top 16 lines of the screen, and then the project view of my todo.txt list in the remaining lines. This is intended to live in one pane of my tmux window, so that the right third of my screen (roughly 80x48 characters) is showing my todo list at all times.

Right now I've got the banner randomizing on load, and then the todo list updates via fswatch whenever I add or remove something. I would also like to update the banner once every 15 minutes or so, so I'd like to use something like watch to run the command to cat a random ANSI banner on a 15 minute interval, but when using watch, the banner portion of the script just blanks out the entire screen. (the code below has a 10 second interval used for testing)

Is there a better way to be doing this, or a way to get watch to start outputting the banners correctly?

Here's the script:

#!/bin/bash
clear
banner.sh
/usr/local/bin/todo.sh projectview | fold -w 80 -s

watch -t -c -n 10 banner.sh # this just gives me a blank screen and hangs

fswatch -l 1 -o --event=Updated -e "~/.todo/.*" -i "todo.txt" ~/.todo | while read;
        do
                tput cup 18 0 && tput ed && /usr/local/bin/todo.sh projectview | fold -w 80 -s # draw the todo list starting on the 18th line.
        done

And here's banner.sh:

#!/bin/bash

filename=`ls -d ~/banners/* | shuf -n 1`
tput cup 0 0 && cat $filename && echo ""

Solution

  • It would appear that cat won't produce colored output in a format that watch will accept, as watching ls or some other colored output works fine, but nothing involving cat and colored output will do anything other than blank the screen.

    My answer involved ditching watch and just combining the scripts, running a bash loop in the background which updates the banner every 60 seconds. Unfortunately the writes to STDOUT are not atomic for either this loop or for the todo list, so I had to simply update variables containing the banner and the list, and update the entire screen every time the list file changes or it's 60 seconds and it's time for a new banner.

    This isn't ideal, because I'm redrawing a bunch of stuff that I don't have to redraw, but it's the only way to get around the fact that I can't find a way to make the writes to STDOUT atomic.

    #!/bin/bash
    clear
    # choose a random banner and initialize variables
    filename=`ls -d ~/banners/*.ans | shuf -n 1`
    banner=$(head -c -1 $filename)
    
    function todo {
            # move to the top left of the screen but don't clear
            tput cup 0 0
            # display the banner
            echo "${banner}"
            # make sure we're on line 16
            tput cup 16 0
            # update the todo list, so that it will clear each line to the end
            todolist=$(/usr/local/bin/todo.sh today | fold -w 80 -s | sed -r 's/$/\\033\[K/g')
            # display the todo list
            echo -e "${todolist}"
            # clear the rest of the screen
            tput ed
    }
    
    # CTRL-C exits cleanly, killing the banner process that was running in the background
    trap 'trap - SIGTERM && tput cnorm && clear && kill 0' SIGINT SIGTERM EXIT
    
    while true; do
            # choose a new banner
            filename=`ls -d ~/banners/*.ans | shuf -n 1`
            banner=$(head -c -1 $filename)
            # redraw the screen
            todo
            # do this every 60 seconds
            sleep 60
    done &
    
    # make the cursor invisible for now
    tput civis
    
    fswatch -l 1 -o --event=Updated -e "~/.todo/.*" -i "todo.txt" ~/.todo | while read;
            do
                    # redraw the screen
                    todo
            done
    
    # make the cursor visible again
    tput cnorm
    exit