Created
September 6, 2025 23:03
-
-
Save mevanlc/62bf0c38bc1cb5edcf048c73d2127f4d to your computer and use it in GitHub Desktop.
what_is.sh that command?
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env bash | |
# what_is.sh - Inspect how Bash would resolve a command name. | |
# Modes: | |
# (A) Source to define the function: . what_is.sh | |
# Then use: what_is <cmd> | |
# (B) Source-and-run once: . what_is.sh <cmd> | |
# (C) Execute (cannot see live aliases/functions in this shell): | |
# ./what_is.sh <cmd> | |
what_is() { | |
# Usage: what_is <cmd> | |
if [ $# -ne 1 ]; then | |
printf -- "Usage: what_is <command>\n" >&2 | |
return 2 | |
fi | |
local cmd=$1 | |
printf -- "Command: %s\n" "$cmd" | |
printf -- "Shell: bash %s\n\n" "${BASH_VERSION:-unknown}" | |
# Must not `exit` here; this runs in the caller when sourced. | |
if ! type -a -- "$cmd" >/dev/null 2>&1; then | |
printf -- "Not found: %s (return 127)\n" "$cmd" | |
return 127 | |
fi | |
printf -- "== type -a output (all resolutions) ==\n" | |
type -a -- "$cmd" | |
printf -- "\n" | |
# 1) Alias | |
if alias "$cmd" >/dev/null 2>&1; then | |
printf -- "---- Alias ----\n" | |
alias "$cmd" | |
printf -- "\n" | |
fi | |
# 2) Function (+ source location if extdebug is available) | |
if declare -F "$cmd" >/dev/null 2>&1; then | |
printf -- "---- Function ----\n" | |
# Remember extdebug state, enable temporarily if needed. | |
local had_extdebug=0 | |
if shopt -q extdebug; then | |
had_extdebug=1 | |
else | |
shopt -s extdebug 2>/dev/null || true | |
fi | |
# With extdebug, `declare -F name` prints: name line file | |
local meta fn_name fn_line fn_file | |
meta=$(declare -F "$cmd" 2>/dev/null || true) | |
set -- $meta | |
fn_name=${1:-} | |
fn_line=${2:-} | |
fn_file=${3:-} | |
if [ -n "$fn_line" ] && [ -n "$fn_file" ]; then | |
printf -- "Defined at: %s:%s\n" "$fn_file" "$fn_line" | |
else | |
printf -- "Defined at: (location not available; try 'shopt -s extdebug')\n" | |
fi | |
# Restore extdebug if we enabled it. | |
if [ "$had_extdebug" -eq 0 ]; then | |
shopt -u extdebug 2>/dev/null || true | |
fi | |
printf -- "Source code:\n" | |
declare -f "$cmd" | |
printf -- "\n" | |
fi | |
# 3) Keyword (reserved word) | |
if compgen -A keyword -- "$cmd" | grep -qx -- "$cmd"; then | |
printf -- "---- Keyword ----\n" | |
printf -- "'%s' is a shell reserved word (keyword).\n" "$cmd" | |
help "$cmd" 2>/dev/null | sed -n '1,12p' || true | |
printf -- "\n" | |
fi | |
# 4) Builtin (even if shadowed elsewhere) | |
if compgen -b | grep -qx -- "$cmd"; then | |
printf -- "---- Builtin ----\n" | |
printf -- "'%s' is a shell builtin.\n" "$cmd" | |
help "$cmd" 2>/dev/null | sed -n '1,20p' || true | |
printf -- "\n" | |
fi | |
# 5) Files on PATH (scripts or binaries). Show all unique paths. | |
printf -- "---- PATH File(s) ----\n" | |
local paths=() seen_list="" | |
# `type -aP` = all, path search only (files) | |
while IFS= read -r p; do | |
[ -z "$p" ] && continue | |
# dedupe without assoc arrays (portable to bash 3.x) | |
if printf -- "%s\n" "$seen_list" | grep -Fxq -- "$p"; then | |
: | |
else | |
paths+=("$p") | |
seen_list="${seen_list}${seen_list:+ | |
}$p" | |
fi | |
done < <(type -aP -- "$cmd" 2>/dev/null || true) | |
if [ "${#paths[@]}" -eq 0 ]; then | |
printf -- "(no disk file found in PATH for '%s')\n\n" "$cmd" | |
else | |
local hashed_path have_file resolved ft | |
hashed_path=$(hash -t -- "$cmd" 2>/dev/null || true) | |
if command -v file >/dev/null 2>&1; then | |
have_file=1 | |
else | |
have_file=0 | |
fi | |
_resolve_path() { | |
# best-effort path resolution without assuming GNU utils | |
local t=$1 out | |
if command -v realpath >/dev/null 2>&1; then | |
realpath -- "$t" 2>/dev/null || true | |
elif readlink -f -- "$t" >/dev/null 2>&1; then | |
readlink -f -- "$t" 2>/dev/null || true | |
else | |
readlink -- "$t" 2>/dev/null || true | |
fi | |
} | |
local p | |
for p in "${paths[@]}"; do | |
printf -- "Path: %s\n" "$p" | |
resolved=$(_resolve_path "$p") | |
if [ -n "$resolved" ] && [ "$resolved" != "$p" ]; then | |
printf -- "Resolved: %s\n" "$resolved" | |
fi | |
if [ "$have_file" -eq 1 ]; then | |
ft=$(file -b -- "$p" 2>/dev/null || true) | |
[ -n "$ft" ] && printf -- "File type: %s\n" "$ft" | |
fi | |
if head -n 1 -- "$p" 2>/dev/null | grep -q '^#!'; then | |
printf -- "Shebang: %s\n" "$(head -n 1 -- "$p" 2>/dev/null)" | |
fi | |
if [ -L "$p" ] 2>/dev/null; then | |
ls -l -- "$p" 2>/dev/null | sed 's/^/ls -l: /' | |
fi | |
if [ -n "$hashed_path" ] && [ "$hashed_path" = "$p" ]; then | |
printf -- "(This path is currently hashed in this shell)\n" | |
fi | |
printf -- "\n" | |
done | |
fi | |
# Primary resolution | |
local primary | |
primary=$(type -t -- "$cmd" 2>/dev/null || true) | |
if [ -n "$primary" ]; then | |
printf -- "== Primary resolution used by this shell ==\n" | |
case "$primary" in | |
alias) printf -- "%s is an alias.\n" "$cmd" ;; | |
function) printf -- "%s is a function.\n" "$cmd" ;; | |
keyword) printf -- "%s is a keyword (reserved word).\n" "$cmd" ;; | |
builtin) printf -- "%s is a builtin.\n" "$cmd" ;; | |
file) printf -- "%s is a disk file (script or binary).\n" "$cmd" ;; | |
*) printf -- "%s has type: %s\n" "$cmd" "$primary" ;; | |
esac | |
fi | |
} | |
# Detect whether we are sourced. | |
# If sourced: define function; if args were provided, run once. | |
# If executed: run once, but warn about alias/function visibility. | |
if [ "${BASH_SOURCE[0]}" != "$0" ]; then | |
# Sourced | |
if [ $# -gt 0 ]; then | |
what_is "$@" | |
else | |
printf -- "Defined function: what_is\n" | |
printf -- "Usage: what_is <command>\n" | |
fi | |
else | |
# Executed | |
if [ $# -eq 0 ]; then | |
printf -- "Usage:\n . %s [<command>] # preferred; sees live aliases/functions\n %s <command> # executed; cannot see live aliases/functions\n" "$0" "$0" >&2 | |
exit 2 | |
fi | |
# Run and forward status | |
what_is "$@" | |
exit $? | |
fi | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment