1. Introduction

Any shell behaves as per its inner workings and what the user instructs it. This fact sometimes leads to unexpected results, especially when users are unaware of the special meaning or mechanics behind their instructions. Usually, the most negligible problems affect only the output and formatting of the shell or terminal.

In this tutorial, we discuss the shell prompt and its position before and after issuing a given command. First, we talk about the prompt in general and its usual position. Next, we explore anomalies in the prompt location and how they can come about. Finally, we look at ways to standardize the prompt position.

We tested the code in this tutorial on Debian 11 (Bullseye) with GNU Bash 5.1.4. It should work in most POSIX-compliant environments unless otherwise specified.

2. The Prompt

The prompt is a visual marker for the user to indicate the shell expects input:

$ █

While a blinking caret or cursor symbol also usually indicates input is expected, having a prompt symbol can tell us more:

  • $ dollar sign might mean the shell has normal user rights
  • # octothrop might mean the shell has root privileges
  • /path/to/dir $ can let us know the current working directory

For example, the default prompt in Debian 11 is a combination of data:

baeldung@xost:/home/baeldung $

In this case, we see four columns with @ asperand, : colon, and space as their respective separators:

  1. baeldung is the username
  2. xost is the hostname
  3. /home/baeldung is the current working directory
  4. $ is the prompt symbol, indicating a normal user session

In our case, we simplify the prompt to just the $ dollar sign as a minimalistic indicator. Now, let’s see how and when prompts usually appear.

3. Prompt Output and Location Anomalies

Let’s check the result of a simple command:

$ ls
file1  file3  file5  file7  file9
file2  file4  file6  file8
$ █

In this example, there are four (4) lines:

  1. $ ls: prompt and the ls command
  2. file1 file3 […]: file listing
  3. file2 file4 […]: file listing
  4. $ █: prompt and caret

Now, let’s check another command run:

$ cat file1
File line 1.
File line 2.
$ █

Again, we see the command output surrounded by prompts.

While a newline ends every command entry, there might not be a newline at the end of the output:

$ cat file2
File line 1.
File line 2.$ █

In this case, the text file doesn’t end with the standard newline, so the prompt is directly adjacent to the EOF (End-Of-File).

So, depending on the prompt, a bad prompt location might make certain actions difficult:

  • extracting the exact contents of a given file
  • copying the file contents from the graphical user interface (GUI) or terminal user interface (TUI)

Let’s understand how to avoid such situations.

4. Standardizing Prompt Positioning

There are different ways to correct a misplaced prompt after running a command.

In all cases, we use file as the example:

$ cat file
File line 1.
File line 2. $ █

The sample has two lines and no newline at the end.

The automatic solutions below ensure we go to the next line by skipping over the necessary number of terminal columns.

4.1. Manual Correction

Naturally, we can simply append another command that adds a newline:

$ cat file; echo
File line 1.
File line 2.
$ █

In this case, we use the ; semicolon separator to include an echo command on the same line as the cat.

There are three main drawbacks to this approach:

  • we augment our command
  • the newline is always appended
  • the exit status code is masked

We can partially work around the last two by replacing the ; semicolon with a && double ampersand:

$ cat file && echo
File line 1.
File line 2.
$ █

Now, any errors in the first command would prevent echo from running, and we’d be able to know the exit code was not zero (success).

4.2. Basic Prompt Positioning

Indeed, we can customize the prompt to reposition itself.

To do so, we issue two commands only once:

$ shopt -s promptvars
$ PS1='$(printf "%$((COLUMNS-1))s\r")'$PS1

To begin with, we use the Bash shopt builtin to [-s]et the default promptvars option, forcing several features to work on the prompt:

After that, we prepend a special printf statement that inserts just enough whitespace according to the COLUMNS variable before appending a \r carriage return to return the caret to the beginning of the (next) line.

Even with this, the COLUMNS variable might become misleading if it doesn’t get updated properly, e.g., in an environment like a terminal user interface (TUI) such as tmux or screen.

4.3. Automatic Prompt Positioning

Like other actions, caret and cursor locations can be modified via ANSI escape codes. Thus, we can make our earlier prompt changes more comprehensive but potentially more stable:

###
# configuration function to check and modify PS1
###
__prompt_conf() {
  PS1=""

  if [ "$(__terminal_columns)" != 0 ]; then
    PS1="\n"
  fi

  PS1+="$ORIGINAL_PS1"
}

###
# get current terminal column value
###
__terminal_columns() {
  exec < /dev/tty local oldstty=$(stty -g) stty raw -echo min 0 echo -en "\033[6n" > /dev/tty
  local pos
  IFS=';' read -r -d R -a pos
  stty $oldstty
  echo "$((${pos[1]} - 1))"
}

# preserve original $PS1
ORIGINAL_PS1="$PS1"

# prepend configuration function to $PROMPT_COMMAND
PROMPT_COMMAND="__prompt_conf;$PROMPT_COMMAND"

The __terminal_columns() function uses the exec command to directly work with /dev/tty. Next, it changes the terminal mode with stty, preserving the old one.

After we read the vertical caret [$pos]ition returned by the \033[6n escape code. Alternatively, we can use tput for this line:

$ IFS=';' read -d R -p "$(tput u7)" -r -s row column

Finally, we restore the mode and return the column count based on $pos.

The __prompt_conf() function, as prepended to the PROMPT_COMMAND, checks the column count via __terminal_columns() and decides whether to add a newline or not.

So, before the prompt is printed, we evaluate the context and add a newline, if needed.

5. Summary

In this article, we looked at reasons for a distorted prompt position and how to work around them.

In conclusion, depending on the situation, we can manually add a command to insert a newline or automatically correct the location based on the current column.

Comments are open for 30 days after publishing a post. For any issues past this date, use the Contact form on the site.