commit 70fc67766aaa2f58f288e3d2d8d932d21fd36eb0
parent 15d2da7d889c929fc3126aac6dfb7cb9bfe2a305
Author: Nick Moffitt <nick@zork.net>
Date: Sun, 17 May 2026 18:09:16 +0100
Expanded documentation
Diffstat:
| M | README.md | | | 99 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
1 file changed, 99 insertions(+), 0 deletions(-)
diff --git a/README.md b/README.md
@@ -37,3 +37,102 @@
Colours have been chosen to highlight urgent information, such as non-zero
exit codes blinking the $ or # as appropriate.
+# Implementation
+
+The `bash(1)` manual page describes the behaviour of `$PS1` as follows:
+
+> After the string is decoded, it is expanded via parameter expansion, command substitution, arithmetic expansion, and quote removal
+
+This means that we theoretically *could* call external programs to glean information between commands at the shell prompt. We could also use `$PROMPT_COMMAND` to set up the environment each time. Some systems (such as [PowerLine](https://powerline.readthedocs.io/en/master/usage/shell-prompts.html) or [Starship](https://starship.rs/)) do this, and achieve stunning effects at the cost of some fairly heavyweight computation.
+
+The inspiration for this prompt came from the way Debian displays the value of the `$debian_chroot` varible iff it is defined:
+
+```
+${debian_chroot:+($debian_chroot)}
+```
+
+This is what the `bash(1)` man page meant by "parameter expansion" above: `${foo:+bar}` means "If `$foo` is defined, ignore its value and use `"bar"` instead." This lets Debian show the variable contents wrapped in parentheses, but doesn't get stuck showing empty parens if you're not in a compatible chroot. Incidentally, this variable is set via `/etc/debian_chroot`, and it can be handy to make that show system roles on non-chroot environments.
+
+## Exit Codes
+
+The `$?` variable shows the exit code of the last command that ran. We use this value to do `if`/`elif`/`else`/`fi` blocks, and to conditionally chain commands with `||` or `&&`. We can do a trick with parameter expansion to only display it if it is non-zero, like so:
+
+```
+echo ${?#0}
+```
+
+This means "chop leading zeroes off of `$?` before displaying it." Since the only time there's a `0` at the front is when it's just `0`, this works quickly and efficiently. Unfortunately, it's not as pretty as the `$debian_chroot` example above. For example, if we run `PS1='${?#0}\$ `, we get something like the following:
+
+```
+$ true
+$ false
+1$
+```
+
+This is already useful! But since a value of `0` is still *defined*, `${?:+($?)}` won't work for us. It will always show a 0, which is distracting noise that could hide important program failure codes.
+
+### Array Indices
+
+Bash supports arrays, which can let us use numbers as indices into them. It also supports integer arithmetic such as `echo $(( 22 / 7 ))`. We can combine these to come up with a test that lets us output text iff something is non-zero:
+
+```
+_noop[0]='array accesses turn 0/nonzero into defined/undef!'
+PS1='${_noop[$(($?==0))]:+($?)}\$ '
+```
+
+If `$?` is `0`, the expression `$(($?==0))` will be `1`, which isn't defined. Anything else will match `${noop[0]}` and return `($?)`. Now our shell looks like this:
+
+```
+$ true
+$ false
+(1)$
+```
+
+## `$XDG_SESSION_TYPE`
+
+The FreeDesktop folks and systemd give us some environment variables that tell us whether we're on a full local desktop environment or on the other end of a terminal connection of some sort. The one I use is `$XDG_SESSION_TYPE`.
+
+This variable is set to `"tty"` when you ssh, sudo, doas, or otherwise connect to a new shell environment via terminal-based tools. It's often set to something like `"wayland"` when it's a local desktop.
+
+This is new enough that we can't rely on it, and not everyone is eager to follow these standards. But we can use it along with other information:
+
+```
+unset _tty
+_tty=$SSH_CONNECTION$SUDO_USER$DOAS_USER
+[ "$XDG_SESSION_TYPE" = "tty" ] && _tty="yes"
+```
+
+We make sure `$_tty` is defined iff sudo, doas, or ssh have set an environment variable pointing back at some original shell session, or if `$XDG_SESSION_TYPE` is `"tty"`.
+
+## hostnames and usernames
+
+We also use this information to decide whether to display the username or hostname. If you're on your local system, you already know your username and hostname. So we do something like the following:
+
+```
+PS1='${_tty:+\u${SSH_CONNECTION:+\h:}\w\$ '
+```
+
+This would result in the following if you opened up a terminal on your desktop:
+
+```
+~$
+```
+
+But if someone ssh'd into your desktop with the same `$PS1`, they'd see this:
+
+```
+someone@yourdesktop:~$
+```
+
+## Colo[u]r
+
+Detecting colour is likewise hard to do portably, so we use some hints. First, if the `$TERM` variable has the word `"color"` in it, we'll consider that good enough. A lot of terminals these days use strings like `tmux-256color-bce` to indicate features, so this catches quite a few. We also match some other patterns (to catch `foot`, `kitty`, `alacritty`, and `ghostty`) and call it done.
+
+But this doesn't get everyone. The `ncurses` system (which was more popular back when terminals used loads of different formats for colour codes) includes a program called `tput` which can test colour support quite simply. This is our one forked off process, run once at the time the prompt is loaded:
+
+```
+case "$TERM" in
+ *[is]tty*|foot*|*color*) color_prompt=yes;;
+ *) [ -x /usr/bin/tput ] && tput setaf 1 >&/dev/null && color_prompt=yes;;
+esac
+```