Bash & the POSIX Shell Module 13 – The interactive shell
Prompt, aliases, dirstack, completion
Customise the Bash prompt with PS1 and PROMPT_COMMAND, choose aliases or functions, move with pushd and popd, and add tab completion with complete and compgen.
What you will learn
- Configure PS1 and run code before each prompt with PROMPT_COMMAND
- Choose between aliases and functions and move with pushd, popd and cd -
- Add simple tab completion for a command with complete and compgen
Before you start
What you’ll learn · Before you start
What you will learn
- Configure PS1 and run code before each prompt with PROMPT_COMMAND
- Choose between aliases and functions and move with pushd, popd and cd -
- Add simple tab completion for a command with complete and compgen
Before you start
On this page
You run git status forty times a day, your prompt is a bare $ that never shows where you are, and pressing Tab
after your own tool’s name does nothing. All three are a few lines in ~/.bashrc away from fixed.
Here you shape the prompt with PS1, decide when a short name should be an alias and when it should be a function,
walk a directory stack with pushd and popd, and teach Bash to complete the subcommands of a command you wrote.
Customise the prompt with PS1
The prompt string: Text shown before the cursor when the shell waits for input; in read -p 'text', Bash prints text first and then waits; PS3 is the prompt for select loops. PS1 is printed before the cursor, written with backslash escapes
that Bash fills in each time. The ${PS1@P} transformation expands those escapes exactly as the prompt does, so you
can see the result without opening a new shell:
from session import run
# PS1 holds the prompt, written with backslash escapes. ${PS1@P} expands those escapes the way the prompt does, so you
# can see what a prompt string renders to without opening a new shell. \\w is the working directory and \\$ is $ (or #
# for root).
run("""
PS1='[\\w]\\$ '
printf '%s\\n' "renders as: ${PS1@P}"
PS1='\\w \\$ '
printf '%s\\n' "renders as: ${PS1@P}"
""") Output
$ PS1='[\w]\$ '
$ printf '%s\n' "renders as: ${PS1@P}"
renders as: [~/demo]$
$ PS1='\w \$ '
$ printf '%s\n' "renders as: ${PS1@P}"
renders as: ~/demo $
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 prompt_session.py
PS1='[\w]\$ ' rendered as [~/demo]$: \w became the working directory and \$ became $. The common escapes
are below. Wrap any non-printing text, such as a colour code, in \[ and \], or Bash miscounts the line width and
editing a long command looks wrong.
| Escape | Expands to |
|---|---|
\u |
your user name |
\h |
the host name up to the first dot |
\w |
the working directory (\W just its last part) |
\$ |
$ for a normal user, # for root |
\[ \] |
wrap non-printing sequences (colours) so the width is counted right |
PROMPT_COMMAND runs just before each prompt is shown, which is how a prompt displays the last exit status or the
current git branch. Since Bash 5.1 it can be an array, and Bash runs each element in turn.
Key idea
PS1 is the prompt written with \u, \h, \w and \$ escapes, and PROMPT_COMMAND runs code before each prompt
to show things like the exit status or git branch.
Checkpoint
Check: PS1 and PROMPT_COMMAND
Aliases, functions and the directory stack
An alias: A short name Bash replaces with fixed text at the start of an interactive command; it takes no mid-command arguments and is off in scripts unless expand_aliases is set. is plain text that Bash substitutes for a word at the start of an interactive
command. It is quick for a fixed shortcut but cannot take arguments in the middle, and it is not expanded in scripts
unless shopt -s expand_aliases is set. A shell function: A named group of commands defined with name() { …; } or function name { …; }, called like a command. It runs in the current shell, shares its variables unless local is used, and returns a status with return. More in “Functions: arguments, local and return status” takes arguments and works
everywhere, so prefer a function when there is any logic:
from session import run
# An alias is plain text substitution for interactive typing; a function takes arguments and works everywhere. pushd,
# popd and dirs keep a stack of directories, and cd - returns to the previous one.
run("""
alias gs='echo "git status"'
gs
greet() { printf 'Namaste, %s\\n' "$1"; }
greet Ravi
mkdir -p src build
pushd src > /dev/null
pushd ../build > /dev/null
dirs -v
popd > /dev/null
dirs -v
cd ~
cd -
""") Output
$ alias gs='echo "git status"'
$ gs
git status
$ greet() { printf 'Namaste, %s\n' "$1"; }
$ greet Ravi
Namaste, Ravi
$ mkdir -p src build
$ pushd src > /dev/null
$ pushd ../build > /dev/null
$ dirs -v
0 ~/demo/build
1 ~/demo/src
2 ~/demo
$ popd > /dev/null
$ dirs -v
0 ~/demo/src
1 ~/demo
$ cd ~
$ cd -
~/demo/src
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 aliases_dirstack_session.py
alias gs='echo "git status"' made gs a shortcut, and the greet function used its argument to print
Namaste, Ravi. The directory stack: A stack of directories managed with pushd, popd and dirs; pushd moves into a directory and adds it, popd removes the top and returns to the one below. is the other half: pushd DIR changes into a
directory and pushes it on the stack, popd removes the top and returns to the one below, and dirs -v lists the
stack newest first. Follow the pushes and the pop:
The drawing shows the whole scene; its 5 steps are listed below.
Every step as text
- Start in ~/demo. You begin in ~/demo. The stack holds one entry, the directory you are in, and dirs shows just it.
- pushd src. pushd src changes into ~/demo/src and pushes it onto the stack. The stack is now src on top of demo, and the top entry is always where you are.
- pushd ../build. pushd ../build changes into ~/demo/build and pushes it too. dirs -v numbered them 0 build, 1 src, 2 demo, newest first.
- popd. popd removes the top entry, ~/demo/build, and changes you into the new top, ~/demo/src. dirs -v then showed 0 src, 1 demo.
- cd - returns to the last place. cd - jumps to the previous directory ($OLDPWD) without touching the stack. After cd ~ then cd -, the session printed ~/demo/src.
After pushd src and pushd ../build, dirs -v showed 0 build, 1 src, 2 demo. popd dropped back to src.
cd - is the lightweight version: it jumps to the previous directory ($OLDPWD) and back, without a stack.
Keep CDPATH out of scripts
CDPATH lets cd name find a directory under a list of parents, handy at the prompt but a trap in scripts, where
cd "$dir" might land somewhere unexpected. Unset it (unset CDPATH) at the top of a script.
Checkpoint
Check: Aliases, functions, directory stack
Add tab completion with complete and compgen
programmable completion: Bash's mechanism for completing a command's arguments when you press Tab, set up with complete and compgen and a function that fills the COMPREPLY array. is what fills in the rest of a word when you press
Tab. compgen -W 'words' -- prefix prints the words of a set that start with the prefix, and complete registers a
completion for a command, either a fixed word list or a function that fills the COMPREPLY array:
from session import run
# compgen -W lists the words of a set that match a prefix; a completion function reads the word being typed from
# COMP_WORDS[COMP_CWORD] and fills COMPREPLY; complete registers it (or a -W word list) for a command.
run("""
compgen -W 'start stop status restart' -- st
_mytool() { local cur="${COMP_WORDS[COMP_CWORD]}"; COMPREPLY=( $(compgen -W 'start stop status' -- "$cur") ); }
COMP_WORDS=(mytool st)
COMP_CWORD=1
_mytool
printf '%s\\n' "${COMPREPLY[@]}"
complete -W 'start stop status' mytool
complete -p mytool
""") Output
$ compgen -W 'start stop status restart' -- st
start
stop
status
$ _mytool() { local cur="${COMP_WORDS[COMP_CWORD]}"; COMPREPLY=( $(compgen -W 'start stop status' -- "$cur") ); }
$ COMP_WORDS=(mytool st)
$ COMP_CWORD=1
$ _mytool
$ printf '%s\n' "${COMPREPLY[@]}"
start
stop
status
$ complete -W 'start stop status' mytool
$ complete -p mytool
complete -W 'start stop status' mytool
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 completion_session.py
compgen -W 'start stop status restart' -- st printed the three words beginning with st and dropped restart. The
_mytool function read the word being completed from COMP_WORDS[COMP_CWORD] and filled COMPREPLY; called with
COMP_WORDS=(mytool st) it produced the same three candidates. complete -W 'start stop status' mytool then
registered a word list for mytool, and complete -p printed the registration back.
| Piece | Role |
|---|---|
compgen -W 'words' -- cur |
print the words of a set that start with cur |
complete -W 'words' cmd |
give cmd a fixed list of completion words |
complete -F fn cmd |
let the function fn fill COMPREPLY for cmd |
COMP_WORDS / COMP_CWORD |
the words on the line and which one is being completed |
For real commands, the bash-completion project ships ready-made completions on most Linux distributions, so you write
your own mainly for a tool of your own. Bash 5.3 adds compgen -V array to put the candidates straight into a named
array.
Key idea
compgen -W filters a word list by prefix, and complete attaches a word list or a COMPREPLY-filling function to
a command so Tab completes its arguments.
Checkpoint
Check: complete and compgen
Practise: prompt, aliases and completion
Each question shows a PS1, an alias, a directory-stack move or a completion like the ones above. Decide what happens,
then check your answer:
Practice that adapts to you
Practise this lesson
7 questions about 3 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
Interview questions
Warm-up (fresher to 2–5 years): when would you use an alias, and when a function?
Use an alias for a fixed shortcut you type often, such as alias ll='ls -l': it is simple text substitution at the
start of an interactive command. Reach for a function when the shortcut needs an argument, any logic, or must work in
a script, because an alias cannot take an argument in the middle and is not expanded in scripts unless
shopt -s expand_aliases is set. A function such as mkcd() { mkdir -p "$1" && cd "$1"; } takes arguments, runs
everywhere, and is what most of your ~/.bashrc shortcuts should be.
Two harder questions follow, about a prompt that breaks line wrapping and an alias that does nothing inside a script. Decide what you would check first, then open each first step:
Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.
-
Mid to senior
After adding colour to PS1, long command lines start wrapping over themselves and the cursor lands in the wrong place when you edit them. The colours look right. What is wrong and how do you fix it?
-
Fresher to mid
A developer moves a handy alias from their ~/.bashrc into a deployment script, but in the script the alias name is reported as "command not found". Explain why and give the right fix.
Flashcards · 6 cards
Recall it, then check
Say the answer to yourself before you look: recalling it is what makes it stick.
-
Which escapes build a PS1 prompt?
\u for the user, \h for the host, \w for the current folder and \$, which shows # for root and $ for everyone else.
-
Why do colour codes in PS1 go inside \[ and \]?
The brackets mark them as non-printing, so Bash counts the prompt's width correctly and long lines wrap in the right place.
-
What does PROMPT_COMMAND do?
It runs code before each prompt is shown, which can display things such as the last exit status or the current git branch.
-
When should a shortcut be a function rather than an alias?
When it needs an argument, any logic, or must work in a script: an alias is plain text substitution at the start of an interactive command, and scripts do not expand aliases unless expand_aliases is set.
-
How do you move between folders and come back?
pushd changes folder and saves the old one on a stack, popd returns to it, and dirs lists the stack; cd - goes back to the previous folder without any stack.
-
How do you give a command its own Tab completion?
compgen -W 'list of words' filters words by the prefix typed, and complete attaches a word list, or a function that fills COMPREPLY, to the command.
Key takeaways
PS1is the prompt written with\u,\h,\wand\$escapes; wrap non-printing colour codes in\[ \], andPROMPT_COMMANDruns code before each prompt.- An alias is interactive text substitution; a function takes arguments and works in scripts, so prefer a function for anything beyond a fixed shortcut.
pushd,popdanddirskeep a directory stack, andcd -returns to the previous directory without one.compgen -Wfilters words by prefix andcompleteattaches a word list or aCOMPREPLY-filling function to a command for Tab completion.
Terms in this lesson
- alias
- A short name Bash replaces with fixed text at the start of an interactive command; it takes no mid-command arguments and is off in scripts unless expand_aliases is set.
- directory stack
- A stack of directories managed with pushd, popd and dirs; pushd moves into a directory and adds it, popd removes the top and returns to the one below.
- programmable completion
- Bash's mechanism for completing a command's arguments when you press Tab, set up with complete and compgen and a function that fills the COMPREPLY array.
References
- Bash Reference Manual: Controlling the Prompt (Chet Ramey (GNU Bash maintainer))
- Bash Reference Manual: Aliases (Chet Ramey (GNU Bash maintainer))
- Bash Reference Manual: The Directory Stack (Chet Ramey (GNU Bash maintainer))
- Bash Reference Manual: Programmable Completion (Chet Ramey (GNU Bash maintainer))
Report a problem with this lesson
Kept only in this browser. Your Learn progress