Issue
A popular method for getting progress bars on the command line is to use the carriage return character, which overwrites the previous line, as seen in this script:
#!/bin/bash
echo -ne '[# ] 25%\r'
sleep 1
echo -ne '[## ] 50%\r'
sleep 1
echo -ne '[### ] 75%\r'
sleep 1
echo -ne '[####] 100%\r'
This is OK... if you only want print a progress bar. But a more sophisticated script may want to print a progress bar WHILE also outputting diagnostic output. You'd like the buffer of your command line application to evolve as follows (separated with ----
):
[# ] 25%
----
msg1
[# ] 25%
----
msg1
msg2
[# ] 25%
----
msg1
msg2
[## ] 50%
etc. And now, the popular approach fails:
#!/bin/bash
echo -ne '[# ] 25%\r'
echo "msg 1"
sleep 1
echo "msg 2"
echo -ne '[## ] 50%\r'
echo "msg 3"
sleep 1
echo "msg 4"
echo -ne '[### ] 75%\r'
sleep 1
echo -ne '[####] 100%\r'
outputs
ezyang@sabre:~$ ./test.sh
msg 1] 25%
msg 2
msg 3] 50%
msg 4
It would seem that to implement this, any other time you want to print a message, you need to:
- Check if it's a progress bar,
- If it is, clear the line, print your output plus a newline, and then reprint the progress bar
(Alternately, you could use ncurses). Unfortunately, this requires every possible thing which could give output to play ball.
Is there any modular method for making this kind of progress bar work, which doesn't require instrumenting all other methods of outputting to the terminal?
Solution
Update: You can do this with ANSI/VT100 escape codes/sequences.
Before I add a working example, you need to be aware of the following:
You need to make sure you're outputting to a TTY/console.
You need to be aware of the difference between IO (stdout/stderr) lines, and TTY lines.
\r
will get you to the beginning of an IO line, even if the line spans more than one TTY line.Those codes/sequences are not supported natively in the Windows console. But you can use them in ConEmu (recommended), or with ansicon.
Here is a working example. The code should be self-explanatory:
#!/bin/bash
ERASE_SCREEN_AFTER="\033[0J"
ERASE_LINE_BEFORE="\033[1K"
ERASE_LINE_AFTER="\033[0K"
UP="\033[1A"
up_count=2
messages="\n"
progress=""
echo_repeat() {
c="$2"
while (( c )); do
echo -en "$1"
(( c-- ))
done
}
update_status() {
echo_repeat "$UP" "$up_count"
echo -en "$ERASE_LINE_BEFORE" "$ERASE_SCREEN_AFTER" "\r"
echo -en "$messages"
echo "$progress"
}
add_msg() {
messages+="$@\n"
update_status
(( up_count++ ))
}
set_progress() {
progress="$@"
update_status
}
echo_repeat "\n" $up_count
set_progress '[# ] 25%'
add_msg "msg 1"
sleep 1
add_msg "msg 2"
set_progress '[## ] 50%'
add_msg "msg 3"
sleep 1
add_msg "msg 4"
set_progress '[### ] 75%'
sleep 1
set_progress '[####] 100%'
You can do much more with those codes/sequences alone, without the use of (n)curses. That includes setting foreground/background colors, using bold fonts, among other functionalities.
saldl is an example of a CLI program where those codes/sequences are extensively used.
Old Answer (obsolete): If I understand your question correctly, all you need to do is add an \n
before the msg
line.
Answered By - Not Important Answer Checked By - Marie Seifert (WPSolving Admin)