Conceptual Overview
- Focus: contrasting C++‐style functions with Bash functions, introducing the concept of subshells, process IDs, and scope rules.
- Central message: Bash is NOT “C++ with different syntax”; it is designed around shell pipelines, stdout/stderr streams, exit codes, and process forking. All design choices (return values, variable scope, subshell behaviour) follow from that.
Functions in C++ (Refresher)
- A C++ function computes and returns a value.
- Example:
int sum(int a, int b) { return a + b; } - Usage: c=sum(x,y) assigns the computed integer to c.
- A procedure is a function that returns
void (no value). - Return value is stored in a dedicated memory location and delivered to caller.
Bash Functions: Core Behaviour & Syntax
- Declared with the name followed by parentheses and braces:
- Multiple parameters accepted, accessed the same way as scripts (
$1, $2, $@, $#, …). - Internally behave like mini-scripts embedded in the current script.
"return" vs "echo" in Bash
- Key distinction: Bash functions do not return data the way C++ does; they only return an exit status.
- Example 1 – unexpected result:
return1() { return 1; }
a=$(return1)
echo "$a" # Prints NOTHING
$(...) captures stdout; the function produced no stdout, only an exit status of 1.- Example 2 – desired stdout capture:
return2() { echo 1; }
a=$(return2)
echo "$a" # Prints 1
- Consequence:
echo, printf, or any other stdout-producing command is how you send data out of a Bash function.
Purpose of "return" (and comparison with "exit")
return <n> sets the function’s exit status.- Convention: 0 = success, ≥1 = failure.
- Mirrors
exit <n> at the script level; difference is scope (return exits only the function). - Practical use cases:
- Gatekeeping in control structures:
bash
if checkPrinterOnline; then
usePrinter
fi
while, until, logical && / || chains.
Capturing Exit Codes with $?
\$? contains the exit status of the most recently executed command.
a=$(return1) # function sets exit status 1
echo $? # Prints 1
echo $? # Prints 0 (because the previous echo succeeded)
- Volatile: any intervening command immediately overwrites it.
- Analogy: In C/C++ we often
return 0; from main; Bash scripts should exit 0 on success for the same reason—communicating outcome to the caller.
Truthiness Inversion: C++ vs Bash
- C/C++: 0⇒false,=0⇒true within boolean contexts.
- Bash (command context): 0⇒success/“true”,=0⇒failure/“false”.
- Arithmetic contexts
(( ... )) still treat 0 as false; hence two co-existing notions of truth. - Example:
if ./script.sh; then
echo "Script succeeded" # executes only if exit status == 0
fi
Variable Scope in Bash Functions
- Default: global — variables assigned inside a function bleed into the parent scope.
normalBash() {
a=1
}
normalBash
echo $a # Prints 1 (global side-effect)
- Rationale rant: limited variable-name space ➔ collisions ➔ messy global namespace.
- Remedy:
local keyword.
scoped() {
local tmp=5 # visible only inside function
}
- Still unlike many languages where local scope is the default.
Practical Takeaways on Bash Functions
- Good for small, repetitive code snippets inside bigger scripts.
- Arguments behave exactly like script arguments:
$1, $2, $@. - Exit status often more useful than returned data.
- Overuse discouraged; Bash not intended for large-scale software engineering (global-by-default is a hint).
Introduction to Subshells
- Subshell = a child process running its own instance of the shell.
- Spawned implicitly by:
- Command substitution:
$(...) or back-ticks `...` - Pipelines (
|) (each side may run in separate subshells depending on implementation) - Parentheses grouping
( ... ).
- Simple example already used:
a=$(ls) # "ls" executes in a subshell; stdout captured in a
Environment Variables for Process & Subshell Insight
- PPID – current Process ID.
- BASHSUBSHELL – integer depth level; 0 for main shell, 1 for first subshell, etc.
- Demonstration:
echo "test1 $BASH_SUBSHELL" # => test1 0
a=$(echo "test2 $BASH_SUBSHELL") # subshell depth 1 inside $(...)
echo "$a" # => test2 1
- Nested subshell example (depth 2):
a=$(echo "outer $BASH_SUBSHELL"; b=$(echo "inner $BASH_SUBSHELL"); echo $b)
# outer prints with depth 1, inner with depth 2
Scope Rules in Subshells
- Subshells do not share variable scope with the parent.
- Example:
bash
a=$(b=1) # Inside subshell: b=1 (no stdout)
echo "$a, $b" # Outputs: , (empty a, undefined b)
- Hence a neat tool to isolate temporary variables without polluting parent namespace.
Practical Guidance & Caveats for Subshells
- Advantages:
- Encapsulate side-effect-heavy operations.
- Capture complex stdout sequences cleanly.
- Hazards:
- Deep nesting (
subshell in subshell in subshell) quickly diminishes readability (“Shell-ception”). - Can obscure variable flow; remember that child modifications don’t propagate back.
- Rule of thumb: use deliberately, document intention, avoid gratuitous depth.
Ethical / Philosophical Notes
- Design philosophy mismatch: expecting Bash to behave like a real programming language leads to confusion.
- Shell was historically a command dispatcher; features such as functions, arithmetic, arrays were bolted on later ("a hack on a hack").
- Wise developer approach: embrace its strengths (glueing, piping, text processing), outsource heavy logic to languages better suited (Python, C++, etc.).
Numerical & Symbolic Reference List
- Success exit code: 0
- Generic failure exit code: 1 (any ≥1 indicates error)
- Special variable: $? – last command’s exit status.
- Special variable: PPID – current process ID.
- Special variable: BASHSUBSHELL – current subshell depth.
Summary Cheat Sheet
- To output data from a Bash function ➔ write to stdout (
echo, printf, …). - To signal status ➔
return n (inside function) or exit n (script level). - Capture last status ➔
status=$? immediately after the command. - Default variable scope ➔ global; declare
local var=… to limit scope. - Command substitution, parentheses, and pipelines spawn subshells; variables are not shared upward; BASHSUBSHELL shows current depth.
- C “truthiness” vs Bash “success/failure” are inverted—keep mental model straight, especially in
if chains.