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.