Rob Cobb

Yak Shave - Terminal Prompt

Oct 13, 2018yak shavebashtoolsdebugging

  1. Overview
  2. TLDR;
  3. Intro
  4. Problem

    a. Background - PS1 and tput

    b. PS1 (not the playstation)

    c. tput

  5. Diagnosing this particular problem
  6. References

Overview

In today’s yak shave, we’ll be bebugging terminal prompt messed-up-edness.

You know how you see people with really cool terminal prompts in screencasts?

Gary Bernhart / Julia Evans / Dan Abramov / etc: 😎

Me: 👀

The problem is that messing with your terminal sometimes breaks things! Per always, a little patience, curiosity, and googling solves it. (but really — any excuse for a yak shave!)

TLDR;

Broken

export PS1="\[\u $(tput bold)$(tput setaf 4)\]\W\[$(tput sgr0)\] \$: "

Fixed

export PS1="\u \[$(tput bold)$(tput setaf 4)\]\W\[$(tput sgr0)\] \$: "

With help from:

Intro

For a long time, I was using liquidprompt to style my prompt. It worked pretty great!

  • the setup was easy
  • the defaults rocked
  • customization was sensible

But, it cost me a teensy bit of startup time when I opened a new terminal tab. Plus, I wanted to understand what was happening with my terminal, and control it myself!

So I decided to learn a little bit and use a prompt of my own design. “Foolish”, you might say. “Why not spend your time doing something useful instead?” Bah!

I took liquidprompt out of my config and added a little sumthin to my `.bashrc`:

export PS1="\[\u $(tput bold)$(tput setaf 4)\]\W\[$(tput sgr0)\] \$: "

Neat-o! Now my terminal looks like:

img

The Problem

I started noticing that sometimes, especially when I scrolled backwards through my bash history, my prompt would get messed up.

img

My cursor wouldn’t show where I expected, the text got bumped all over, everything was pretty messed up. I could clear the screen with ctrl-l or cmd-k, which got me back to a working state. But I couldn’t scroll up and edit previous commands, and forget about searching bash history with ctrl-r. 😭😭😭

Time to strap on the bug-stomping shoes and start googling!

🕵 🐛 👞

Background: PS1 and tput

First, amusing: searches for PS1 console get results for the Sony gaming machine. PS1 variable gets results for the bash variable.

There’s a good guide to PS1 from Vivek Gite at nixCraft

Following the guide, you might echo $PS1. I know I did.

And you might ask yourself: WTF is \\u@\h \\W]\\$?

PS1 (not the playstation):

  • PS1 is a bash variable that sets the prompt
  • Prompts like PS1 have a DSL! (Domain Specific Language)
  • \u, \h, \W - all of these mean something to bash
  • \u is the current user’s username, \h is the hostname up to the first ., \W is the basename of the current working directory
  • (More in the list in the post above)

tput

  • tput is freakin cool for styling terminal output (among other things)
  • It’s a utility that comes with the ncurses package. (if you can use clear, you might have ncurses)
  • It inserts non-printing characters that change color, bold, and back
  • For instance, tput bold prints out \033[1m$

This doesn’t show up in the terminal — it turns text to bold!

Prompt Breakage:

A critical piece of the list of PS1 options mentioned in the cyberciti post above:

\[ : begin a sequence of non-printing characters which could be used to embed a terminal control sequence into the prompt
\] : end a sequence of non-printing characters

This is how terminals get messed up!

Let me expand:

  • Bash is keeping track of how many characters are in your prompt
  • if you have characters that don’t print in your prompt (like tput bold), bash thinks your cursor is further than it should be
  • if you have characters in between \[ these guys \], bash does not count them for determining where your cursor goes

Diagnosing this particular problem

Now that we’ve got a picture for how the PS1 variable works and how tput is styling the prompt, we can debug!

My broken PS1 was:

export PS1="\[\u $(tput bold)$(tput setaf 4)\]\W\[$(tput sgr0)\] \$: "

Look! The \u in my $PS1 is inside of these bad boys \[ \]! That means bash thinks that the output of \u (my username, robertcobb) is non-printing characters.

Brain blast: bash won’t count my username as characters for the prompt 🤯

Now that I understand the bug, the solution is easy:

export PS1="\u \[$(tput bold)$(tput setaf 4)\]\W\[$(tput sgr0)\] \$: "

Moving the \u outside of the non-printing section means that bash will count it towards my prompt characters. If it can get the count right, it won’t show my cursor in the wrong spot or otherwise ‘be all messed up’.

Now I can scroll through my bash history without my prompt getting b*rked 😌.

References



🤓😽 Rob Cobb
🐦 twitter