Promt customization
5 minutes read in LinuxI’ve been customizing my own prompt for a long time, and the main customizations were: adding the current time and color to the production server (Yes, I had and still have pet servers, where can I go without them). After that, I added the simplest scripts to display the current git branch and the repository status (whether there are changes or not). And here is in another times having looked in its .bashrc I decided to update the appearance and simplify the script that deals with filling PS1, as a result, I got new knowledge about what PS1-4 is, how to use PROMPT_COMMAND and most importantly - the types of $TERM and the use of colors in them. Below is an example of my PS1 and useful links.
So PS1, PS1 is responsible for prompt output in the console, for example:
user@host:~$ PS1="test> "
test>
Using Escape Sequences you can add useful information to PS1:
user@host:~$ PS1="test> "
test> PS1="\l \A \u@\h \w"
0 11:23 user@host ~
Where:
\l - the number of the terminal
\A - time
\u - user
\h - host
\w - directory
Full list of bash escape sequences.
But, what if you want to add something other than this limited set? You can use variables!
user@host:~$ PS1="$(pwd) "
/home/user cd nginx
/home/user PS1="$(pwd) "
/home/user/nginx
But, as you can see, there is a problem with updating variables inside PS1, going to a different directory, the value in PS1 is not updated. Just to solve this problem, there is a PROMPT_COMMAND
variable, its contents are executed every time before bash displays PS1:
user@host:~$ function __prompt() { PS1="$(pwd) $ "; };
user@host:~$ PROMPT_COMMAND=__prompt
/home/user $ cd nginx
/home/user/nginx $
Inside the `__prompt ’ function, you can add your own logic and customize the PS1 in a huge number of ways. One way is to change the color.
You can use ANSI Control Dequences to work with color, for example:
red="\e[31m"
reset="\e[0m"
echo -e "${red}Hello${reset} World"
Or tput:
for C in {0..255}; do
# set foreground
tput setaf $C;
echo -n "$C ";
done
# reset
tput sgr 0
for C in {0..255}; do
# set background
tput setab $C;
echo -n "$C ";
done
# reset
tput sgr 0
Using these basic blocks, you can make any complex PS1, below I will give the code of my PS1:
# ----------------------------------------------------------------
# CUSTOM PROMPT
# https://github.com/jimeh/git-aware-prompt
function find_git_info() {
# Based on: http://stackoverflow.com/a/13003854/170413
local status
status=$(git status --porcelain 2>/dev/null)
local branch
if branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null); then
if [[ "${branch}" == "HEAD" ]]; then
branch="detached*"
fi
if [[ "$status" != "" ]]; then
git_info=" (${branch})* "
else
git_info=" (${branch}) "
fi
else
git_info=""
fi
}
# set variable identifying the chroot you work in (used in the prompt below)
if [ -z "${debian_chroot:-}" ] && [ -r /etc/debian_chroot ]; then
debian_chroot=$(cat /etc/debian_chroot)
fi
PS2="~> "
PROMPT_COMMAND=__prompt_command
function __prompt_command() {
local EXIT="$?"
local color_prompt
# set a fancy prompt (non-color, unless we know we "want" color)
case "$TERM" in
xterm-color | *-256color) color_prompt=yes ;;
esac
# https://askubuntu.com/questions/372849/what-does-debian-chrootdebian-chroot-do-in-my-terminal-prompt
local visual_debian_chroot="${debian_chroot:+" (${debian_chroot}) "}"
# If this is an xterm set the title to user@host:dir
case "$TERM" in
xterm* | rxvt*)
[ -n "$__vte_prompt_command" ] && __vte_prompt_command
local terminalTitle="[\l] ${visual_debian_chroot}\u@\h: \w"
local setTerminalTitle="\[\e]0;${terminalTitle}\a\]"
PS1="$setTerminalTitle"
;;
*) ;;
esac
# save and update history in every window instantly
history -a
history -n
find_git_info
if [ "$color_prompt" = yes ]; then
# https://misc.flogisoft.com/bash/tip_colors_and_formatting
local defaultFg blackFg redFg greenFg yellowFg blueFg magentaFg cyanFg lightGrayFg
defaultFg="\[$(tput setaf 0)\]"
blackFg="\[$(tput setaf 16)\]"
redFg="\[$(tput setaf 1)\]"
greenFg="\[$(tput setaf 2)\]"
yellowFg="\[$(tput setaf 3)\]"
blueFg="\[$(tput setaf 4)\]"
magentaFg="\[$(tput setaf 5)\]"
cyanFg="\[$(tput setaf 6)\]"
lightGrayFg="\[$(tput setaf 7)\]"
local defaultBg blackBg redBg greenBg yellowBg blueBg magentaBg cyanBg lightGrayBg
defaultBg="\[$(tput setab 0)\]"
blackBg="\[$(tput setab 16)\]"
redBg="\[$(tput setab 1)\]"
greenBg="\[$(tput setab 2)\]"
yellowBg="\[$(tput setab 3)\]"
blueBg="\[$(tput setab 4)\]"
magentaBg="\[$(tput setab 5)\]"
cyanBg="\[$(tput setab 6)\]"
lightGrayBg="\[$(tput setab 7)\]"
local reset
reset="\[$(tput sgr 0)\]"
function addText() {
local fg="${1}"
local bg="${2}"
local text="${3}"
PS1+="$fg$bg$text$reset"
}
function addSeparator() {
local separator=$'\uE0B0'
local bg="${1}"
local fg="${2}"
PS1+="$bg$fg$separator$reset"
}
local prevFg=""
addText "" "${blackBg}" " \l "
prevFg="${blackFg}"
# debian_chroot="test"
if [ -n "$debian_chroot" ]; then
addSeparator "${magentaBg}" "${blackFg}"
addText "" "${magentaBg}" "${visual_debian_chroot}"
prevFg="${magentaFg}"
else
true
fi
if [ -n "$prevFg" ]; then
addSeparator "${yellowBg}" "${prevFg}"
else
true
fi
addText "" "${yellowBg}" " \A "
addSeparator "${greenBg}" "${yellowFg}"
addText "" "${greenBg}" " \u@\h "
addSeparator "${blueBg}" "${greenFg}"
addText "" "${blueBg}" " \w "
if [ -n "$git_info" ]; then
addSeparator "${cyanBg}" "${blueFg}"
addText "" "${cyanBg}" "${git_info}"
prevFg="${cyanFg}"
else
prevFg="${blueFg}"
fi
if [ $EXIT != 0 ]; then
addSeparator "${redBg}" "${prevFg}"
addText "" "${redBg}" " \$ "
addSeparator "${reset}" "${redFg}"
else
addSeparator "${lightGrayBg}" "${prevFg}"
addText "${blackFg}" "${lightGrayBg}" " \$ "
addSeparator "${reset}" "${lightGrayFg}"
fi
addText "${reset}" "${reset}" " "
else
PS1="${visual_debian_chroot}\A \u@\h:\w${git_info}"
if [ $EXIT != 0 ]; then
PS1+="\$?↵ \$ "
else
PS1+="\$ "
fi
fi
}
Some not obvious things that was used in my PS1
When I was developing my PS1 I noticed that PROMPT_COMMAND by default has the value __the_prompt_command
, what it is? Let’s see:
$ type __vte_prompt_command
__vte_prompt_command is a function
__vte_prompt_command ()
{
local command=$(HISTTIMEFORMAT= history 1 | sed 's/^ *[0-9]\+ *//');
command="${command//;/ }";
local pwd='~';
[ "$PWD" != "$HOME" ] && pwd=${PWD/#$HOME\//\~\/};
printf '\033]777;notify;Command completed;%s\033\\\033]0;%s@%s:%s\033\\%s' "${command}" "${USER}" "${HOSTNAME%%.*}" "${pwd}" "$(__vte_osc7)"
}
It turned out that this is a function that will send notify
if PROMPT_COMMAND is started, but the terminal is not in focus, for example, when you run a long command and do something else in another window. The source code located in the file /etc/profile.d/vte.sh
"\[\e]0;${terminalTitle}\a\]"
- this escape sequence sets the title of the terminal window. for convenience, I have separated the escape sequence, and the title itself.
Conclusion
Yes, you can always install oh-my-zsh/bash, roll a beautiful theme on top and get the same thing, but sometimes it’s nice to figure out the topic yourself and do something on your own.