Nathan Vance
Nathan Vance

Reputation: 605

In bash print to line above terminal output

EDIT: Corrected process/thread terminology

My shell script has a foreground process that reads user input and a background process that prints messages. I would like to print these messages on the line above the input prompt rather than interrupting the input. Here's a canned example:

sleep 5 && echo -e "\nINFO: Helpful Status Update!" &
echo -n "> "
read input

When I execute it and type "input" a bunch of times, I get something like this:

> input input input inp
INFO: Helpful Status Update!
ut input

But I would like to see something like this:

INFO: Helpful Status Update!
> input input input input input

The solution need not be portable (I'm using bash on linux), though I would like to avoid ncurses if possible.

EDIT: According to @Nick, previous lines are inaccessible for historical reasons. However, my situation only requires modifying the current line. Here's a proof of concept:

# Make named pipe
mkfifo pipe
# Spawn background process
while true; do
        sleep 2
        echo -en "\033[1K\rINFO: Helpful Status Update!\n> `cat pipe`"
done &
# Start foreground user input
echo -n "> "
pid=-1
collected=""
IFS=""
while true; do
        read -n 1 c
        collected="$collected$c"
        # Named pipes block writes, so must do background process
        echo -n "$collected" >> pipe &
        # Kill last loop's (potentially) still-blocking pipe write
        if kill -0 $pid &> /dev/null; then
                kill $pid &> /dev/null
        fi
        pid=$!
done

This produces mostly the correct behavior, but lacks CLI niceties like backspace and arrow navigation. These could be hacked in, but I'm still having trouble believing that a standard approach hasn't already been developed.

Upvotes: 2

Views: 1718

Answers (1)

Nick
Nick

Reputation: 5180

The original ANSI codes still work in bash terminal on Linux (and MacOS), so you can use \033[F where \033 is the ESCape character. You can generate this in bash terminal by control-V followed by the ESCape character. You should see ^[ appear. Then type [F. If you test the following script:

echo "original line 1"
echo "^[[Fupdated line 1"
echo "line 2"
echo "line 3"

You should see output:

updated line 1
line 2
line 3

EDIT:

I forgot to add that using this in your script will cause the cursor to return to the beginning of the line, so further input will overwrite what you have typed already. You could use control-R on the keyboard to cause bash to re-type the current line and return the cursor to the end of the line.

Upvotes: 2

Related Questions