Summary
An engineer attempted to automate the execution of a series of numbered bash aliases (rcv1 through rcv5) using a loop within a custom function. Despite enabling expand_aliases and sourcing the alias file, the function failed with “command not found” errors for every dynamic iteration. The core issue is a fundamental misunderstanding of how the Bash shell parses commands versus how it evaluates variables.
Root Cause
The failure occurs because of the order of command expansion in the Bash execution lifecycle.
- Parsing Phase: When Bash reads a line of code, it identifies the first word of a command as the “command identifier.”
- Tokenization: Bash looks for a literal match for that identifier in its hash table (built-ins, functions, or executables) or its alias list.
- Variable Expansion: Variable expansion (e.g., turning
rcv"$a"intorcv1) happens after the shell has already attempted to identify what the command is.
In the code rcv"$a", Bash looks for a literal command named rcv"$a". Since no command exists with that exact string (including the literal quotes and dollar sign), the shell fails before it ever has a chance to substitute $a with 1.
Why This Happens in Real Systems
This is a classic case of Lexical vs. Dynamic Evaluation.
- Static Lexing: Most shells are designed to be fast by performing as much “discovery” as possible before executing code. They expect the command name to be a static, immutable token.
- Non-Recursive Parsing: Once the shell decides “this line is a command execution,” it does not re-scan the command name for further expansions.
- Scope Misconceptions: Engineers often assume that if a variable is available in the environment, the shell will “glue” it to other strings to form a command, but the shell treats the first word of a command as a special, non-expandable token.
Real-World Impact
- Automation Failure: Critical deployment scripts or cleanup routines that rely on dynamic command generation will silently fail or crash.
- Brittle Tooling: Developers create “helper” functions that work for hardcoded values but break immediately when parameterized, leading to production regressions when the tool is used in a generic context.
- Wasted Debugging Cycles: Because the error message (
command not found) seems so simple, engineers often waste hours debugging the alias definition or file permissions when the issue is actually the syntax of the call itself.
Example or Code
To fix this, you must use the eval command, which instructs Bash to take a string, process its expansions, and then treat the resulting string as a new command to be parsed.
function runcv() {
# Ensure aliases are expanded in this shell context
shopt -s expand_aliases
local limit=$1
local a=1
while [[ $a -le $limit ]]
do
# eval forces the shell to re-parse the constructed string
eval "rcv$a"
a=$((a + 1))
done
}
How Senior Engineers Fix It
A senior engineer looks for ways to avoid eval whenever possible, as eval is a common vector for code injection vulnerabilities.
- Refactor to Functions: Instead of using aliases (which are intended for interactive convenience), move the logic into standard Bash functions. Functions are first-class citizens and much easier to call dynamically.
- Use Arrays: If the commands are known, store them in an array and iterate through the array elements.
- Prefer
printf -vor Command Substitution: If dynamic construction is unavoidable, ensure the string is constructed cleanly before execution. - Use Arithmetic Expansion: Replace the legacy
exprcommand with the modern$((...))syntax for better performance and readability.
Why Juniors Miss It
- Mental Model Mismatch: Juniors often treat Bash like a high-level language (like Python or JavaScript) where
getattr(object, "name" + i)is common. They assume the shell is “smart” enough to resolve the string. - Misunderstanding the Shell Lifecycle: They focus on whether the alias exists (it does) rather than how the shell locates it (parsing the token).
- Over-reliance on Aliases: Juniors often use aliases for everything, whereas senior engineers recognize that aliases are for humans and functions are for scripts.