The type builtin tells you exactly what bash will execute when you
run a command name. Is it a shell builtin, an alias, a function, or an external
binary on the PATH? type answers that question without ambiguity —
making it invaluable for debugging, scripting, and understanding your environment.
Unlike which — which only finds external binaries —
type sees the full bash command resolution order:
aliases → functions → builtins → PATH.
When you type a command, bash resolves it in this strict order — first match wins:
Shell aliases defined with alias command
Shell functions defined in your session or rc files
Commands built into bash itself — cd, echo, type
External binaries found on $PATH
Reserved words — if, while, function
ls,
it shadows the external /bin/ls entirely. type ls will
reveal that immediately. This is the most common source of "why isn't my command
working?" confusion.
The fundamental use — find out what a name resolves to:
type ls type cd type echo type if type grepOutput:
type commandname is the first thing to run. An unexpected alias
or function shadowing an external binary is a very common culprit.
The -t flag prints just the type word — perfect for scripting and conditionals:
type -t ls # alias type -t cd # builtin type -t grep # file type -t if # keyword type -t myFunc # function (if defined) type -t nothing # (no output, returns 1)Output (one word per command):
Use in scripts to branch on command type:
# Only proceed if the command is an external binary if [[ "$(type -t rsync)" == "file" ]]; then rsync -av /src/ /dst/ else echo "rsync not found on PATH" >&2 exit 1 fi # Check before calling — works for any type cmd_exists() { [[ -n "$(type -t "$1" 2>/dev/null)" ]] } cmd_exists docker && echo "docker available" || echo "docker missing"
type -t returns exit code 1
if the name is not found at all — making it clean for boolean tests
without parsing output.
The -a flag reveals every place a name is defined — aliases,
functions, builtins, AND all PATH matches:
type -a echo type -a ls type -a pythonOutput:
-a shows all of them in order. The first one is what bash actually runs.
This is how you discover version conflicts — e.g., system Python vs. a virtualenv Python.
# Useful one-liner — see full resolution for several commands at once
type -a python python3 pip pip3 2>/dev/null
-P bypasses aliases and functions and searches PATH directly,
like a more honest version of which:
# Even if 'ls' is aliased, -P finds the binary type -P ls type -P echo type -P cd # returns nothing — cd is a builtin, not on PATHOutput:
which
is an external binary that cannot see bash aliases or functions at all.
type -P is a builtin that deliberately skips them.
In scripts, prefer type — it's always available, no package needed.
The canonical way to check if a command is available before using it:
#!/bin/bash
# Check required tools before starting
require_cmd() {
local cmd=$1
if ! type -t "$cmd" &>/dev/null; then
echo "ERROR: Required command '$cmd' not found." >&2
exit 1
fi
}
require_cmd rsync
require_cmd jq
require_cmd aws
echo "All required tools found. Starting..."
Or a softer check with a fallback:
# Use pigz (parallel gzip) if available, fall back to gzip
if type -t pigz &>/dev/null; then
COMPRESS=pigz
else
COMPRESS=gzip
fi
tar -cf - /data | $COMPRESS > /backup/data.tar.gz
command -v name is POSIX-portable (works in sh).
type -t name is bash-specific but gives you the type keyword too.
For bash scripts, either works — type -t is more informative.
Diagnosing why a command behaves differently than expected:
# Something is wrong with grep — let's see what it really is type grep type -a grep # Is rm really rm? On some systems it's aliased to rm -i type rm # Check if someone defined a function shadowing a system command type sudo type sshExample revealing an alias:
To run the real binary, bypassing any alias or function:
# Prefix with backslash to bypass alias \grep pattern file # Or use command builtin to bypass alias AND function command grep pattern file # Or use the full path from type -P $(type -P grep) pattern file
type will show you that something is a function — and display its definition:
# Define a function mybackup() { tar -czf "/backup/$(date +%Y%m%d).tar.gz" "$@" } # type shows the full definition type mybackupOutput:
type functionname
is a quick way to inspect functions defined in your environment without hunting
through rc files — especially useful when inheriting someone else's system.
type accepts multiple names in one call:
# Check several commands at once type ls cd grep awk sed find xargs # Audit your PATH for a list of required tools REQUIRED=(rsync jq curl wget python3 ansible) for cmd in "${REQUIRED[@]}"; do printf "%-15s %s\n" "$cmd" "$(type -t "$cmd" 2>/dev/null || echo 'NOT FOUND')" doneOutput:
| Command | What it does |
|---|---|
| type NAME | Full description of what NAME resolves to |
| type -t NAME | Print type keyword only: alias, function, builtin, file, keyword |
| type -a NAME | Show ALL definitions in resolution order |
| type -P NAME | Search PATH only — skip aliases and functions |
| type -p NAME | PATH search — silent if not a file |
| type -f NAME | Suppress function lookup |
| type NAME1 NAME2 | Check multiple names in one call |
| type -t NAME &>/dev/null | Existence check for scripts (exit 0/1) |
| Command | Sees aliases? | Sees functions? | Sees builtins? | Notes |
|---|---|---|---|---|
| type | ✅ Yes | ✅ Yes | ✅ Yes | Bash builtin — most complete |
| which | ❌ No | ❌ No | ❌ No | External binary — PATH only |
| command -v | ✅ Yes | ✅ Yes | ✅ Yes | POSIX portable — works in sh |
| whereis | ❌ No | ❌ No | ❌ No | Finds binary, man page, source |
| whatis | ❌ No | ❌ No | ❌ No | Shows man page one-liner only |