Lecture 06 – Bash Functions & Subshells

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)c = \text{sum}(x, y) assigns the computed integer to cc.
  • 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:
    • bash myFunc() { # code }
  • 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 11.
    • 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: 00 = success, 1\ge 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++: 0false,  0true0 \Rightarrow \text{false},\; \neq 0 \Rightarrow \text{true} within boolean contexts.
  • Bash (command context): 0success/“true”,  0failure/“false”0 \Rightarrow \text{success/“true”},\; \neq 0 \Rightarrow \text{failure/“false”}.
  • Arithmetic contexts (( ... )) still treat 00 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

  • PPIDPPID – current Process ID.
  • BASHSUBSHELLBASH_SUBSHELL – integer depth level; 00 for main shell, 11 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: 00
  • Generic failure exit code: 11 (any 1\ge 1 indicates error)
  • Special variable: $?\$? – last command’s exit status.
  • Special variable: PPIDPPID – current process ID.
  • Special variable: BASHSUBSHELLBASH_SUBSHELL – current subshell depth.

Summary Cheat Sheet

  • To output data from a Bash function ➔ write to stdout (echo, printf, …).
  • To signal statusreturn 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; BASHSUBSHELLBASH_SUBSHELL shows current depth.
  • C “truthiness” vs Bash “success/failure” are inverted—keep mental model straight, especially in if chains.