PS1 shell
your prompt is boring and it's your fault.
Your bash prompt says user@hostname:~$. It has said that since the day you installed Linux. It has never changed. It tells you three things: who you are, where you are, and that you’re not root. That’s it. That’s your entire relationship with the most frequently displayed piece of text on your screen.
Meanwhile, you’ve seen people with terminals that show the git branch, color-code the working directory, display the exit code of the last command, and somehow look like they’re piloting a spaceship. You assumed they installed something. They probably did — some 50MB prompt framework that takes 400ms to render. But the truth is, you can build a better prompt with one line in your .bashrc.
The variable is called PS1. It controls everything your prompt displays. It’s been sitting there, waiting for you to customize it, since you first typed ls.
Unless you’re running Windows then wtf none of this applies to you. But hey, come to the dark side, go install WSL2 and you can follow along. We’ll wait. Impatiently.
If you’re lazy like me (all sysadmins are!) then click here for the PS1 cheat sheet.
What PS1 actually is
echo $PS1
Run that. Whatever it prints is your current prompt definition. It’ll look like escaped gibberish — something like \u@\h:\w\$. Those backslash codes are what bash expands into your visible prompt.
The codes that matter
| Code | What it shows |
|---|---|
\u |
Username (e.g., owner) |
\h |
Hostname (short, e.g., ubuntu) |
\H |
Full hostname (e.g., ubuntu.local) |
\w |
Working directory (full path, ~ for home) |
\W |
Current directory name only (just projects, not /home/owner/projects) |
\d |
Date (e.g., Sat Mar 15) |
\t |
Time 24-hour (e.g., 14:32:07) |
\T |
Time 12-hour (e.g., 02:32:07) |
\@ |
Time 12-hour with AM/PM |
\n |
Newline — split your prompt across two lines |
\$ |
$ for normal users, # for root |
\! |
History number of the current command |
\# |
Command number in the current session |
Set your prompt
PS1='\u@\h:\w\$ '
That’s the default on most systems. Username, hostname, working directory, dollar sign. Add it to your ~/.bashrc to make it permanent.
Add colors (because it’s not 1987 anymore)
Colors use ANSI escape codes. They look terrible in the config but great in your terminal.
The color codes
# Format: \[\033[COLORm\] ... \[\033[0m\]
# The \[ and \] tell bash "this is non-printing" so line wrapping works correctly
RED='\[\033[0;31m\]'
GREEN='\[\033[0;32m\]'
YELLOW='\[\033[0;33m\]'
BLUE='\[\033[0;34m\]'
PURPLE='\[\033[0;35m\]'
CYAN='\[\033[0;36m\]'
WHITE='\[\033[0;37m\]'
RESET='\[\033[0m\]'
A colored prompt
GREEN='\[\033[0;32m\]'
BLUE='\[\033[0;34m\]'
RESET='\[\033[0m\]'
PS1="${GREEN}\u@\h${RESET}:${BLUE}\w${RESET}\$ "
Username and hostname in green. Working directory in blue. Everything else in the default color. You can now instantly distinguish your username from your path at a glance instead of parsing a wall of same-colored text.
Bold colors
Change 0; to 1; for bold:
BOLD_RED='\[\033[1;31m\]'
BOLD_GREEN='\[\033[1;32m\]'
Bold stands out more. Use it for things you want to notice — like being root.
Useful prompts you can steal
The minimalist
PS1='\W \$ '
Just the current directory name and a dollar sign. projects $. Clean. No noise. For people who know what machine they’re on and don’t need a reminder.
The informative
GREEN='\[\033[0;32m\]'
BLUE='\[\033[0;34m\]'
YELLOW='\[\033[0;33m\]'
RESET='\[\033[0m\]'
PS1="${GREEN}\u@\h${RESET} ${BLUE}\w${RESET} ${YELLOW}\t${RESET}\n\$ "
owner@ubuntu ~/projects/myapp 14:32:07
$
Two lines. Username, hostname, full path, and time on the first line. Prompt on the second line. The newline (\n) means your cursor always starts at the same position regardless of how deep your directory path is.
The “am I root?” prompt
if [ "$EUID" -eq 0 ]; then
PS1='\[\033[1;31m\]\u@\h:\w#\[\033[0m\] '
else
PS1='\[\033[0;32m\]\u@\h:\w\$\[\033[0m\] '
fi
Green for normal user. Bold red for root. You will never accidentally run commands as root without realizing it again. This has prevented more disasters than any monitoring tool.
Add git branch to your prompt
This is the one everyone wants. Show the current git branch in your prompt so you stop committing to main by accident.
The simple version
parse_git_branch() {
git branch 2>/dev/null | grep '\*' | sed 's/* //'
}
GREEN='\[\033[0;32m\]'
BLUE='\[\033[0;34m\]'
YELLOW='\[\033[0;33m\]'
RESET='\[\033[0m\]'
PS1="${GREEN}\u@\h${RESET}:${BLUE}\w${RESET} ${YELLOW}\$(parse_git_branch)${RESET}\$ "
owner@ubuntu:~/projects/myapp feature/login$
The \$(parse_git_branch) calls the function every time the prompt renders. Inside a git repo, it shows the branch. Outside a git repo, it shows nothing. The 2>/dev/null suppresses errors when you’re not in a repo.
With dirty state indicator
parse_git_status() {
local branch
branch=$(git branch 2>/dev/null | grep '\*' | sed 's/* //')
if [ -n "$branch" ]; then
local status
status=$(git status --porcelain 2>/dev/null)
if [ -n "$status" ]; then
echo " ($branch*)"
else
echo " ($branch)"
fi
fi
}
Shows (main) for a clean repo and (main*) for uncommitted changes. Now you know at a glance whether you have work to commit. No running git status to check.
Show the exit code of the last command
When a command fails silently, this tells you.
prompt_exit_code() {
local exit=$?
if [ $exit -ne 0 ]; then
echo "\[\033[1;31m\][$exit]\[\033[0m\] "
fi
}
PS1='\$(prompt_exit_code)\u@\h:\w\$ '
If the last command succeeded, nothing extra shows. If it failed, you see [1] or [127] or whatever the exit code was, in red. No more wondering “did that work?” when a command produces no output.
PS2, PS3, PS4: the other prompts
PS1 isn’t the only prompt variable. There are others, and knowing about them makes you marginally more powerful.
PS2: the continuation prompt
export PS2='→ '
This is what you see when bash expects more input — like when you hit Enter after an unclosed quote or a backslash continuation. Default is >. Change it to something more visible so you know you’re in continuation mode and not staring at a broken terminal.
PS4: the debug prompt
export PS4='+(${BASH_SOURCE}:${LINENO}): '
This shows when you run a script with bash -x script.sh. Each traced line is prefixed with the filename and line number. Default is +. The upgraded version tells you exactly where in the script you are.
The flags that actually matter
This isn’t about flags — it’s about prompt escape codes and techniques.
| Technique | What it does |
|---|---|
\u, \h, \w |
Username, hostname, working directory. |
\[\033[0;32m\] |
Start green color. |
\[\033[0m\] |
Reset to default color. |
\n |
Newline — split prompt across two lines. |
\$(function) |
Call a function every time prompt renders. |
$? |
Exit code of last command (use in a function). |
PS2 |
Continuation prompt (multi-line input). |
“But I use—”
Right.
“Starship gives me a beautiful prompt.” Starship is a 5MB Rust binary that replaces your prompt. It’s fast. It’s pretty. It also adds a dependency to your shell that you need to install on every machine you touch. A PS1 in your .bashrc works on every Linux box on earth with zero installation.
“Oh My Zsh handles my prompt.” Oh My Zsh loads an entire framework to give you a prompt theme. The framework adds 200-400ms of startup time. Your prompt function takes 0ms because it’s three lines of bash. You traded instant shell startup for a prompt that shows the same information but in a fancier font.
“Powerlevel10k is the best prompt.” Powerlevel10k is impressive. It’s also a 12,000-line zsh configuration. To show you a prompt. The cognitive overhead of debugging a 12,000-line prompt engine when something goes wrong is a cost you’re pretending doesn’t exist.
“I don’t care what my prompt looks like.” You look at your prompt hundreds of times a day. A prompt that shows you the exit code, the time, and whether you’re root isn’t vanity — it’s situational awareness. You’re driving without a dashboard.
PS1 cheat sheet
You made it. Or you skipped straight here. Either way, no judgment. Copy and paste these. Pin them. Tattoo them on your forearm. Whatever works.
Starter prompt (colored, two-line, with exit code indicator):
# Add to ~/.bashrc
prompt_command() {
local exit=$?
if [ $exit -eq 0 ]; then
STATUS="${GREEN}✓${RESET}"
else
STATUS="${RED}✗ $exit${RESET}"
fi
}
RED='\[\033[0;31m\]'
GREEN='\[\033[0;32m\]'
BLUE='\[\033[0;34m\]'
CYAN='\[\033[0;36m\]'
RESET='\[\033[0m\]'
PROMPT_COMMAND=prompt_command
PS1='${STATUS} ${GREEN}\u@\h${RESET} ${BLUE}\w${RESET} ${CYAN}\t${RESET}\n\$ '
Shows ✓ owner@ubuntu ~/projects 14:32:07 when the last command succeeded, or ✗ 127 owner@ubuntu ~/projects 14:32:07 in red when it failed. The timestamp tells you when you ran each command — useful when scrolling back through terminal history.
Common escape codes:
| Code | Shows |
|---|---|
\u |
Username |
\h |
Hostname |
\w |
Full working directory |
\W |
Current directory only |
\t |
Time (24-hour) |
\$ |
$ or # (root) |
\n |
Newline |
Color template:
| Color | Code |
|---|---|
| Red | \[\033[0;31m\] |
| Green | \[\033[0;32m\] |
| Yellow | \[\033[0;33m\] |
| Blue | \[\033[0;34m\] |
| Purple | \[\033[0;35m\] |
| Cyan | \[\033[0;36m\] |
| Bold | Change 0; to 1; |
| Reset | \[\033[0m\] |
The one rule: Always wrap color codes in
\[and\]. Without them, bash miscounts the prompt length and line wrapping breaks. You’ll curse for an hour before finding this answer on Stack Overflow.