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.
Measured by making one name exist at every layer in turn and running it after each addition:
$ 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 functionEach 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:
$ alias ll='ls -lh'
$ ll /tmpThe 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.
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:
alias # list all
alias ll # show one
unalias ll # remove one
unalias -a # remove allThe two reasons an alias does not expand
Both measured, and both catch people.
Aliases are off in non-interactive shells.
$ bash -c 'alias ll="echo RAN"; ll'
bash: ll: command not foundA script does not expand aliases. The option that enables it exists:
shopt -s expand_aliases...and even then, not on the same line.
$ 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'
RANSame 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:
mkcd() {
mkdir -p "$1" && cd "$1"
}$ mkcd /tmp/newdir
$ pwd
/tmp/newdirAn alias cannot do that. Here is the comparison, measured:
$ alias mkcd='mkdir -p'
$ mkcd /tmp/aliastestThat 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
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:
| Written | Skips |
|---|---|
\ls or 'ls' | The alias layer only |
command ls | Alias and function |
builtin cd | Alias and function, forcing the builtin |
/bin/ls | Everything — an exact file |
Measured:
$ 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 tooThe 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:
$ 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:
enable -n echo # disable the builtin, so the file is used
enable echo # re-enable it
enable -a | head # list them all with their stateThis 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):
# ~/.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):
# ~/.bashrc
[ -f ~/.bash_functions ] && . ~/.bash_functions6. When a Familiar Command Behaves Strangely
The diagnostic procedure, in order.
1 — Ask what the name is.
$ type -a ls
ls is aliased to `ls -lh --color=auto'
ls is /bin/lsThat usually ends it.
2 — Run it with the layers stripped off.
\ls # without the alias
command ls # without the alias or a function
/bin/ls # the file itselfIf the behaviour disappears, you have found the layer.
3 — Find where it was defined.
shopt -s extdebug
declare -F mkcd # prints: mkcd 12 /home/engineer/.bash_functions
shopt -u extdebugWith 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:
bash --norc
type -a echo
enable -n echo; type echo; enable echo
echo() { builtin echo "FUNCTION"; }; echo; unset -f echo
exit2 — Define and inspect an alias.
alias ll='ls -lh'
type -a ll
alias ll
unalias ll3 — Watch the same-line failure, then fix it.
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.sh4 — Prove an alias cannot take arguments properly.
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/null5 — Bypass each layer.
alias grep='echo ALIASED'
grep x
\grep --version >/dev/null && echo "backslash worked"
command grep --version >/dev/null && echo "command worked"
unalias grep6 — Wrap a command without recursing.
ls() { echo "[wrapped]"; command ls "$@"; }
ls /tmp | head -3
unset -f ls7 — Find where a function came from.
printf 'myfn() { echo hi; }\n' > /tmp/fns.sh
source /tmp/fns.sh
shopt -s extdebug; declare -F myfn; shopt -u extdebug8 — Count what you already have.
alias | wc -l
declare -F | wc -lOn 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 -alists every meaning in resolution order and is the right first diagnostic.whichonly 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_aliasesturns 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.
commandskips alias and function;builtinforces the builtin;\nameskips only the alias; a full path skips everything.- Wrap a command with
commandor the function calls itself forever. - Aliases and functions are not inherited. They belong in
~/.bashrc, or a file it sources. export -fsmuggles a function to child Bash processes and is not a substitute for sourcing.shopt -s extdebugmakesdeclare -Freport 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.
