← All 117 books bash parameter expansion, one expansion per page
One expansion per page

bash parameter expansion, one expansion per page

Thirty lessons on the ${var} mini-language inside bash: the plain dollar and brace forms, defaults with :- and :=, stopping the run with :?, the alternate branch :+, counting characters with ${#var}, slicing by position and from the end, case with ^^ and every trim and replace form from # and ## through % and %%, anchored substitution, indirection with ${!var}, positional parameters from $1 to ${10}, $# and shift, "$@" versus "$*", $0, arrays read out with ${arr[@]}, sized, sliced and keyed, and why the quotes keep each value whole, with one real terminal task per page, from defaulting empty inputs to renaming file extensions in a loop.


Steve Hodgkiss 8 lessons

One ${...} form, one prompt line, one result. That's a page.

bash parameter expansion, one expansion per page

Thirty lessons on the ${var} mini-language inside bash: the plain dollar and brace forms, defaults with :- and :=, stopping the run with :?, the alternate branch :+, counting characters with ${#var}, slicing by position and from the end, case with ^^ and every trim and replace form from # and ## through % and %%, anchored substitution, indirection with ${!var}, positional parameters from $1 to ${10}, $# and shift, "$@" versus "$*", $0, arrays read out with ${arr[@]}, sized, sliced and keyed, and why the quotes keep each value whole, with one real terminal task per page, from defaulting empty inputs to renaming file extensions in a loop.


Set in Space Grotesk, Inter and JetBrains Mono (SIL Open Font License).

Every fact in this book is as the official source states it, fetched and read during this build: the Bash Reference Manual sections Shell Parameter Expansion (the ${parameter} forms, the colon rule for unset versus null, substring offsets and lengths, the pattern trims, substitutions and case modification, indirect expansion and name prefixes), Special Parameters ($* $@ $# $? $$ $! $0), Positional Parameters ($1 to $n, the braces ${10} needs), Arrays (${arr[@]} versus ${arr[*]}, lengths, slices, keys), Word Splitting (when unquoted expansions split), and Shell Parameters (assignment, set versus unset); the BASH(1) man page's Parameter Expansion section as shipped with bash 5.2.21; and the Wikipedia article Bash (Unix shell). Every code claim on every page was run in local bash 5.2.21 and its output confirmed before printing. No facts are sourced from Reddit, Stack Overflow or tutorials. An independent guide, not affiliated with or endorsed by the GNU Project or the Bash maintainers.

Your purchase is for personal use only. You do not have redistribution rights: please do not share, resell, or republish this book or its pages.

© 2026 Steve Hodgkiss. All rights reserved. Personal use only; no redistribution rights.

Edition 1.0 · stevehodgkiss.net

Contents

Contents


Part 1 · The plain forms
Dollar and braces4
Part 2 · Defaults
A default with :-5
Part 3 · Length and slices
Count from the end6
Part 4 · Case
Shout it, whisper it7
Part 5 · Trim with patterns
Trim the front8
Part 6 · Replace with patterns
Replace every match9
Part 7 · Indirection
The name in a name10
Part 8 · Positional parameters
$@ versus $*11

Per Bash Reference Manual, Shell Parameter Expansion: the $ character introduces parameter expansion, and the basic form ${parameter} substitutes the value of parameter.

bash companion · No. 01
The plain forms

Dollar and braces

$var and ${var}, one value

one dollar sign introduces the whole mini-languagethe basic form$name${name}both substitute the valueof the variable nameat the prompt:$ name=report.txt$ echo $name ${name}report.txt report.txtone value, either spelling$name and ${name} are the same expansion: the value of the variable

Parameter expansion is the mini-language inside every script, and it starts with one character: the dollar sign. Write $name and bash substitutes the value of the variable name.

The braces are optional here: ${name} is the same expansion, the manual's basic form. Where braces matter is the next page's story; for now, read both as one thing: the value.

Every form in this book is this one, plus operators packed inside the braces.

RUN IT THIS WEEK

Set name=report.txt at the prompt, print it with $name, then with ${name}, and confirm the two lines match.

Per Bash Reference Manual, Shell Parameter Expansion: ${parameter:-word} substitutes the expansion of word if parameter is unset or null, otherwise the value of parameter; word is itself subject to parameter expansion.

bash companion · No. 02
Defaults

A default with :-

A fallback when it is empty

a default for the empty case, without an ifthe form${name:-guest}unset or null:guest. set:the value itself$ echo "${name:-guest}"guestempty, so the word$ name=stevesteveset, so the value${name:-guest}: one expansion replaces the empty check and keeps the fallback local

Scripts meet empty variables constantly: the unset flag, the missing option. ${name:-guest} substitutes guest when name is unset or null, and the value itself otherwise.

It is an if-statement compressed into an expansion: the fallback travels with the use, not three lines above it.

The word is expanded first, so ${dir:-$HOME/tmp} falls back to a live value.

RUN IT THIS WEEK

With EDITOR unset, echo "${EDITOR:-nano}"; then set EDITOR=vi and run the same line again.

Per Bash Reference Manual, Shell Parameter Expansion: a negative offset counts from the end of the value and must be separated from the colon by at least one space to avoid being confused with the :- expansion; a negative length is read as an offset from the end.

bash companion · No. 03
Length and slices

Count from the end

Negative offsets, a space

negative offsets count back from the tailnegative${v: -7}note the space::- is a defaultkeep the spacestring=01234567890abcdefgh${string: -7}bcdefgh${string: -7:2}bc${string:7:-2}7890abcdefa negative length is an offset from the endfrom the end: -7 lands on the seventh character back, and the space keeps it a slice

A negative offset counts from the end: ${string: -7} keeps the last seven characters. The manual is blunt: separate the minus from the colon with a space, or bash reads a default.

A negative length is an offset from the end too: ${string:7:-2} runs from character seven to two from the end.

Tail slices: extensions, suffixes, the newest part of a version string.

RUN IT THIS WEEK

With ver=1.2.3-rc1, cut the rc tag with ${ver: -3} and the numeric core with ${ver:0:-4}, and print both halves.

Per Bash Reference Manual, Shell Parameter Expansion: the ^^ and ,, variants examine all characters in the expanded value and convert each one that matches the pattern.

bash companion · No. 04
Case

Shout it, whisper it

Double the key, all of it

double the operator, convert every characterthe doubles${s^^}shout${s,,}whisperevery character thatmatches the patterns="hello world"${s^^}HELLO WORLDT=TERMINATE${T,,}terminatefilename case-fixing, no tr needed^^ uppercases the lot, ,, lowercases the lot: case as a pure expansion

Double the operator and the whole value converts: ${s^^} uppercases every character, ${s,,} lowercases every one.

Use it on filenames that arrive SHOUTING: mv "$f" "${f,,}" and filesystem case-sensitivity stops biting.

Case conversion as a pure expansion: nothing executed, nothing forked.

RUN IT THIS WEEK

Rename three uppercase REPORT files to lowercase with a for loop and mv "$f" "${f,,}", keeping the extensions.

Per Bash Reference Manual, Shell Parameter Expansion: ${parameter#word} deletes the shortest matching pattern from the beginning of the expanded value of parameter; if the pattern does not match, the value is unchanged.

bash companion · No. 05
Trim with patterns

Trim the front

Shortest prefix cut

one hash trims the shortest front matchthe form${path#*/}shortest match ofthe pattern is cutfrom the frontpath=/usr/share/doc${path#*/}usr/share/docone slash cut, the rest kept${path#*x}value unchangedno match: nothing trimmed# removes the shortest matching prefix: strip one directory, one token, cleanly

The hash trims from the front. ${path#*/} matches the pattern */ against the start of the value and removes the shortest match: one directory slash from /usr/share/doc.

The pattern follows the pathname rules: * ? and character classes all work. If it does not match, the value comes through untouched.

One token off the front: a prefix, a protocol, a leading dash.

RUN IT THIS WEEK

Strip one leading directory from three paths with ${path#*/} and keep the results for the greedy form on the next page.

Per Bash Reference Manual, Shell Parameter Expansion: with two slashes, all matches of pattern in the expanded value are replaced with string; applied to @ or an array, the operation runs on each element in turn.

bash companion · No. 06
Replace with patterns

Replace every match

Two slashes, all matches

two slashes replace every match in the valuethe form${v//pat/str}every match,however many,is replacedline="one two one three"${line//one/1}1 two 1 threecsv="a,b,c"${csv//,/ }a b ccommas to spaces, all of them// sweeps the whole value: separators changed in one expansion

Two slashes sweep the whole value: ${line//one/1} replaces every match. Commas to spaces is the classic: ${csv//,/ }.

Applied to @ or an array subscripted with @, the substitution runs on each element in turn, which meets this book again on the array pages.

One expansion rewrites every separator, every token, everywhere.

RUN IT THIS WEEK

Convert a comma-separated list to one space-separated line with ${csv//,/ }, then loop over it with for w in $list.

Per Bash Reference Manual, Shell Parameter Expansion: an exclamation point immediately after the left brace introduces indirect expansion, where the value formed by expanding the rest of parameter becomes the new parameter that is expanded.

bash companion · No. 07
Indirection

The name in a name

Bang reads, then reads

the exclamation reads the name, then the variablethe form${!name}name holds a variablename; bash expandsthat one, and uses itwhichvar=PRIORITYPRIORITY=high${!whichvar}hightwo hops: read whichvar,then read that variable! right after the brace: the value names the variable you actually read

An exclamation point right after the brace adds a hop: ${!whichvar} reads whichvar, gets a name, then expands that variable. whichvar=PRIORITY with PRIORITY=high yields high.

The manual calls it indirect expansion, and the bang must sit immediately after the left brace: one level of indirection, no eval anywhere.

The lookup table without an eval: variable names chosen at run time.

RUN IT THIS WEEK

With env=prod and prod_host=eu-1, build name="${env}_host" then print ${!name} and confirm you read prod_host.

Per Bash Reference Manual, Special Parameters: within double quotes $* expands to a single word with each parameter separated by the first character of IFS, while $@ expands each parameter to a separate word.

bash companion · No. 08
Positional parameters

$@ versus $*

Each whole, or one joined

quoted at keeps each argument whole; quoted star glues themset -- "web server" cachefor w in "$@"two passesfor w in "$*"one pass"$@"web server | cache two words"$*"web server cache one wordquoted $@ keeps each argument its own word; quoted $* joins on the first IFS character

Unquoted, both forms split. Quoted, they part ways: "$@" keeps each argument its own word, while "$*" joins them into one word with the first character of IFS between.

The manual's equations: quoted star is "$1c$2c" and on; quoted at is "$1" "$2" and on. Loop over at, print with star.

In a for header, quoted at is nearly always what you mean.

RUN IT THIS WEEK

Run for w in "$@" and for w in "$*" with arguments "web server" cache, and count the passes each makes.

Index

Index


$@ versus $*11
A default with :-5
Count from the end6
Dollar and braces4
Replace every match9
Shout it, whisper it7
The name in a name10
Trim the front8