Linux CLI 65 🐧 Parameter expansion in shell scripts
#!/bin/bash
var="hello world"
echo "Substring from position 3: ${var:2}"
echo "Substring from position 3 to length 5: ${var:2:5}"
default_var=${unset_var:-"default_value"}
echo "Default value assigned: $default_var"
replace_var=${var/world/universe}
echo "After replacing 'world' with 'universe': $replace_var"
length=${#var}
echo "Length of the variable: $length"
pattern_replace_var=${var//o/O}
echo "After replacing all 'o' with 'O': $pattern_replace_var"
remove_prefix=${var#hello }
echo "Remove shortest matching prefix 'hello ': $remove_prefix"
remove_suffix=${var% world}
echo "Remove shortest matching suffix ' world': $remove_suffix"
Parameter expansion is what turns bash variables from simple placeholders into a real text-processing toolkit. With a single ${...} expression you can extract substrings, provide defaults, replace patterns, strip prefixes and suffixes, and get the length — all without calling external commands like sed, cut, or awk.
Key point: Parameter expansion happens inside the shell — it’s fast, it’s portable, and it doesn’t spawn a subprocess. Learn these forms and you’ll write shorter, faster scripts.
a – what is parameter expansion in shell scripts part 1
Parameter expansion in a shell script allows you to manipulate variable values in various ways — substring extraction, default value assignment, and pattern replacement.
Basic syntax:
${variable}
${variable:...}
${variable/...}
The braces ${...} are required when you apply an operation to a variable. Without them, the shell only knows the variable’s name and can’t tell where it ends.
$ name="Kronos"
$ echo "$name_file" # looks for $name_file → empty
$ echo "${name}_file" # Kronos_file ✅
Common parameter expansions:
1. Substring extraction:
| Form | Meaning |
|---|---|
${var:position} | Substring from position to end |
${var:position:length} | Substring of length starting at position |
$ var="hello world"
$ echo "${var:2}"
[ llo world ]
$ echo "${var:2:5}"
[ llo w ]
Note: Positions start at 0. So
${var:0:5}gives the first five characters.
$ echo "${var:0:5}"
[ hello ]
$ echo "${var:6}"
[ world ]
Negative offsets count from the end (bash 4.2+, needs a space after the colon):
$ echo "${var: -5}"
[ world ]
$ echo "${var: -5:3}"
[ wor ]
2. Default value assignment:
| Form | Meaning |
|---|---|
${var:-word} | If unset or empty, use word — don’t assign |
${var:=word} | If unset or empty, use word — assign it |
$ unset unset_var
$ default_var=${unset_var:-"default_value"}
$ echo "$default_var"
[ default_value ]
$ echo "${unset_var:-empty}"
[ empty ]
The difference between :- and :=:
# :- doesn't change the variable
$ unset x
$ echo "${x:-fallback}"
[ fallback ]
$ echo "${x:-still unset}"
[ still unset ]
# := assigns the variable
$ unset x
$ echo "${x:=fallback}"
[ fallback ]
$ echo "${x:-now set}"
[ fallback ]
More default-related forms:
| Form | Meaning |
|---|---|
${var:-word} | Use word if unset or null |
${var:=word} | Assign word if unset or null |
${var:+word} | Use word if set, else empty |
${var:?message} | Error and exit if unset or null |
$ unset x
$ echo "${x:+set}"
[ ]
$ x=hello
$ echo "${x:+set}"
[ set ]
$ unset x
$ echo "${x:?must be set}"
[ bash: x: must be set ]
${var:?message} is the classic way to validate required variables in scripts:
#!/bin/bash
: "${CONFIG:?CONFIG must be set}"
echo "Using config: $CONFIG"
If CONFIG is unset, the script exits with the message. If it’s set, execution continues.
3. Parameter replacement:
These forms check whether a variable is set — but not its value:
| Form | Behavior |
|---|---|
${var+word} | If var is set, use word; else empty |
${var?word} | If var is set, use its value; else error |
$ unset x
$ echo "${x+set}"
[ ]
$ x="anything"
$ echo "${x+set}"
[ set ]
$ unset y
$ echo "${y?y is required}"
[ bash: y: y is required ]
Note the subtle difference from :- and :?:
| Form | Checks |
|---|---|
${var-word} | Is var unset (ignores empty) |
${var:-word} | Is var unset or empty |
${var+word} | Is var set (ignores empty) |
${var:+word} | Is var set and non-empty |
$ x=""
$ echo "${x:-default}"
[ default ]
$ echo "${x-default}"
[ ]
$ echo "${x:+set}"
[ ]
$ echo "${x+set}"
[ set ]
The : variants treat empty as “not there”; the non-colon variants only care whether the variable has ever been assigned.
b – what is parameter expansion in shell scripts part 2
4. Length of a parameter:
| Form | Meaning |
|---|---|
${#var} | Length of the variable’s value |
$ var="hello world"
$ length=${#var}
$ echo "Length of the variable: $length"
[ Length of the variable: 11 ]
Works for arrays too:
$ arr=(a b c d)
$ echo "${#arr[@]}"
[ 4 ]
$ echo "${#arr[0]}"
[ 1 ]
5. Pattern replacement:
| Form | Meaning |
|---|---|
${var/pattern/string} | Replace first occurrence |
${var//pattern/string} | Replace all occurrences |
${var/#pattern/string} | Replace at the start only |
${var/%pattern/string} | Replace at the end only |
$ var="hello world"
$ replace_var=${var/world/universe}
$ echo "After replacing 'world' with 'universe': $replace_var"
[ After replacing 'world' with 'universe': hello universe ]
$ pattern_replace_var=${var//o/O}
$ echo "After replacing all 'o' with 'O': $pattern_replace_var"
[ After replacing all 'o' with 'O': hellO wOrld ]
Examples of each variant:
$ s="foo bar foo baz foo"
$ echo "${s/foo/X}"
[ X bar foo baz foo ] # first only
$ echo "${s//foo/X}"
[ X bar X baz X ] # all
$ echo "${s/#foo/X}"
[ X bar foo baz foo ] # start only
$ echo "${s/%foo/X}"
[ foo bar foo baz X ] # end only
Patterns are globs, not regex:
The pattern in ${var/pattern/string} is a glob pattern — *, ?, [...] — not a regular expression:
$ file="report.txt"
$ echo "${file/.*/.bak}"
[ report.bak ]
$ name="alice.smith"
$ echo "${name/./_}"
[ alice_smith ]
To remove instead of replace, use an empty string:
$ phone="555-123-4567"
$ echo "${phone//-/}"
[ 5551234567 ]
6. Remove matching prefix/suffix:
| Form | Removes |
|---|---|
${var#pattern} | Shortest matching prefix |
${var##pattern} | Longest matching prefix |
${var%pattern} | Shortest matching suffix |
${var%%pattern} | Longest matching suffix |
$ var="hello world"
$ remove_prefix=${var#hello }
$ echo "Remove shortest matching prefix 'hello ': $remove_prefix"
[ Remove shortest matching prefix 'hello ': world ]
$ remove_suffix=${var% world}
$ echo "Remove shortest matching suffix ' world': $remove_suffix"
[ Remove shortest matching suffix ' world': hello ]
The power of # and % — extracting paths and extensions:
$ path="/home/kronos/documents/report.txt"
# Get the filename (remove longest prefix ending in /)
$ echo "${path##*/}"
[ report.txt ]
# Get the directory (remove shortest suffix starting with /)
$ echo "${path%/*}"
[ /home/kronos/documents ]
# Get the extension (remove longest prefix ending in .)
$ echo "${path##*.}"
[ txt ]
# Get the filename without extension
$ echo "${path%.*}"
[ /home/kronos/documents/report ]
This is the idiomatic way to split paths in shell scripts — no basename or dirname needed (though those are clearer for readability).
Shortest vs longest — a concrete example:
$ file="archive.tar.gz"
$ echo "${file#*.}"
[ tar.gz ] # shortest prefix up to first .
$ echo "${file##*.}"
[ gz ] # longest prefix up to last .
$ echo "${file%.*}"
[ archive.tar ] # shortest suffix from last .
$ echo "${file%%.*}"
[ archive ] # longest suffix from first .
| Form | Meaning | Result for archive.tar.gz |
|---|---|---|
${file#*.} | Remove shortest prefix matching *. | tar.gz |
${file##*.} | Remove longest prefix matching *. | gz |
${file%.*} | Remove shortest suffix matching .* | archive.tar |
${file%%.*} | Remove longest suffix matching .* | archive |
Common real-world uses:
# Change file extension
for f in *.txt; do
mv "$f" "${f%.txt}.md"
done
# Strip path to get filename
for path in /var/log/*.log; do
echo "File: ${path##*/}"
done
# Remove trailing slash
dir="/home/kronos/"
echo "${dir%/}"
[ /home/kronos ]
# Remove leading ./
file="./script.sh"
echo "${file#./}"
[ script.sh ]
# Extract just the extension
for f in *; do
echo "$f → ${f##*.}"
done
# Replace spaces with underscores
name="my file.txt"
echo "${name// /_}"
[ my_file.txt ]
# Default if empty
read -p "Name [Guest]: " name
name="${name:-Guest}"
Putting it all together:
#!/bin/bash
var="hello world"
echo "Substring from position 3: ${var:2}"
echo "Substring from position 3 to length 5: ${var:2:5}"
default_var=${unset_var:-"default_value"}
echo "Default value assigned: $default_var"
replace_var=${var/world/universe}
echo "After replacing 'world' with 'universe': $replace_var"
length=${#var}
echo "Length of the variable: $length"
pattern_replace_var=${var//o/O}
echo "After replacing all 'o' with 'O': $pattern_replace_var"
remove_prefix=${var#hello }
echo "Remove shortest matching prefix 'hello ': $remove_prefix"
remove_suffix=${var% world}
echo "Remove shortest matching suffix ' world': $remove_suffix"
$ ./expand.sh
[ Substring from position 3: llo world ]
[ Substring from position 3 to length 5: llo w ]
[ Default value assigned: default_value ]
[ After replacing 'world' with 'universe': hello universe ]
[ Length of the variable: 11 ]
[ After replacing all 'o' with 'O': hellO wOrld ]
[ Remove shortest matching prefix 'hello ': world ]
[ Remove shortest matching suffix ' world': hello ]
Complete Example Session
# ============================================
# PART 1: SUBSTRING
# ============================================
$ var="hello world"
$ echo "${var:2}"
[ llo world ]
$ echo "${var:2:5}"
[ llo w ]
$ echo "${var:0:5}"
[ hello ]
$ echo "${var:6}"
[ world ]
# ============================================
# PART 2: NEGATIVE OFFSETS
# ============================================
$ echo "${var: -5}"
[ world ]
$ echo "${var: -5:3}"
[ wor ]
# ============================================
# PART 3: DEFAULTS
# ============================================
$ unset x
$ echo "${x:-default}"
[ default ]
$ echo "${x:=assigned}"
[ assigned ]
$ echo "$x"
[ assigned ]
# ============================================
# PART 4: REQUIRED VARIABLES
# ============================================
$ unset CONFIG
$ echo "${CONFIG:?CONFIG is required}"
[ bash: CONFIG: CONFIG is required ]
# ============================================
# PART 5: ALTERNATE VALUES
# ============================================
$ unset x
$ echo "${x:+set}"
[ ]
$ x=hello
$ echo "${x:+set}"
[ set ]
# ============================================
# PART 6: LENGTH
# ============================================
$ var="hello world"
$ echo "${#var}"
[ 11 ]
$ arr=(a b c d)
$ echo "${#arr[@]}"
[ 4 ]
# ============================================
# PART 7: REPLACE FIRST
# ============================================
$ s="foo bar foo baz foo"
$ echo "${s/foo/X}"
[ X bar foo baz foo ]
# ============================================
# PART 8: REPLACE ALL
# ============================================
$ echo "${s//foo/X}"
[ X bar X baz X ]
# ============================================
# PART 9: REPLACE AT START
# ============================================
$ echo "${s/#foo/X}"
[ X bar foo baz foo ]
# ============================================
# PART 10: REPLACE AT END
# ============================================
$ echo "${s/%foo/X}"
[ foo bar foo baz X ]
# ============================================
# PART 11: REMOVE PREFIX
# ============================================
$ var="hello world"
$ echo "${var#hello }"
[ world ]
$ echo "${var#* }"
[ world ]
$ echo "${var##* }"
[ world ]
# ============================================
# PART 12: REMOVE SUFFIX
# ============================================
$ echo "${var% world}"
[ hello ]
$ echo "${var% *}"
[ hello ]
$ echo "${var%% *}"
[ hello ]
# ============================================
# PART 13: PATH AND FILENAME
# ============================================
$ path="/home/kronos/documents/report.txt"
$ echo "${path##*/}"
[ report.txt ]
$ echo "${path%/*}"
[ /home/kronos/documents ]
$ echo "${path##*.}"
[ txt ]
$ echo "${path%.*}"
[ /home/kronos/documents/report ]
# ============================================
# PART 14: EXTENSION MANIPULATION
# ============================================
$ file="archive.tar.gz"
$ echo "${file#*.}"
[ tar.gz ]
$ echo "${file##*.}"
[ gz ]
$ echo "${file%.*}"
[ archive.tar ]
$ echo "${file%%.*}"
[ archive ]
# ============================================
# PART 15: CHANGE EXTENSION
# ============================================
$ f="report.txt"
$ echo "${f%.txt}.md"
[ report.md ]
# ============================================
# PART 16: REMOVE TRAILING SLASH
# ============================================
$ dir="/home/kronos/"
$ echo "${dir%/}"
[ /home/kronos ]
# ============================================
# PART 17: REPLACE SPACES
# ============================================
$ name="my file.txt"
$ echo "${name// /_}"
[ my_file.txt ]
# ============================================
# PART 18: LOOP WITH EXTENSION CHANGE
# ============================================
$ touch a.txt b.txt c.txt
$ for f in *.txt; do
> echo "mv $f ${f%.txt}.md"
> done
[ mv a.txt a.md ]
[ mv b.txt b.md ]
[ mv c.txt c.md ]
# ============================================
# PART 19: DEFAULT WITH PROMPT
# ============================================
$ read -p "Name [Guest]: " name
Name:
$ name="${name:-Guest}"
$ echo "$name"
[ Guest ]
# ============================================
# PART 20: FULL SCRIPT
# ============================================
$ cat > expand.sh << 'EOF'
#!/bin/bash
var="hello world"
echo "Substring from position 3: ${var:2}"
echo "Substring from position 3 to length 5: ${var:2:5}"
default_var=${unset_var:-"default_value"}
echo "Default value assigned: $default_var"
replace_var=${var/world/universe}
echo "After replacing 'world' with 'universe': $replace_var"
length=${#var}
echo "Length of the variable: $length"
pattern_replace_var=${var//o/O}
echo "After replacing all 'o' with 'O': $pattern_replace_var"
remove_prefix=${var#hello }
echo "Remove shortest matching prefix 'hello ': $remove_prefix"
remove_suffix=${var% world}
echo "Remove shortest matching suffix ' world': $remove_suffix"
EOF
$ chmod +x expand.sh
$ ./expand.sh
[ Substring from position 3: llo world ]
[ Substring from position 3 to length 5: llo w ]
[ Default value assigned: default_value ]
[ After replacing 'world' with 'universe': hello universe ]
[ Length of the variable: 11 ]
[ After replacing all 'o' with 'O': hellO wOrld ]
[ Remove shortest matching prefix 'hello ': world ]
[ Remove shortest matching suffix ' world': hello ]
Quick Reference
Substring
| Form | Meaning |
|---|---|
${var:offset} | From offset to end |
${var:offset:length} | Length chars from offset |
${var: -N} | Last N characters |
${var:0:N} | First N characters |
Defaults
| Form | Meaning |
|---|---|
${var:-word} | word if unset or empty |
${var:=word} | Assign word if unset or empty |
${var:+word} | word if set, else empty |
${var:?msg} | Error if unset or empty |
${var-word} | word if unset (empty OK) |
${var=word} | Assign if unset (empty OK) |
${var+word} | word if set (empty OK) |
${var?msg} | Error if unset (empty OK) |
Length
| Form | Meaning |
|---|---|
${#var} | Length of value |
${#arr[@]} | Number of array elements |
${#arr[0]} | Length of first element |
Replace
| Form | Meaning |
|---|---|
${var/pat/str} | Replace first match |
${var//pat/str} | Replace all matches |
${var/#pat/str} | Replace at start |
${var/%pat/str} | Replace at end |
Remove
| Form | Meaning |
|---|---|
${var#pat} | Remove shortest prefix |
${var##pat} | Remove longest prefix |
${var%pat} | Remove shortest suffix |
${var%%pat} | Remove longest suffix |
Case Conversion (bash 4+)
| Form | Meaning |
|---|---|
${var^} | Capitalize first |
${var^^} | UPPERCASE ALL |
${var,} | lowercase first |
${var,,} | lowercase all |
Path and File Idioms
| Idiom | Result |
|---|---|
${path##*/} | Filename |
${path%/*} | Directory |
${file##*.} | Extension |
${file%.*} | Name without extension |
${file%.txt}.md | Change extension |
${dir%/} | Remove trailing slash |
${file#./} | Remove leading ./ |
Best Practices
✅ Do This:
# Use :- for defaults
name="${1:-Guest}" # ✅
# Use := to assign
: "${CONFIG:=/etc/default.conf}" # ✅
# Use :? for required variables
: "${API_KEY:?API_KEY must be set}" # ✅
# Use # and % for path manipulation
file="${path##*/}" # ✅
dir="${path%/*}" # ✅
ext="${file##*.}" # ✅
# Use ${#var} for length
if [ "${#password}" -lt 8 ]; then ... # ✅
# Use // for global replace
clean="${input//[^a-zA-Z0-9]/}" # ✅
# Use %% for extension removal
base="${file%%.*}" # ✅
# Quote when safe
echo "${var:-default}" # ✅
# Use negative offsets carefully (space!)
echo "${var: -5}" # ✅
❌ Don’t Do This:
# Don't forget the space with negative offset
echo "${var:-5}" # ❌ this is a default!
echo "${var: -5}" # ✅ last 5 chars
# Don't mix up : and no-colon forms
echo "${x-default}" # ⚠️ only if UNSET (empty passes)
echo "${x:-default}" # ✅ if unset OR empty
# Don't use regex syntax in patterns
echo "${var/[0-9]/X}" # ⚠️ [] is a glob class, not regex
# Don't forget quotes when assigning
name=${1:-Guest} # ⚠️ breaks on spaces
name="${1:-Guest}" # ✅
# Don't use # with no pattern
echo "${var#}" # ⚠️ removes nothing
# Don't assume case conversion works in old bash
echo "${var^^}" # ❌ pre-bash 4
Common Pitfalls
| Pitfall | Problem | Solution |
|---|---|---|
${var:-5} vs ${var: -5} | Default vs substring | Space after : |
: vs no colon | Empty vs unset | Use : for “unset or empty” |
| Unquoted expansion | Word splitting | Quote "${...}" |
| Pattern = regex | Glob, not regex | Use globs (*, ?, [...]) |
## vs # | Longest vs shortest | Choose deliberately |
| Missing braces | Wrong variable name | Use ${var} |
| Offset out of range | Empty result | Check length first |
| Case conv pre-bash 4 | Not supported | Upgrade or use tr |
Real-World Examples
1. Substring from Position
#!/bin/bash
var="hello world"
echo "${var:2}"
[ llo world ]
2. Substring with Length
#!/bin/bash
var="hello world"
echo "${var:2:5}"
[ llo w ]
3. Default Value
#!/bin/bash
default_var=${unset_var:-"default_value"}
echo "$default_var"
[ default_value ]
4. Replace Once
#!/bin/bash
var="hello world"
echo "${var/world/universe}"
[ hello universe ]
5. Length
#!/bin/bash
var="hello world"
echo "${#var}"
[ 11 ]
6. Replace All
#!/bin/bash
var="hello world"
echo "${var//o/O}"
[ hellO wOrld ]
7. Remove Prefix
#!/bin/bash
var="hello world"
echo "${var#hello }"
[ world ]
8. Remove Suffix
#!/bin/bash
var="hello world"
echo "${var% world}"
[ hello ]
9. Filename from Path
#!/bin/bash
path="/var/log/syslog"
echo "${path##*/}"
[ syslog ]
10. Directory from Path
#!/bin/bash
path="/var/log/syslog"
echo "${path%/*}"
[ /var/log ]
11. Change Extension
#!/bin/bash
file="report.txt"
echo "${file%.txt}.md"
[ report.md ]
12. Strip Extension
#!/bin/bash
file="archive.tar.gz"
echo "${file%%.*}"
[ archive ]
echo "${file%.*}"
[ archive.tar ]
13. Remove Trailing Slash
#!/bin/bash
dir="/home/kronos/"
echo "${dir%/}"
[ /home/kronos ]
14. Replace Spaces
#!/bin/bash
name="my file.txt"
echo "${name// /_}"
[ my_file.txt ]
15. Uppercase
#!/bin/bash
name="kronos"
echo "${name^^}"
[ KRONOS ]
16. Lowercase
#!/bin/bash
name="KRONOS"
echo "${name,,}"
[ kronos ]
17. Required Variable
#!/bin/bash
: "${API_KEY:?API_KEY must be set}"
echo "Using key: $API_KEY"
18. Validate Non-Empty
#!/bin/bash
if [ -z "${1:-}" ]; then
echo "Usage: $0 <name>" >&2
exit 1
fi
echo "Hello, $1"
19. Batch Rename
#!/bin/bash
for f in *.txt; do
mv "$f" "${f%.txt}.md"
done
20. Full Example
#!/bin/bash
var="hello world"
echo "Substring from position 3: ${var:2}"
echo "Substring from position 3 to length 5: ${var:2:5}"
default_var=${unset_var:-"default_value"}
echo "Default value assigned: $default_var"
replace_var=${var/world/universe}
echo "After replacing 'world' with 'universe': $replace_var"
length=${#var}
echo "Length of the variable: $length"
pattern_replace_var=${var//o/O}
echo "After replacing all 'o' with 'O': $pattern_replace_var"
remove_prefix=${var#hello }
echo "Remove shortest matching prefix 'hello ': $remove_prefix"
remove_suffix=${var% world}
echo "Remove shortest matching suffix ' world': $remove_suffix"
Visual: Parameter Expansion Types
┌──────────────────────────────────────────────┐
│ ${var...} at a glance │
│ │
│ ${var} → value │
│ ${var:-default} → default if unset/empty │
│ ${var:=default} → assign default │
│ ${var:+alt} → alt if set │
│ ${var:?msg} → error if unset │
│ ${#var} → length │
│ ${var:2} → substring from 2 │
│ ${var:2:5} → substring, 5 chars │
│ ${var/p/s} → replace first │
│ ${var//p/s} → replace all │
│ ${var/#p/s} → replace at start │
│ ${var/%p/s} → replace at end │
│ ${var#p} → remove shortest prefix │
│ ${var##p} → remove longest prefix │
│ ${var%p} → remove shortest suffix │
│ ${var%%p} → remove longest suffix │
│ ${var^^} → UPPERCASE │
│ ${var,,} → lowercase │
│ │
└──────────────────────────────────────────────┘
Visual: Removing Prefixes and Suffixes
┌──────────────────────────────────────────────┐
│ path = /home/kronos/docs/report.txt │
│ │
│ ${path#*/} → home/kronos/docs/report.txt │
│ ${path##*/} → report.txt │
│ ${path%/*} → /home/kronos/docs │
│ ${path%%/*} → (empty) │
│ │
│ # = remove from left, ## = greedy from left │
│ % = remove from right, %% = greedy from right│
│ │
└──────────────────────────────────────────────┘
Summary
| Concept | Syntax | Example |
|---|---|---|
| Value | ${var} | ${name} |
| Length | ${#var} | ${#name} |
| Substring | ${var:2:5} | ${var:2} |
| Last N chars | ${var: -5} | ${var: -3} |
| Default | ${var:-word} | ${1:-Guest} |
| Assign default | ${var:=word} | ${x:=fallback} |
| Alternate | ${var:+word} | ${x:+set} |
| Required | ${var:?msg} | ${API_KEY:?missing} |
| Replace first | ${var/p/s} | ${s/foo/bar} |
| Replace all | ${var//p/s} | ${s// /_} |
| Replace at start | ${var/#p/s} | ${s/#foo/X} |
| Replace at end | ${var/%p/s} | ${s/%foo/X} |
| Remove prefix | ${var#pat} | ${path##*/} |
| Remove longest prefix | ${var##pat} | ${path##*/} |
| Remove suffix | ${var%pat} | ${file%.txt} |
| Remove longest suffix | ${var%%pat} | ${file%%.*} |
| Uppercase | ${var^^} | ${name^^} |
| Lowercase | ${var,,} | ${name,,} |
Key takeaways:
- Parameter expansion works inside the shell — fast, portable, no subprocess
- Use
${var:-default}to provide a fallback when a variable is unset or empty - Use
${var:=default}to assign the fallback as well - Use
${var:?message}to enforce required variables at the top of a script - Use
${#var}for string length - Use
${var:offset:length}for substring extraction - Use
${var/pattern/string}to replace —//for all,/#for start,/%for end - Use
${var#pattern}and${var##pattern}to strip prefixes - Use
${var%pattern}and${var%%pattern}to strip suffixes #and%are the secret weapons for path and extension manipulation- Patterns are globs, not regex —
*,?,[...] - Always quote —
"${var}"— to avoid word splitting - Watch the space in negative offsets:
${var: -5}, not${var:-5}
Remember: Parameter expansion is how you do text manipulation without spawning subprocesses. Learn the four families — substring (:offset:length), defaults (:-, :=, :?), replacement (/, //), and removal (#, ##, %, %%). The # and % forms are especially powerful for paths and filenames — ${path##*/} for the filename, ${path%/*} for the directory, ${file%.*} to strip the extension. Master these, and half your sed, cut, and basename calls disappear.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!