Bash prompt cursor resets to the start of the line

Hi, I’ve recently watched Jay’s video on terminal tips and it inspired me to go ahead and try to customize my shell prompt a little.

I managed to get things working the way I like, but then noticed that when typing long commands, or the terminal window is narrow (and sometimes, just because), the cursor suddenly jumps back to the start of the line.

Here’s an example of what I mean. At one point, it jumps back to the beginning and overwriting what I have i there. The command still works correctly though, it’s just a visual bug.

Luckily, I noticed the issue once I started to refactor the PS1 string a bit which is pretty long. So while I have no idea what it could be, I know if must be in how I did the refactoring. Here’s the working prompt:

PS1='${debian_chroot:+($debian_chroot)}\[\033[01;32m\]${i_fa_user} \u \[\033[01;34m\]${i_fa_folder_open}  \W\[\033[38;5;214m\]$(__git_ps1 " $i_dev_git_branch %s")\[\033[38;5;205m\]$(docker_compose_count %s)\[\033[00m\] $i_fa_angle_double_right '

And here’s the output of the entire bash_profile with the updated

source /usr/lib/git-core/git-sh-prompt

# Makes nerd-fonts icons available using convenient aliases
source ~/.local/bin/nerd-fonts/i_all.sh

# Prints the number of containers running on the current directory
function docker_compose_count() {
  container_count=$(docker compose ps 2>/dev/null | grep -v STATUS | wc -l)

  if [[ "${container_count}" -eq 0 ]] 
  then
    printf ""
  else
    printf "\033[38;5;205m"
    printf " $i_linux_docker $container_count"
  fi
}

# Prints the number of jobs associated to the terminal session
function bg_jobs() {
  job_count=$(jobs | wc -l)

  if [[ "${job_count}" -eq 0 ]]
  then
    printf ""
  else
    printf "\033[38;5;157m"
    printf " $i_fa_cog $job_count"
  fi
}

# Prints the current checked out branch on a git-tracked directory
function git_branch() {
  printf "\033[38;5;214m"
  __git_ps1 " $i_dev_git_branch %s"
}

function user_icon() {
  printf "\033[01;32m"
  printf "$i_fa_user"
}

function folder_icon() {
  printf "\033[01;34m"
  printf "$i_fa_folder_open"
}

function double_arrow_icon() {
  printf "\033[00m"
  printf " $i_fa_angle_double_right"
}

PS1='${debian_chroot:+($debian_chroot)}$(user_icon) \u $(folder_icon)  \W$(git_branch)$(docker_compose_count)$(bg_jobs)$(double_arrow_icon) '

Any help is much appreciated :slight_smile:

I remember that bug in bash. I never got around to fixing it, but I changed my shell to oksh. It is a bit minimalistic (which is why I use it in the first place, but not as minimalistic as dash, which is supposed to be used only with scripts, not as an interactive shell, unlike ksh), so maybe using zsh would be a better option for you.

I don’t remember, but it might also depend on how your terminal handles the output too. Try using xterm just as a test. I used to like alacritty, but it only worked on x86, when I moved to arm, it never worked. I used st for a while (because I was using dvtm anyway, so didn’t need the terminal to actually do anything). I now use foot, because it is wayland native and minimalist enough.

I did try an experiment with both st and foot. Both of them have this < when the line is longer than the output, but st shows it way closer, while foot shows it at the end of the terminal. Probably how they are programmed.

Deleting the output with either backspace or escape + dd (vim keybindings if you use -o vi option in your shell) returns the cursor to normal.

If you make the terminals smaller, they will continue displaying text on the next line.

If you do escape + dd, it writes a new line, outputs the normal prompt and the cursor goes to the normal position.

But then, if you delete the output with backspace, something weird happens on both terminals.

I typed ffff until I filled the screen, then typed echo lol. foot moved me to the beginning of the screen where the cursor normally lies and typed on the next line once it got to big. When I hit enter, my username suddenly became the PS1 variable. If I make foot larger, the username becomes normal again.

st on the other hand did not show how it was deleting the output from the 2 lines above it, it only deleted the output from line 3. I kept going with backspace, because I knew there was a lot of text to be removed. Eventually I typed echo lol and it typed on the same line, but the prompt remained normal. Actually, it was the font size, I made st so small that all the output would overflow on the next line and it had the same behavior as foot.

Anyway, it is probably because of the terminal itself. No terminal actually handles this in a decent way.


Because I use dvtm (it’s a terminal multiplexer, with abduco as the session manager, kind of what screen or tmux do by themselves) and because I don’t make my terminals so small (thank you, tilling WMs), I basically never have to deal with this.

There are more tutorials on the internet about tmux and screen, with tmux probably having more. GNU Screen is available on basically all distros. Tmux is more minimal and extensible. By minimal it means it lacks some things, like a Serial connection (which if you need, you just install screen) and other legacy baggage, but has most of the features that screen has.

dvtm is their minimalist smaller cousin that only does terminal multiplexing and can be found in most distros. Abduco is a session manager developed in tandem with dvtm, but can be used without it just for keeping a session of anything on say, remote servers. Abduco is hard to find in most distros, even though it is made by the same individual as dvtm. I feel like dvtm is easier to use, even without tutorials, because the documentation is so well written. I haven’t mastered it, but it does most of what I need.

