Loading
0xA0Lesson 11 of 18

How Bash expands a command line

Follow Bash’s expansion steps, slice strings with parameter expansion, and use command substitution and arithmetic.

30 min 7-question quiz 2 code exercises
By the end of this lesson you can
  • List the order of Bash’s expansions and predict a command’s final arguments
  • Use parameter expansion for defaults, lengths, trimming and replacing
  • Capture command output with $(...) and calculate with $((...))

Before running any command, Bash rewrites the line in a fixed order. Knowing the order turns “why did that happen?!” into “of course”:

  1. Brace expansion - {a,b} and {1..3}
  2. Tilde expansion - ~ becomes your home directory
  3. Parameter expansion, command substitution $(...) and arithmetic $((...)), left to right
  4. Word splitting - unquoted results are split at spaces, tabs and newlines
  5. Pathname expansion - unquoted *, ? and [...] become matching file names
  6. Quote removal - the quotes themselves disappear

Steps 4 and 5 only touch text that came from an expansion and isn’t inside double quotes. That’s the whole reason "$var" is safe and $var isn’t.

Try it

Expansion stepper

Step through each command line one expansion at a time, then predict what the command finally receives. Watch especially for word splitting and globbing on unquoted text.

Round 1 of 6Predicted 0/0
# variables
file="mission log.txt"

$ rm $file

Parameter expansion: string tools built in

Inside ${...}, Bash can do far more than look a variable up - no extra programs needed:

ExpansionGivesWith file="logs/reactor.tar.gz"
${#var}length19
${var:-default}default if var is unset or empty
${var:=default}same, and assigns it
${var:?message}stops the script with message if unset or empty
${var#pattern}removes the shortest matching prefix${file#*/} → reactor.tar.gz
${var##pattern}removes the longest matching prefix${file##*.} → gz
${var%pattern}removes the shortest matching suffix${file%.*} → logs/reactor.tar
${var%%pattern}removes the longest matching suffix${file%%.*} → logs/reactor
${var/old/new}replaces the first match (// for all)${file/tar/zip} → logs/reactor.zip.gz
${var^^} / ${var,,}upper / lower case${file^^} → LOGS/REACTOR.TAR.GZ
${var:start:length}a substring${file:5:7} → reactor

Memory aid: on a keyboard # is left of $ (so it trims the front) and % is right of it (trims the back); doubling makes it greedy.

param.sh
1file="logs/reactor.tar.gz"
2echo "${#file} ${file##*/} ${file##*.} ${file%%.*}"
3echo "${file/tar/zip} ${file^^} ${file:5:7}"
4unset captain
5echo "Captain: ${captain:-unassigned}"
6name="ada"
7echo "Hello, ${name^}!"
Output
19 reactor.tar.gz gz logs/reactor
logs/reactor.zip.gz LOGS/REACTOR.TAR.GZ reactor
Captain: unassigned
Hello, Ada!

Command substitution and arithmetic

$(command) runs a command and drops its output (minus trailing newlines) into the line. You’ll see old scripts use backticks, `command`, which do the same but nest badly. Quote it like a variable: "$(command)".

$((expression)) does integer arithmetic: + - * / % and ** for powers, with C-style comparisons. Variables inside don’t need $. (( ... )) on its own runs arithmetic as a command - (( count++ )) - and succeeds when the result is non-zero. Bash has no decimals: $((7 / 2)) is 3. Reach for awk or bc when you need fractions.

subst.sh
1cd "$(mktemp -d)"
2touch a.log b.log c.txt
3logs=$(ls *.log | wc -l)
4loudest=$(echo "kestrel" | tr a-z A-Z)
5echo "There are $logs log files aboard $loudest"
6crates=17
7echo "Pallets of 5: $((crates / 5)), leftover: $((crates % 5))"
8(( crates += 3 ))
9echo "Now $crates crates, squared $((crates ** 2))"
Output
There are 2 log files aboard KESTREL
Pallets of 5: 3, leftover: 2
Now 20 crates, squared 400

Key takeaways

  • Bash expands in order: braces, tilde, parameters/commands/arithmetic, word splitting, globbing, quote removal.

  • Word splitting and globbing only hit unquoted expansions - quote to stay safe.

  • ${var#...}, ${var%...}, ${var/.../...} and friends slice strings without extra programs.

  • "$(cmd)" captures output; $((...)) does integer arithmetic.

Lesson quiz

7 questions · pass with 5 correct · up to 50 XP

Passing this quiz completes the lesson and keeps your streak going. Questions you miss come back in review sessions later.

Practice: write Bash scripts

Write a script in the editor and run it for real against sample input. Each run gets a fresh Linux sandbox with Bash 5.2 and the GNU tools on Wandbox, a free public service - so experiment freely, even with rm. Your script and test input are sent there.

Exercise 1

Split the path

+25 XP

stdin is one file path. Using only parameter expansion (no basename, dirname, sed or cut), print four lines:

1dir: /station/logs
2file: reactor.2026.log
3name: reactor.2026
4ext: log

(for the path /station/logs/reactor.2026.log).

  • Reactor log
  • Photo
script.sh
Loading editor…

Your script runs with Bash 5.2 and GNU tools on Wandbox, a free public service, in a fresh sandbox each time. Your script and test input are sent to that service.

Exercise 2

Mission clock

+25 XP

stdin is a number of seconds. Print it as HH:MM:SS with leading zeros, then on a second line the total in whole minutes, like:

01:02:05
62 minutes

(for 3725 seconds). Use $((...)) and printf '%02d'.

  • 3725 seconds
  • 59 seconds
  • A long shift
script.sh
Loading editor…

Your script runs with Bash 5.2 and GNU tools on Wandbox, a free public service, in a fresh sandbox each time. Your script and test input are sent to that service.

Questions about this lesson

Stuck? Ask. Figured something out? Share it. Explaining is one of the best ways to learn.

Loading posts…

Did you like the lesson? 😆👍
Consider a donation to support our work: