Skip to content
VLSI Mentor

Linux & Bash · Part I · Module 10 · The Environment, PATH and Shell Startup

Aliases, Functions and Lookup Order

A name you type does not necessarily mean the program of that name. Three things get consulted before the filesystem is ever reached, and knowing the order turns an inexplicable result into a one-command diagnosis.

Intermediate18 min readLinuxPart IFundamentalsEnvironmentBash

Part I · Module 10 · Chapter 10.5 · Aliases, Functions and Lookup Order

Chapter 10.2 explained how the shell finds a file, and warned that the answer is sometimes not the whole answer. This chapter is the rest of it.

1. Four Layers

When you type a word, Bash asks four questions in a fixed order and takes the first answer it gets.

The order in which the shell decides what a name means. An alias is checked first, then a function, then a built-in command, and only last a file found along the search path. The first match wins, which is why a name can behave in a way the program of that name never would.01IS IT AN ALIAS?checked first, and only in an interactive shell02IS IT A FUNCTION?defined in this shell or something it sourced03IS IT A BUILT-IN?part of the shell itself, no process started04A FILE ON THE SEARCH PATHthe last place looked, not the first
Figure 1 — the order in which the shell decides what a name means. An alias is checked first, then a function, then a command built into the shell itself, and only last a file found along the search path. The first match wins and the remaining layers are never consulted — which is why a perfectly familiar name can behave in a way the program of that name never would.

Measured by making one name exist at every layer in turn and running it after each addition:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ echo hi                       # the builtin
hi

$ enable -n echo                # disable the builtin
$ echo
4. EXTERNAL file                # now a file on PATH wins

$ enable echo
$ echo() { builtin echo "2. FUNCTION"; }
$ echo
2. FUNCTION                     # the function beats the builtin

$ alias echo='builtin echo "1. ALIAS"'
$ echo
1. ALIAS                        # the alias beats the function

Each new layer took precedence over everything below it, in exactly the order of Figure 1.

2. Aliases

An alias is text substitution on the first word of a command:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ alias ll='ls -lh'
$ ll /tmp

The shell replaces ll with ls -lh and runs ls -lh /tmp. Nothing clever is happening — anything you type after the alias simply follows the replacement text.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
alias ll='ls -lh'
alias la='ls -A'
alias grep='grep --color=auto'
alias ..='cd ..'
alias gs='git status -sb'

Note alias grep='grep --color=auto' — an alias may refer to the command it shadows. There is no infinite loop, because the expansion's first word is not re-expanded as an alias.

Managing them:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
alias                  # list all
alias ll               # show one
unalias ll             # remove one
unalias -a             # remove all

The two reasons an alias does not expand

Both measured, and both catch people.

Aliases are off in non-interactive shells.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ bash -c 'alias ll="echo RAN"; ll'
bash: ll: command not found

A script does not expand aliases. The option that enables it exists:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
shopt -s expand_aliases

...and even then, not on the same line.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ bash -c 'shopt -s expand_aliases; alias ll="echo RAN"; ll'
bash: ll: command not found

$ bash -c 'shopt -s expand_aliases
alias ll="echo RAN"
ll'
RAN

Same option, same alias — the only difference is whether the definition and the use are on separate lines.

Bash reads and parses an entire line before executing any of it, and alias expansion happens during that parse. On one line, the alias does not exist yet when the parser reaches the use. On separate lines, it does.

This is the same reason an alias defined inside a function body cannot be used in that same function, and it is a good reason not to rely on aliases in scripts at all.

3. Functions

A function is real code with real arguments:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
mkcd() {
  mkdir -p "$1" && cd "$1"
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ mkcd /tmp/newdir
$ pwd
/tmp/newdir

An alias cannot do that. Here is the comparison, measured:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ alias mkcd='mkdir -p'
$ mkcd /tmp/aliastest

That created the directory and left you where you were, because the alias is only text — the cd has nowhere to go in a substitution, and there is no $1 to use.

And a function runs in your current shell (Chapter 10.4), which is why mkcd can change your directory where a script could not.

A function may wrap the command it shadows

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
ls() {
  command ls --color=auto "$@"
}

command skips the alias and function layers and goes straight to builtin-or-file — without it, ls inside the function would call the function again, forever.

The three escape hatches, one per layer:

WrittenSkips
\ls or 'ls'The alias layer only
command lsAlias and function
builtin cdAlias and function, forcing the builtin
/bin/lsEverything — an exact file

Measured:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ alias grep='echo ALIASED'
$ grep x
ALIASED x
$ \grep --version >/dev/null && echo "backslash bypassed the alias"
backslash bypassed the alias
$ command grep --version >/dev/null && echo "command bypassed it too"
command bypassed it too

The backslash works because quoting any character of the word stops it being treated as an alias — the word is no longer a bare identifier at parse time.

4. Builtins

A builtin is part of the shell itself. Chapter 1.7 introduced them; the relevant fact here is that some names exist as both a builtin and a file, and the builtin wins:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ type -a echo
echo is a shell builtin
echo is /bin/echo

$ type -a cd
cd is a shell builtin
cd is /usr/bin/cd

/usr/bin/cd exists and is useless — it changes its own directory and exits, which is Chapter 7.1's point exactly. It is there for standards compliance, and the builtin is what you get.

Builtins can be switched off:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
enable -n echo        # disable the builtin, so the file is used
enable echo           # re-enable it
enable -a | head      # list them all with their state

This is rarely useful outside demonstrations like §1, but enable -n is occasionally the only way to reach an external version of a name.

5. Making Them Persist

Aliases and functions are not inherited — Chapter 10.1 listed them among the things that do not cross a process boundary. So they must be re-created in every shell, which is exactly what ~/.bashrc is for (Chapter 10.3):

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
# ~/.bashrc
alias ll='ls -lh'
alias ..='cd ..'

mkcd() { mkdir -p "$1" && cd "$1"; }

simlog() {
  local run="${1:?usage: simlog <run-name>}"
  less "$HOME/runs/$run/sim.log"
}

For a larger collection, put them in a file and source it (Chapter 10.4):

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
# ~/.bashrc
[ -f ~/.bash_functions ] && . ~/.bash_functions

6. When a Familiar Command Behaves Strangely

The diagnostic procedure, in order.

1 — Ask what the name is.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ type -a ls
ls is aliased to `ls -lh --color=auto'
ls is /bin/ls

That usually ends it.

2 — Run it with the layers stripped off.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
\ls              # without the alias
command ls       # without the alias or a function
/bin/ls          # the file itself

If the behaviour disappears, you have found the layer.

3 — Find where it was defined.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
shopt -s extdebug
declare -F mkcd       # prints: mkcd 12 /home/engineer/.bash_functions
shopt -u extdebug

With extdebug on, declare -F reports the line number and file a function came from, which is the fastest way to find something defined three sourced files deep.

4 — Check for the PATH version of the same problem. If type says it is a plain file, you are back in Chapter 10.2 — which -a and hash -r.

Practice

1 — Walk the four layers. In a throwaway shell:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
bash --norc
type -a echo
enable -n echo; type echo; enable echo
echo() { builtin echo "FUNCTION"; }; echo; unset -f echo
exit

2 — Define and inspect an alias.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
alias ll='ls -lh'
type -a ll
alias ll
unalias ll

3 — Watch the same-line failure, then fix it.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
bash -c 'shopt -s expand_aliases; alias x="echo RAN"; x'
printf 'shopt -s expand_aliases\nalias x="echo RAN"\nx\n' > /tmp/al.sh
bash /tmp/al.sh

4 — Prove an alias cannot take arguments properly.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
alias mkcd='mkdir -p'
mkcd /tmp/a1 && echo "alias made it, pwd is still $(pwd)"
mkcd2() { mkdir -p "$1" && cd "$1"; }
mkcd2 /tmp/a2 && echo "function made it and moved: $(pwd)"
cd - >/dev/null

5 — Bypass each layer.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
alias grep='echo ALIASED'
grep x
\grep --version >/dev/null && echo "backslash worked"
command grep --version >/dev/null && echo "command worked"
unalias grep

6 — Wrap a command without recursing.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
ls() { echo "[wrapped]"; command ls "$@"; }
ls /tmp | head -3
unset -f ls

7 — Find where a function came from.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
printf 'myfn() { echo hi; }\n' > /tmp/fns.sh
source /tmp/fns.sh
shopt -s extdebug; declare -F myfn; shopt -u extdebug

8 — Count what you already have.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
alias | wc -l
declare -F | wc -l

On a long-lived account both numbers are usually larger than people expect.

Check Your Understanding

1. What order does Bash resolve a name in, and what is the one command that shows you?

2. Your alias works at the prompt and not in a script. Give both reasons it might fail.

3. Why can mkcd be a function but not an alias?

4. ls() { ls --color=auto "$@"; } hangs. Why, and what is the fix?

5. What is the difference between \ls, command ls and /bin/ls?

6. Why is alias rm='rm -i' a questionable habit?

7. Remember

  • Four layers, in order: alias, function, builtin, file on PATH. First match wins.
  • type -a lists every meaning in resolution order and is the right first diagnostic.
  • which only sees files, so it can report something the shell would never run.
  • An alias is text substitution on the first word. It cannot use arguments.
  • An alias may name the command it shadows — the replacement's first word is not re-expanded.
  • Aliases are off in non-interactive shells; shopt -s expand_aliases turns them on.
  • An alias defined and used on the same line does not expand, because Bash parses a whole line before executing any of it.
  • Use functions in scripts and aliases at the prompt.
  • A function runs in your current shell, so it can change your directory — which a script cannot.
  • command skips alias and function; builtin forces the builtin; \name skips only the alias; a full path skips everything.
  • Wrap a command with command or the function calls itself forever.
  • Aliases and functions are not inherited. They belong in ~/.bashrc, or a file it sources.
  • export -f smuggles a function to child Bash processes and is not a substitute for sourcing.
  • shopt -s extdebug makes declare -F report the file and line a function came from.
  • Avoid aliasing destructive commands. The habit does not transfer and does not apply in scripts.

Two of the commands in this chapter — shopt -s expand_aliases and shopt -s extdebug — have been used without explanation. The last chapter of the module is about those switches: what set and shopt each control, why there are two of them, and how to read somebody else's options line.

Where this fits

Part of the Linux & Bash curriculum.