Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

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.

  • Intermediate
  • 5 minutes
  • Examples run with Bash 5.3.20, dash 0.5.13.5, shellcheck 0.11.0 and Python 3.14.8
  • By MySmartCoPilot
Free lesson, Pro answers: 5 minutes
Free: the whole lesson. With a pass: the model answers to its 2 interview questions.

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:

Expanding a prompt string with PS1@P Python · prompt_session.py
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

  1. Question 1 of 2 In PS1, what does the \w escape expand to?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: The current working directory

    In the session PS1='[\w]\$ ' rendered as [~/demo]$, so \w became the directory and \$ became $.

  2. Question 2 of 2 You want the prompt to show the current git branch, recomputed each time. Which setting is built for that?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: PROMPT_COMMAND

    PROMPT_COMMAND runs just before each prompt and, since Bash 5.1, can be an array of commands.

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:

An alias, a function, and the directory stack Python · aliases_dirstack_session.py
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:

Interactive · step by step How pushd and popd move a directory stack Step through the pushes and the pop and watch the stack grow and shrink; the top is where you are now.
How pushd and popd move a directory stack, step 5 of 5. Showing ~/demo/src, ~/demo. In focus: ~/demo/src.~/demo/src~/demo

The drawing shows the whole scene; its 5 steps are listed below.

Every step as text
  1. Start in ~/demo. You begin in ~/demo. The stack holds one entry, the directory you are in, and dirs shows just it.
  2. 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.
  3. pushd ../build. pushd ../build changes into ~/demo/build and pushes it too. dirs -v numbered them 0 build, 1 src, 2 demo, newest first.
  4. 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.
  5. 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

  1. Question 1 of 2 Why does a function suit "make a directory and cd into it" better than an alias?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: A function takes the directory name as an argument; an alias cannot use an argument mid-command

    mkcd() { mkdir -p "$1" && cd "$1"; } uses its argument; an alias could not.

  2. Question 2 of 2 The stack (newest first) is build, src, demo and you are in build. What does popd do?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: Removes build and changes you into src

    In the session popd left dirs -v showing 0 src, 1 demo.

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:

compgen, a completion function and complete Python · completion_session.py
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

  1. One question What does compgen -W 'start stop status restart' -- st print?

    Choose one answer.

    Show the answer: One question

    Answer: start, stop and status

    The session printed start, stop and status for the prefix st.

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.

  1. Question 1 of 7 What does the \$ escape in PS1 show for an ordinary (non-root) user?

    Choose one answer.

    Show the answer: Question 1 of 7

    Answer: $

    \$ is $ for a normal user and # for root, a quick way to see whether you are root.

  2. Question 2 of 7 A colourful prompt makes long command lines wrap in the wrong place. What is usually missing?

    Choose one answer.

    Show the answer: Question 2 of 7

    Answer: The colour codes are not wrapped in \[ \]

    \[ and \] tell Bash that the bytes between them take no screen width, so wrapping lines up.

  3. Question 3 of 7 An alias defined in a script does nothing when the script runs. Why?

    Choose one answer.

    Show the answer: Question 3 of 7

    Answer: Alias expansion is off in non-interactive shells unless shopt -s expand_aliases is set

    shopt -s expand_aliases turns on alias expansion in a script; a function avoids the issue.

  4. Question 4 of 7 What does cd - do?

    Choose one answer.

    Show the answer: Question 4 of 7

    Answer: Changes to the previous directory ($OLDPWD)

    cd - jumps to $OLDPWD and prints it, as the session showed (~/demo/src).

  5. Question 5 of 7 dirs -v numbers the stack. Which entry is number 0?

    Choose one answer.

    Show the answer: Question 5 of 7

    Answer: The directory you are in now (the most recent pushd)

    In the session dirs -v showed 0 build while you were in build.

  6. Question 6 of 7 You want Tab on "mytool " to offer different words depending on the subcommand already typed. What do you register?

    Choose one answer.

    Show the answer: Question 6 of 7

    Answer: A function with complete -F, reading COMP_WORDS and COMP_CWORD

    complete -F fn mytool lets fn read COMP_WORDS[COMP_CWORD] and set COMPREPLY for the current word.

  7. Question 7 of 7 Inside a completion function, where does Bash read the candidate list you produce?

    Choose one answer.

    Show the answer: Question 7 of 7

    Answer: From the COMPREPLY array you fill

    The _mytool function filled COMPREPLY with compgen output, and Bash offers those words.

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.

  1. 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?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. 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.

    Model answer, Pro. First step and follow-ups

Flashcards · 6 cards

Recall it, then check

Say the answer to yourself before you look: recalling it is what makes it stick.

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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.

  6. 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

  • PS1 is the prompt written with \u, \h, \w and \$ escapes; wrap non-printing colour codes in \[ \], and PROMPT_COMMAND runs 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, popd and dirs keep a directory stack, and cd - returns to the previous directory without one.
  • compgen -W filters words by prefix and complete attaches a word list or a COMPREPLY-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

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.