Advanced Bash Scripting

Go beyond the basics. In this lesson you'll write real-world bash: functions that return data and status, arrays, modern [[ ]] tests and case branching, "strict mode" safety with set -euo pipefail , cleanup with trap , debugging traces, and powerful parameter-expansion tricks.

Learn Advanced Bash Scripting in our free Command Line course — an interactive lesson with worked examples, a practice exercise and a quick reference.

Part of the free Cli course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

1. Functions: Arguments, Scope & Return

Define a function with name() {' ... '} and call it like any command. Inside, the arguments are $1 , $2 , and $@ for all of them — exactly like a script. Use local so a function's variables don't leak out. A function doesn't return data with return ; instead it echoes its result (you capture it with result=$(myfunc arg) ), and uses return N only to set an exit status ( 0 – 255 ).

2. Arrays

An array holds many values under one name: arr=(apple banana cherry) . Read one element by index with {'$ arr[0] '} (arrays are 0-based), all elements with {'$ arr[@] '} , and the count with {'$ #arr[@] '} . Append with arr+=(date) , and loop safely with for x in "{'$ arr[@] '}"; do ... done (the quotes keep elements with spaces intact).

3. Modern Tests with [[ ]]

[[ ]] is bash's modern conditional — safer than [ ] with strings, and it understands == (with glob pattern matching), =~ (regex), and && / || inside the brackets. Handy string tests: -z (empty), -n (non-empty); file tests: -f (regular file), -d (directory). The older [ ] still works and is more portable, but in bash scripts prefer [[ ]] .

4. The case Statement

When you'd write a long if/elif/elif chain comparing one value against many options, a case statement is cleaner: case "$x" in pattern) ... ;; *) ... ;; esac . Each clause ends with ;; , patterns can use globs (and | to list several), and *) is the catch-all default . Close the whole thing with esac (that's "case" backwards).

5. Safety: set -euo pipefail

Add this near the top of any serious script — it's often called "bash strict mode." set -e exits the moment any command fails (so the script can't blunder on with bad state). set -u turns the use of an unset variable into an error (catching typos in names). set -o pipefail makes a pipeline fail if any stage fails, not just the last one — so a broken command in the middle can't hide.

6. Cleanup with trap

trap registers a handler to run when a signal or special event fires. The classic use is guaranteed cleanup: trap 'rm -f "$tmpfile"' EXIT deletes a temp file whenever the script ends — success, error, or Ctrl-C. You can also catch interrupts explicitly with trap ... INT TERM . This is far more reliable than a delete line at the bottom, which is skipped if the script exits early.

7. Exit Codes & Debugging

Every script ends with an exit code : exit 0 means success, any non-zero means failure. $? holds the exit code of the last command, and functions report status with return . To debug , turn on a trace with set -x — bash prints each command (with its values expanded) as it runs — and set +x turns it back off. You can also trace a whole file with bash -x script.sh .

8. Parameter Expansion Tricks

Parameter expansion edits a value as you read it , with no external tools. The big ones: {'$ VAR:-default '} uses a default if VAR is unset/empty; {'$ VAR:=default '} also assigns it; {'$ #VAR '} is the string length; {'$ VAR%.txt '} strips a suffix; {'$ VAR#prefix '} strips a prefix; and {'$ VAR/old/new '} replaces text. These replace whole pipelines of sed / cut calls.

The function is almost done — fill in the blanks marked ___ using the # 👉 hints, then run it and check the Output panel matches.

Two blanks: get the element count, then expand every element in the loop. Then run it.

No blanks this time — just a brief and an outline. Combine everything from this lesson: strict mode, an argument guard, a trap cleanup, parameter expansion to build the backup name, and a case for a friendly message. Check your output against the example in the comments.

Practice quiz

How do you declare a variable that stays private to a function?

  • local var=value
  • let var=value
  • scope var=value
  • private var=value

Answer: local var=value. Inside a function, 'local var=value' keeps the variable from leaking into the rest of the script.

Inside a function, how do you refer to its first argument?

  • $first
  • ${1st}
  • $1
  • $arg1

Answer: $1. A function receives positional arguments just like a script: $1 is the first, $2 the second, and so on.

How do you expand ALL elements of an array named arr?

  • ${arr}

${arr[@]} expands to every element; quoted as "${arr[@]}" each element stays a separate word.

How do you get the number of elements in array arr?

  • ${arr.length}
  • ${#arr}
  • $arr.count

${#arr[@]} gives the element count. Note ${#arr} would give the length of the FIRST element instead.

What does set -e do?

  • Erase the environment
  • Exit the script immediately if a command fails
  • Echo every command
  • Enable extended globbing

Answer: Exit the script immediately if a command fails. set -e (errexit) stops the script the moment any command returns a non-zero status.

What does set -u do?

  • Update the PATH
  • Undo the last command
  • Unset all variables
  • Treat unset variables as an error

Answer: Treat unset variables as an error. set -u (nounset) makes referencing an undefined variable an error instead of silently using an empty value.

What does pipefail do?

  • Make a pipeline fail if any command in it fails
  • Print the pipeline
  • Buffer the pipe output
  • Disable all pipes

Answer: Make a pipeline fail if any command in it fails. Normally a pipeline's status is that of the LAST command; set -o pipefail makes it fail if ANY stage fails.

What does 'trap cleanup EXIT' do?

  • Trap network exits only
  • Run the handler when the script exits
  • Delete the cleanup function
  • Ignore the EXIT signal

Answer: Run the handler when the script exits. trap registers a handler that runs on the named event; EXIT fires whenever the script terminates, perfect for cleanup.

What does ${VAR:-default} do?

  • Always assign 'default' to VAR
  • Delete VAR
  • Compare VAR to 'default'
  • Use 'default' if VAR is unset or empty

Answer: Use 'default' if VAR is unset or empty. ${VAR:-default} substitutes 'default' when VAR is unset or empty, WITHOUT changing VAR. Use := to also assign it.

What ends each clause in a case statement?

  • done
  • esac
  • ;;
  • fi

Answer: ;;. Each case clause ends with ;; and the whole statement is closed with esac.