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.

Back to the top, you overachiever.