My default terminal which I really like is Tilix. I just now tried with Gnome-Terminal and installed Kitty, Alacritty and XTerm. The same issue happens with all of them, although with the former two the wrap happens with much longer line inputs (the default font size is also much smaller).

I heard about Tmux, but I thought it was used to split terminal windows? I guess is much more than that. I will try the options you mentioned later as well.

But in any case I was curious whether I did something wrong while writing the functions or something. At the end of the day I can live with leaving the really long PS1 string and be happy about it, as I will not really look at it that often.

Thank you for the help, I learned something from it

1 Like

It must be something related to how printf handles escape characters, and how it’s different from bash. When I pass the color code to printf it works, but some characters get written to stdout. And what I did to avoid this issue is to remove those characters, turning this: \[\033[38;5;213m\] into \033[38;5;213m.

sc322

One thing I just noticed this is causing the problem for some reason. This is interesting because I’m using the __git_ps1 function which comes with the system by default (pop_os) and that also uses printf and yet, I’m able to provide the color code and it outputs everything correctly. I tried reading through the code on that function but it’s a little too complicated for me. But something about how this __git_ps1 function is written makes it bypass this issue.

Anyway I thought I’d also share what the output looks like right now in case someone might also find it useful. What this does is print additional output based on the activity from the current project.

sc3
sc4
sc5
sc6

The orange bit is the current checked out git branch while I’m on a directory tracked by git. Next is the number of running containers started by docker compose on the current directory. Sometimes I forget to tear them down so this gives me nice reminder about running containers. It also helps to detect issues when I’m expecting more containers than what it’s being reported. And lastly the number of background processes assigned to the terminal, which I again use as a reminder.

What I really like about this setup is that when something doesn’t need printing, like when I’m not in a directory using git, there’s not empty spaces produced. I could have some background processes for example, but the git and container count wouldn’t mess the formatting.

I hope you like it :slight_smile: I will continue to explore what’s causing the issue of this thread and update this if I find a workaround.

1 Like

I’ve encountered this bug myself, and it drove me crazy!!! But what’s strange, it kind of just went away. I have no idea why. I thought I was the only one dealing with this.

2 Likes

Finally! I found a solution to this thanks to this website.

The problem is that the character escape sequences \[ and \] only work for the PS1 string. When trying to use the output of a function, as I was doing, they wouldn’t be processed as expected and mess up the resulting prompt. Either with too many visible characters, or too few invisible ones that mess with it’s ability to correctly figure out the number of columns.

When writing the whole PS1 string inline this is not a problem but of course, very hard to read. When producing output destined to be used inside the PS1 string however, you have to use another special but equivalent codes: \001 and \002.

One more thing that I did is start using the tput command to change the behavior of the shell, instead of manually writing the equivalent ANSI escape sequences. Color codes like this: \033[38;5;157m now become tput setaf 157.
Apparently, it’s also much more compatible as it interfaces with the underlying database that determines what capabilities are available in order to make them available to the shell.

I think this summarizes but please checkout the website for a much more detailed explanation about how this works. What an absolute gem, by the way. Lot of information related to unix shell scripting and system administration, with examples and in-depth explanations. I wish there were more sites like this and less click-bait Medium articles with subpar content.

@jay perhaps a new video or an update to address this issue might be a good idea. I know every one has a preferred way of doing things, but using tput makes this so much easier.

Any way this is the resulting file for those interested:

green=$(tput setaf 10)
blue=$(tput setaf 75)
orange=$(tput setaf 214)
teal=$(tput setaf 157)
pink=$(tput setaf 205)

bold=$(tput bold)
reset=$(tput sgr 0)

# Prints the number of containers running on the current directory
function docker_compose_count() {
  local container_count=$(docker compose ps 2>/dev/null | grep -cv STATUS)

  if [[ "${container_count}" -eq 0 ]] 
  then
    printf "" >&2
  else 
    printf "\001%s\002%s %s " "$pink" "$i_linux_docker" "$container_count"
  fi
}

# Prints the number of jobs associated to the terminal session
function bg_jobs() {
  local job_count=$(jobs | wc -l)

  if [[ "${job_count}" -eq 0 ]]
  then
    printf "" >&2
  else
    printf "\001%s\002%s %s " "$teal" "$i_fa_cog" "$job_count"
  fi
}

# Prints the current checked out branch on a git-tracked directory
function git_branch() {
  printf "\001$orange\002"
  __git_ps1 "$i_dev_git_branch %s "
}

function host_icon() {
  printf "\001%s\002%s" "$green" "$i_seti_home"
}

function folder_icon() {
  printf "\001%s\002%s" "$blue" "$i_seti_folder"
}

function double_arrow_icon() {
  printf "\001%s\002%s" "$reset" "$i_fa_angle_double_right"
}

PS1='\[$bold\] $(host_icon) \h $(folder_icon) \W $(git_branch)$(docker_compose_count)$(bg_jobs)$(double_arrow_icon) '
1 Like