17

My script doing something like:

while :;
   clear

   do_a_lot_of_output_here

   sleep 1
done

Is it any options to prevent screen of blinking when I'm doing clear and output? I want to do it like in watch command (but it's written in C). Any advices?

clear | hexdump -C

00000000  1b 5b 48 1b 5b 32 4a                              |.[H.[2J|
00000007

PS. I'm using bash only.

6

5 Answers 5

16

One way to help prevent flashing is to get all the output before clearing the screen so that there is a minimum amount of time between clearing and redrawing it. This is similar to the concept of double buffering:

while :; do
   output=$(do_a_lot_of_output_here)
   clear
   echo "$output"
   sleep 1
done

This does not completely eliminate the flicker, but it happens significantly less frequently in my experience.

7
  • Double buffering, just like in any other situation, safes the day :-)
    – Ikke
    Sep 29, 2015 at 19:49
  • Worked like a charm. No flickering at all :) Feb 24, 2017 at 10:00
  • 2
    The remaining flickering can be eliminated by including the clear into the buffer, i.e. output=$(clear; do_a_lot_of_output_here).
    – kdb
    Jul 20, 2017 at 12:15
  • @kdb Nice suggestion! This almost completely eliminates flickering for me and is much simpler than the accepted answer. Jun 5, 2018 at 15:00
  • This works like a charm! At first I missed the double quotes in the 'echo "$output"', and then the lines get all muddled up. (All whitespaces are considered single spaces.)
    – Popup
    Feb 7, 2019 at 7:37
10

The flashing occurs because the script clears the entire screen. If it paints over the existing text and clears only as necessary, then there will be no flickering.

Here is an example:

#!/bin/sh
watchit() {
    HOME=$(tput cup 0 0)
    ED=$(tput ed)
    EL=$(tput el)
    ROWS=$(tput lines)
    COLS=$(tput cols)
    printf '%s%s' "$HOME" "$ED"
    while true
    do
        CMD="$@"
        ${SHELL:=sh} -c "$CMD" | head -n $ROWS | while IFS= read LINE; do
            printf '%-*.*s%s\n' $COLS $COLS "$LINE" "$EL"
        done
        printf '%s%s' "$ED" "$HOME"
        sleep 1
    done
}

watchit top -b -n 1

It does this:

  • prints the output from the given command that will fit on the screen (no wrapping or scrolling)
  • writes over existing lines, clearing the portion of each line which is not overwritten
  • uses the ed capability of your terminal to print from the current location to the end of the screen.

If you wanted to handle a resizable screen, you could move the assignments to ROWS and COLS inside the outer loop, e.g.,

#!/bin/sh
watchit() {
    HOME=$(tput cup 0 0)
    ED=$(tput ed)
    EL=$(tput el)
    printf '%s%s' "$HOME" "$ED"
    while true
    do
        ROWS=$(tput lines)
        COLS=$(tput cols)
        CMD="$@"
        ${SHELL:=sh} -c "$CMD" | head -n $ROWS | while IFS= read LINE; do
            printf '%-*.*s%s\n' $COLS $COLS "$LINE" "$EL"
        done
        printf '%s%s' "$ED" "$HOME"
        sleep 1
    done
}

watchit top -b -n 1

because tput asks for the current screensize from the system.

Further reading:

3
  • 1
    For those who use #!/bin/bash and wish to use watchit embedded in a small stand-alone script with some functions, you can do export -f function_name; watchit function_name. Jun 21, 2016 at 5:11
  • This worked perfectly for me except that I ran into funny issues at the top of the screen, where things would shift and get glitchy for longer chunks of text (anything getting shortened by head). Seems the issue is an off-by-one error (at least in my setup -- ssh session in split panes, iTerm2) when computing the number of rows. ROWS=`expr $(tput lines) - 1` addressed this perfectly.
    – mattsilver
    Sep 9, 2016 at 23:54
  • This is wonderful, thank you very much @thomas-dickey.
    – mbarkhau
    Sep 3, 2018 at 9:12
8

The flashing is an unavoidable result of clearing the screen each time around the loop. You can move the cursor to the top of the screen and overwrite parts of your old output instead.

# You may want to do this if your code is in a script.
unhide_cursor() {
    printf '\e[?25h'
}
trap unhide_cursor EXIT

# Hide the cursor (there is probably a much better way to do this)
printf '\e[?25l'
clear 
while true ; do
    # Move the cursor to the top of the screen but don't clear the screen
    printf '\033[;H' 
    do_a_lot_of_output_here
    sleep 1
done

This script will leave artifacts if your output shrinks. It's also not very likely to be portable. I've only tested it with urxvt, xterm, and st.

6
  • In this case cursor starts jumping to the end of output from random position (and artifacts of course). Also I tried tput clear with the same results (blinking)
    – ravnur
    Jun 29, 2013 at 21:31
  • I added a couple of lines to the script to hide the cursor.
    – user26112
    Jun 29, 2013 at 21:40
  • No cursor - no jumping. But about artifacts? Output has a random amount of rows. Do I need to fill all script with spaces before starting new output? But, anyway, thanks for your time and efforts: +1 from me for that but it doesn't solve my issue
    – ravnur
    Jun 29, 2013 at 22:08
  • I found only one way to get rid of all artifacts: tput ed. But it cause blinking
    – ravnur
    Jun 29, 2013 at 22:33
  • What terminal emulator are you using? With tput ed right before the do_a_lot... line, I'm seeing blinking in urxvt but not xterm or st.
    – user26112
    Jun 30, 2013 at 0:18
1

As an extension of Cristian's answer, I made the following bash function which works if the console window is smaller than the output of the command:

function watcher()
{
    lines=$(tput lines)
    while true; do
        output="$($@ | head -n $lines)"
        clear
        echo -e "$output"
        sleep 2
    done
}

This allows you to pass any command to the watcher. If you are using git, use git config --global color.status always and then:

watcher git status

Will display an always-updating git status output.

0

Depends on if you have one line or multiple lines. tput isn't an bad option but I like using esc codes...

while true
do
    printf "^[[H^[[0K$do_a_lot_of_output"
    sleep 0.01
done

^[[H moves the cursor to 0,0 or the top left corner and ^[[0K erases from the current cursor position to the end of the line.

^[[0K is nice because it will clear the line from the current cursor position to the end of line and wouldn't see any residual data if the next string happens to be shorter than the previous string

one example is if $do_a_lot_of_output = 20 during the first iteration and then $do_a_lot_of_output = 5 on the second the output would be 50. If you didn't include ^[[0K since it's only going to output as far as it has to, the 2 in 20 gets overwritten with the new 5 but the 0 remains giving you a misleading 50.

I think ^[[K can be used as well

Start an esc code sequence by pressing ctrl+v then ESC to produce ^[ then add another [ + the code in this case H or K

you put it all together to get ^[[H or ^[[K

list of Ansi escape codes: https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797

2
  • tput is the portable way of outputting escape codes. It means you don’t need to know the specific codes for whatever output device is being used. Aug 22, 2022 at 20:52
  • I agree tput is nice, I just prefer using esc codes for maybe formatting a table or something like printf "^[[H$varible_at_home^[[20G^[[K$something_20_columns_away"
    – ace
    Aug 23, 2022 at 1:17

You must log in to answer this question.

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