How to Customize Your Bash or Zsh Prompt on Fedora

Customize your Bash or Zsh prompt on Fedora by editing the PS1 variable in your shell configuration file.

The prompt is just a string that runs on every keystroke

You stare at a terminal full of identical user@hostname:~$ lines. You just ran three commands across two directories, and you have no idea where you are. You want your prompt to show the current Git branch, the exit status of the last command, and a color that changes when sudo is active. You found a snippet online, pasted it into ~/.bashrc, and now your terminal is printing literal [32m instead of green text.

The prompt is not a special feature. It is a plain text variable that the shell prints before every command. Bash stores it in PS1. Zsh stores it in PROMPT. When you press Enter, the shell executes your command, captures the exit code, runs any prompt hooks, expands the prompt variable, and prints the result. That cycle happens dozens of times per minute. A prompt that calls external binaries or parses large files will make your terminal feel sluggish. A prompt that skips escape brackets will break line wrapping. You control the cycle by editing the variable in your shell configuration file.

Edit ~/.bashrc for Bash. Edit ~/.zshrc for Zsh. Never edit the system-wide files in /etc/. Those ship with the package and get overwritten on updates. Keep your changes in your home directory. Test new prompts in a subshell before committing them to your config file.

How prompt expansion actually works

The shell reads the prompt string from left to right. It replaces escape sequences with dynamic values. It interprets ANSI color codes. It executes command substitutions if you ask it to. The tricky part is telling the shell which characters take up space on the screen and which ones are invisible control codes.

Color codes are control codes. They change the text color but do not advance the cursor. If you wrap a color code in \[ and \], Bash knows to ignore it when calculating line length. Without those brackets, Bash thinks the color code is printable text. Your terminal will wrap lines at the wrong column, and your cursor will jump to the middle of the next line. Zsh handles this automatically, which is why Bash prompt snippets often break in Zsh and vice versa.

Here is how to check your current prompt variable and see exactly what the shell is rendering.

# Print the raw PS1 variable to see escape sequences
echo "$PS1"
# Show how the shell actually expands it right now
printf "%q\n" "$PS1"
# WHY: %q escapes the string so you see exactly what Bash will process

The output will look like a jumble of backslashes and brackets. That is normal. You only need to understand the sequences you plan to use. The shell evaluates PS1 differently depending on how you define it. Single quotes delay expansion until render time. Double quotes expand variables immediately when the config file loads. Use single quotes for dynamic prompts. Use double quotes only when you want static values baked in at startup.

Building a functional Bash prompt

Bash uses backslash escapes for dynamic values. The most useful ones are \u for the username, \h for the hostname, \w for the full working directory, \W for the basename only, and \d for the date. You add colors with ANSI escape codes. The standard format is \033[CODEm. Code 0 resets styling. Code 1 is bold. Codes 30 through 37 are foreground colors. Codes 40 through 47 are background colors.

Here is a safe, readable Bash prompt that shows the user, hostname, directory, and a colored dollar sign that turns red when the last command failed.

# Define the prompt variable with proper escape brackets for colors
PS1='\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ '
# WHY: \[ and \] tell Bash the color codes are non-printable
# WHY: 01;32 sets bold green for user@host
# WHY: 01;34 sets bold blue for the directory path
# WHY: 00 resets all formatting before the $ sign

Add that line to ~/.bashrc. Reload the configuration with source ~/.bashrc. The prompt updates immediately. If you want the dollar sign to turn red on failure, you need a conditional. Bash does not support inline conditionals in PS1 directly. You use PROMPT_COMMAND instead.

# Run this function before every prompt render
update_prompt() {
    # WHY: $? captures the exit status of the last command
    local exit_code=$?
    if [ $exit_code -ne 0 ]; then
        # WHY: Red color for failed commands
        PS1='\[\033[01;31m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ '
    else
        # WHY: Green color for successful commands
        PS1='\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ '
    fi
}
# WHY: PROMPT_COMMAND runs the function right before Bash prints the prompt
PROMPT_COMMAND=update_prompt

Place that block at the bottom of ~/.bashrc. The shell calls update_prompt on every render. The prompt changes color based on the exit code. Keep the function lightweight. Every millisecond you add multiplies across your daily terminal sessions.

Building a functional Zsh prompt

Zsh uses percent escapes instead of backslashes. The syntax is cleaner and handles non-printable characters automatically. %n is the username. %m is the hostname. %~ is the working directory with tilde expansion. %? is the exit code. %F{color} sets the foreground color. %f resets it.

Here is the Zsh equivalent of the Bash prompt above.

# Set the prompt with Zsh percent escapes
PROMPT='%F{green}%n@%m%f:%F{blue}%~%f\$ '
# WHY: %F{color} automatically handles terminal color codes
# WHY: %f resets the color without manual escape sequences
# WHY: %~ shows ~/projects instead of /home/user/projects

Add it to ~/.zshrc. Zsh evaluates PROMPT on every render. You do not need PROMPT_COMMAND for simple color changes. Zsh supports inline conditionals directly in the prompt string. You can switch colors based on the exit code without a separate function.

# Conditional prompt that changes color on failure
PROMPT='%(?..%F{red})%n@%m%f:%F{blue}%~%f\$ '
# WHY: %(?..%F{red}) prints red only if the last command failed
# WHY: The first part after ? is for success (empty here)
# WHY: The second part after .. is for failure (red color)

Zsh also provides precmd hooks. They run before the prompt prints, just like PROMPT_COMMAND. Use them when you need to calculate complex values like Git branch names or Docker container IDs. Keep the hook fast. The shell waits for it to finish before showing you the cursor.

Verify the prompt behaves correctly

A prompt that looks good in a fresh terminal often breaks under real usage. Test it against the three failure modes: line wrapping, slow rendering, and broken escape sequences.

Open a terminal and run a long command that wraps to the next line.

# Force a long output to test line wrapping
seq 1 50 | xargs echo
# WHY: Generates a single long line that should wrap cleanly

Watch where the cursor lands after the output finishes. If it jumps to the middle of the line, your color codes are missing escape brackets in Bash. If the prompt renders slowly, run time on your prompt function or hook.

# Measure how long your prompt takes to render
time bash -c 'source ~/.bashrc; echo done'
# WHY: Isolates the config file to measure load time

A healthy prompt loads in under 50 milliseconds. Anything over 200 milliseconds will feel laggy. Check for external commands like git branch or hostname -f. Replace them with cached variables or asynchronous hooks if you need them. Run journalctl -xe first if your prompt causes the shell to crash on startup. The error will usually point to a syntax mistake in ~/.bashrc or ~/.zshrc. Fix the syntax before adding more features.

Common pitfalls and what the error looks like

The most frequent mistake is copying a Bash prompt into Zsh or vice versa. Bash expects \u. Zsh expects %n. If you mix them, the shell prints the literal escape sequence instead of the value. You will see \u@\h on your screen instead of user@hostname.

Another common issue is unescaped color codes in Bash. The terminal prints [32m or [0m as plain text. The prompt looks broken and line wrapping fails. Wrap every ANSI code in \[ and \].

Dynamic prompts that call git status or docker ps on every render will freeze your terminal. You will see a noticeable delay between pressing Enter and getting the prompt back. The shell is waiting for the subprocess to finish. Cache the value or use a prompt framework that runs Git checks asynchronously.

Overwriting system defaults is a quiet failure. If you set PS1 before sourcing /etc/bashrc, you lose the system's PROMPT_COMMAND hooks, color definitions, and history settings. Always place your custom prompt at the very bottom of your config file. The system files load first. Your overrides load last.

When to customize manually versus using a prompt framework

Use a manual PS1 or PROMPT string when you want a lightweight prompt that loads instantly and requires zero dependencies. Use PROMPT_COMMAND or precmd hooks when you need conditional logic or environment checks that change per command. Use Starship when you want cross-shell consistency and automatic Git, Docker, and language detection without writing functions. Use Powerlevel10k when you need a highly configurable theme with instant prompt optimization for Zsh. Stay with the default Fedora prompt if you only open the terminal occasionally and do not need Git branches or exit codes visible.

Where to go next