Bash Command Resolution Tracer
ct (Command Trace) is a Bash command-resolution debugger and tracer.
It examines how a command name resolves and traces the subsequent filesystem and execution layers, bringing them together into a single resolution model.
The goal is to answer not only what command Bash will use, but why it resolves that way and what ultimately executes.
ct has its own command-resolution engine.
ct does not wrap or depend on type, which, or command -v to perform command resolution. It independently models Bash's command-resolution rules, then traces the selected command through filesystem and executable-loading layers.
ct was written in Bash and models Bash command resolution from within the running shell. It observes the current Bash environment and resolution state without modifying Bash's command-resolution behavior.
It models the resolution decision first, then follows the selected command through filesystem indirection and executable-loading layers to determine what ultimately executes.
When Bash encounters a command name, it does not simply search $PATH.
Depending on the command and the current shell environment, Bash may resolve the name as:
- an alias
- a function
- a reserved keyword
- a builtin
- an external executable found through
$PATH
The resolution can also be affected by POSIX mode, disabled builtins, and $PATH ordering.
When an external executable is selected, additional filesystem and execution layers can affect what ultimately runs, including symlinks, /etc/alternatives, interpreter paths, and system directory layouts.
Traditional tools such as type, which, and command -v answer useful but much narrower questions.
ct is intended to answer the broader question:
Why does this command resolve the way it does?
For a command such as:
ct awkct can show:
- aliases
- functions
- reserved keywords
- builtin status
- disabled builtins
- external executables
$PATHentries in search order- shadowed executables
- canonical filesystem paths
- symlink chains
/etc/alternativesindirection/usr-merge relationships- ELF interpreters
- script shebangs
- the Bash resolution target
- the kernel execution target, when applicable
This makes ct useful when a command behaves differently from what was expected.
The Bash resolution target and the kernel execution target are not necessarily the same thing
For an ELF executable, the path Bash resolves may pass through filesystem indirection before the kernel loads the executable and its ELF interpreter:
Bash Resolution Target
↓
/usr/bin/example
↓
symlink
↓
/usr/bin/example-real
↓
ELF interpreter
↓
kernel execution
For scripts, the execution path can instead involve a shebang:
Bash Resolution Target
↓
/usr/local/bin/example
↓
#!/usr/bin/env python3
↓
interpreter
↓
kernel execution
ct exposes these layers as a whole snapshot of command resolution.
ct examines $PATH in order and identifies how those entries affect command visibility.
This includes:
- the first executable Bash can reach
- later shadowed executables
- duplicate or equivalent filesystem locations
- selected system directories that are not currently present in
$PATH
This is particularly useful when multiple versions of a command exist.
For example:
ct pythoncan show not only which python is selected, but what other candidates exist behind it.
Some system commands exist in administrative directories such as:
/usr/sbin
/sbin
These directories may not be present in an ordinary user's $PATH.
ct can temporarily extend the search path for discovery, allowing it to determine whether an otherwise hidden command exists outside the current $PATH.
The shell environment is restored afterward.
When ct changes $PATH, Bash discards its remembered command locations, stored in its command hash table. Bash lazily rebuilds this table as commands are subsequently executed.
ct does not directly modify Bash's command hash table. It does inspect the table to identify cached command paths and detect discrepancies between Bash's cached state and the current $PATH resolution.
Bash maintains an internal hash table of remembered command locations to speed up command resolution. ct inspects this cached state and compares it against its own live $PATH resolution model.
When Bash has a cached path that differs from the path ct resolves through the current $PATH, ct reports the discrepancy as a hash-table warning.
Bash Resolution Target:
↳ Resolved to: Filesystem → /home/jb/hash_test/bin2/hashfoo
↳ Hash Table Warning:
↳ Cached path: /home/jb/hash_test/bin1/hashfoo
↳ If the problem persists, run: hash -d hashfoo
A discrepancy can occur when:
- A command was moved or removed after Bash cached its location.
- A previously cached path is no longer valid while another matching command is available later in
$PATH.
Note:
ctnever modifies Bash's internal hash table. It reports the cached state as it exists whenctis run.
A hash mismatch does not necessarily mean command execution will fail. For example, if Bash's cached path no longer exists, Bash can discard the stale entry, perform a new $PATH search, and update its hash table with the newly resolved location.
The cached state is exposed in machine-readable JSON through the hash_table object.
Cached with a mismatch:
"hash_table": {
"cached": true,
"path": "/home/jb/hash_test/bin1/hashfoo",
"mismatch": true
}Cached with no mismatch:
"hash_table": {
"cached": true,
"path": "/usr/bin/awk",
"mismatch": false
}Uncached (no entry in the hash table):
"hash_table": {
"cached": false,
"path": null,
"mismatch": false
}Manual extension is useful when investigating command-name conflicts involving directories that are not currently in $PATH.
For example, if a user command shadows a system command, extending $PATH can expose the system copy and show the resulting shadowing relationship:
ct -x useraddThis is particularly useful for determining whether a command in a user-controlled directory is masking an administrative or system command.
ct -c is a separate analysis mode for finding command-name collisions inside the current Bash environment.
ct -cIt examines command names provided by:
- aliases
- functions
- builtins
- reserved keywords
and checks whether those names also exist as external commands.
This answers a different question from normal command tracing:
Which names in this shell environment can represent more than one kind of command?
A normal resolution trace is concerned with how one particular command resolves.
Conflict analysis is concerned with potential collisions across the environment.
ct -c therefore does not perform a complete $PATH trace for every collision. External commands are used as presence indicators rather than producing a full resolution report.
This keeps conflict reports focused instead of filling them with normal $PATH precedence information.
ct can emit machine-readable JSON:
ct -j awkJSON mode is intended for:
- scripting
- automation
- inspection
- integration with other tools
Conflict analysis can also produce JSON:
ct -c -jBoolean values are emitted as JSON booleans, and null is used where a field is not applicable.
The JSON structure may evolve between major versions.
- Bash command-resolution debugging and tracing
- Alias / function / keyword / builtin / executable detection
- Enabled & disabled builtin detection
- Shadowed command detection
$PATHvisibility and precedence analysis- Symlink resolution + chain
/etc/alternativesdetection/usr-merge detection- ELF interpreter & shebang detection
- Bash resolution target vs kernel execution target
- Automatic
$PATHextension - Manual
$PATHextension with-x - Environment conflict analysis with
-c command_not_found_handledetection- Bash command hash-table inspection + cached-path mismatch detection
- Colorized human output
- JSON with
-j - Combined short options such as
-cjx - Tab completion
- OSC 8 hyperlinks for source locations
- Shell state preservation
- Works in interactive shells and scripts
Clone the repository:
git clone https://github.com/JB63134/bash_ct.git ~/.bash_ctSource .bash_ct from your Bash startup file:
echo "source ~/.bash_ct/.bash_ct" >> ~/.bashrcThen reload the shell:
source ~/.bashrc- Bash 4.4 or newer
ct requires:
readlinkreadelfmktempstat
tput is used for color output when available.
ct [options] command
| Option | Description |
|---|---|
-h, --help |
Show usage information |
-v, --version |
Show version and license |
-j, --json |
Emit JSON output |
-x, --extend |
Extend $PATH for discovery |
-c, --conflict |
Environment conflict analysis |
Combined short options are supported:
ct -cjxTrace an external command:
ct lsTrace a command with potentially interesting filesystem resolution:
ct pythonProduce JSON:
ct -j bashDiscover commands outside the normal $PATH:
ct -x useraddAnalyze command-name conflicts:
ct -cCombine options:
ct -cjxct operates on bare command names.
Valid:
ct awk
ct python
ct cdNot valid:
ct /bin/ls
ct ./scriptThis restriction reflects what ct is designed to investigate: Bash command-name resolution, rather than arbitrary pathname execution.
command_not_found_handle is a Bash mechanism that can be invoked when normal command resolution fails. It is not part of Bash's command-resolution search itself.
When ct cannot resolve a command name, it reports the command as unknown or invalid.
If the Bash function command_not_found_handle is defined, ct also reports its presence and source location:
ct foobarbazUnknown or invalid command: foobarbaz
↳ Check your spelling and try again
⎟
↳ Post-failure Bash fallback:
↳ Bash's command_not_found_handle → defined
↳ Declared in (/home/jb/.bash_functions : line 104)
↳ Bash may invoke this handler for a bare invocation of: foobarbaz
When examining an unresolved command, ct reports the handler's presence and location but does not invoke the handler.
Because command_not_found_handle is itself a Bash function, it can also be examined directly:
ct command_not_found_handleIn that case, ct traces it as a normal Bash function and reports its definition and location.
A command can look simple at the prompt:
$ foobut Bash may have several layers of information to consider when resolving it.
There may be:
aliases
functions
builtins / keywords
$PATH executables
shadowed entries
symlink chains
ELF interpreters or shebangs
Most command-discovery tools expose only a portion of this information.
ct was built to make these layers inspectable.
The goal is not merely to answer:
Where is
foo?
but:
What does
foomean in this Bash environment, what is hiding behind it, what else could it resolve to, and, when applicable, what will actually execute?




