1. Overview

When we write shell scripts, we often print output.

In this tutorial, we’ll explore how to make output overwrite the same line using the echo and printf commands.

2. Introduction to the Problem

As usual, an example can help us to understand the problem quickly. So first, let’s see a simple shell script:

$ cat print_status.sh
#!/bin/bash
echo "[INFO] Processing file: readme.txt"
sleep 2 # To simulate the file processing

echo "[INFO] Processing file: veryPowerfulService.service"
sleep 2

echo "[INFO] Processing file: log.txt"
echo "DONE"

As the print_status.sh script shows, it simply prints some messages to the terminal. We use the sleep command to wait two seconds to simulate the file processing.

If we run the script, we’ll get the output:

$ ./print_status.sh
[INFO] Processing file: readme.txt
[INFO] Processing file: veryPowerfulService.service
[INFO] Processing file: log.txt
DONE

Well, all three processing status messages have been printed. However, each message is printed in a new line. If we need to process a big amount of files, the script might produce a huge output.

It would be good if we could somehow make the echo command always overwrite the same line. This is a common requirement for showing the processing progress, such as showing what percentage of the task has been done, which object is currently in processing, and so on.

In this tutorial, we’ll address how to achieve that step by step.

3. The echo Command’s -n Option

The echo command is pretty straightforward. It prints the message to the console. By default, echo always prints the trailing newline. That’s why our print_status.sh script produces multiple lines of progress information.

If we want the echo commands to overwrite the same line, apparently, the trailing newline character shouldn’t be printed. The -n option asks the echo command to stop outputting the trailing newline character. So, let’s add the -n option to the echo commands in our script:

$ cat print_status.sh
#!/bin/bash
echo -n "[INFO] Processing file: readme.txt"
sleep 2 

echo -n "[INFO] Processing file: veryPowerfulService.service"
sleep 2

echo "[INFO] Processing file: log.txt"
echo "DONE"

As we can see, we don’t add the -n option to the log.txt file’s message, as it’s the last file. Now, let’s run the script and see what it’ll produce:

$ ./print_status.sh
[INFO] Processing file: readme.txt[INFO] Processing file: veryPowerfulService.service[INFO] Processing file: log.txt
DONE

Now, all messages are in the same line. However, we’ve a new problem – when we print a new message, it’s always after the old messages.

Next, let’s see how to solve it.

4. The “Magic Code”: \033[0K\r

To let the new message erase the old ones, we need to add some “magic code” to our echo commands. So let’s first see the solution and then understand how it works:

$ cat print_status.sh
#!/bin/bash
echo -ne "[INFO] Processing file: readme.txt\033[0K\r"
sleep 2 

echo -ne "[INFO] Processing file: veryPowerfulService.service\033[0K\r"
sleep 2

echo -e "[INFO] Processing file: log.txt\033[0K\r"
echo "DONE"

Next, let’s run the script and check if it works as expected:

progress

As we’ve seen, the progress messages overwrite the same line. So, the script works correctly. Next, let’s understand what’s going on.

If we check the script, we’ve made two changes: adding the -e option to echo commands and appending a “\033[0K\r” string to each message.

First, the -e option allows the echo command to interpret backslash escapes such as \n (newline) and \r (carriage return).

Then, we come to the “magic” part: “\033[0K\r“. Now, let’s understand what it means step by step:

  • \033  – It’s the escape sequence. In other words, it’s ESC.
  • *\033[* – Then this becomes “*ESC [*“, which is the control sequence introducer (CSI).
  • \033[0k – So it’s “CSI 0 K“. Further, “CSI 0 K” erases the text from the cursor to the end of the line.
  • \r – This is the carriage return. It brings the cursor to the beginning of the line.

Therefore, the command echo -ne “Message\033[0K\r” performs the following steps:

  • print “Message
  • erase everything after “Message“. In other words, it removes the text from the previous output. Now, the cursor is at the end of the line.
  • finally, “\r” moves the cursor back to the beginning

In this way, each echo command overwrites the previous message on the same line.

5. Using the printf Command

Now that we understand how to solve the problem using echo, using printf to solve the problem isn’t a challenge for us.

The printf command can print output using predefined formats. Further, the printf command interprets backslash escapes and doesn’t print the trailing newline by default.

Therefore, we can also replace echo commands with printf commands in our script:

#!/bin/bash
printf "[INFO] Processing file: readme.txt\033[0K\r"
sleep 2 

printf "[INFO] Processing file: veryPowerfulService.service\033[0K\r"
sleep 2

printf "[INFO] Processing file: log.txt\033[0K\r\n"
echo "DONE"

6. Conclusion

In this article, we’ve learned how to make outputs overwrite the same line using the echo and printf commands.