Skip to main content

Bash Parameter Expansion Cheatsheet

By Dejan Panovski •Updated on • Download PDF

Bash parameter expansion syntax for default values, substrings, prefix and suffix removal, search and replace, case conversion, and arrays.

Parameter expansion reads, tests, and reshapes variable values inside ${...} without running an external command. The syntax below is for Bash 5. If a script must also run on macOS, which ships Bash 3.2, check the version card first.

Basic Expansion

Braces mark where the variable name ends. Quote expansions to keep the value as one word.

ExpansionResult
$varValue of var
${var}_oldValue followed by _old; without braces, Bash reads $var_old
"${var}"Value as one word, with no word splitting or globbing
${10}Tenth positional parameter; $10 is $1 followed by 0

Default Values

The colon forms treat an empty value like an unset one. Without the colon, only an unset variable triggers the operator.

ExpansionResult
${var:-word}word if var is unset or empty; var stays unchanged
${var-word}word only if var is unset
${var:=word}Assign word to var if unset or empty, then expand it
${var=word}Assign word only if var is unset
${var:?message}Print message and fail if var is unset or empty
${var?message}Fail only if var is unset
${var:+word}word if var is set and non-empty, otherwise nothing
${var+word}word if var is set, even to an empty string
: "${var:=word}"Assign a default on its own line

Bash expands word only when it is used, so ${var:-$(cmd)} runs cmd only when var is unset or empty. In a script, a failed :? check exits the script.

Length and Substrings

Offsets start at 0. A negative offset needs a space or parentheses, otherwise Bash reads it as the :- operator.

ExpansionResult
${#var}Number of characters in the value
${var:7}Everything from offset 7 to the end
${var:0:5}Five characters starting at offset 0
${var: -5}Last five characters
${var:(-5)}Last five characters, using parentheses instead of a space
${var: -5:3}Three characters, starting five from the end
${var:1:-1}Value without its first and last character
${var:i:n}Offset and length from variables; both are arithmetic, so $ is optional

${var:-5} is a default value, not an offset. It returns the whole value when var is set and non-empty, or 5 when it is unset or empty. In a UTF-8 locale, lengths and offsets count characters, not bytes.

Remove a Prefix or Suffix

# trims from the start and % trims from the end. One operator removes the shortest match; two remove the longest.

ExpansionResult
${var#pattern}Remove the shortest match from the start
${var##pattern}Remove the longest match from the start
${var%pattern}Remove the shortest match from the end
${var%%pattern}Remove the longest match from the end
${var#"$prefix"}Remove the exact text in prefix, even if it contains * or ?
${dir%/}Remove one trailing slash

A pattern that does not match leaves the value unchanged.

Split Paths and Filenames

These examples use path=/var/www/site/index.backup.html and file=archive.tar.gz.

ExpansionResult
${path##*/}index.backup.html, the filename
${path%/*}/var/www/site, the directory
${path%.*}/var/www/site/index.backup, without the last extension
${path%%.*}/var/www/site/index, without any extension
${path##*.}html, the last extension
${file#*.}tar.gz, every extension
${file%%.*}archive, the name before the first dot

A name without a dot comes back unchanged from ${file##*.}, and ${file%.*} turns .bashrc into an empty string. Extension patterns also match dots in directory names, so extract the filename with file=${path##*/} before removing extensions when directory names may contain dots. For the edge cases that the basename command handles, such as a trailing slash, use the command instead.

Search and Replace

Patterns are globs, not regular expressions. Leave out the replacement to delete the match.

ExpansionResult
${var/pattern/text}Replace the first match
${var//pattern/text}Replace every match
${var/#pattern/text}Replace a match at the start only
${var/%pattern/text}Replace a match at the end only
${var//pattern}Delete every match
${var// /_}Replace each space with an underscore
${var//[0-9]/#}Replace each digit with #
${var/#/text}Add text at the start
${var/%/text}Add text at the end
${var//o/[&]}Wrap each o in brackets; & inserts the matched text
${var//o/\&}Replace each o with a literal &

Bash 5.2 turned on patsub_replacement, so an unquoted & in the replacement means the matched text, even when it comes from a variable. Quote the replacement, as in ${var//o/"$text"}, to keep it literal.

Change Case

A pattern after the operator limits which characters change.

ExpansionResult
${var^}Uppercase the first character
${var^^}Uppercase every character
${var,}Lowercase the first character
${var,,}Lowercase every character
${var^^[aeiou]}Uppercase only the vowels
[[ ${a,,} == "${b,,}" ]]Compare two strings without regard to case

Transform Values

The ${var@op} operators format a value or describe the variable that holds it.

ExpansionResult
${var@Q}Value quoted so the shell can read it back as input
${var@E}Value with backslash escapes such as \n and \t expanded
${var@P}Value expanded like a prompt string, so \u@\h becomes user@host
${var@A}Assignment or declare command that recreates var
${var@a}Attribute flags, such as a (array), i (integer), r (read-only), or x (exported)
${var@U}Uppercase the value
${var@u}Uppercase the first character
${var@L}Lowercase the value
"${arr[@]@Q}"Every element, quoted separately
${map[@]@K}Keys and quoted values of an associative array, as one string
"${map[@]@k}"Keys and values as separate words

Arrays

Quote [@] to keep each element as one word. The string operators above apply to every element.

ExpansionResult
${arr[0]}Element at index 0
${arr[-1]}Last element
"${arr[@]}"Every element, one word each
"${arr[*]}"Every element in one word, joined by the first character of IFS
${#arr[@]}Number of elements
${#arr[2]}Length of element 2
${!arr[@]}Indices, or the keys of an associative array
"${arr[@]:1:2}"Two elements, starting at index 1
"${arr[@]: -2}"Last two elements when indices are consecutive
"${arr[@]%.*}"Every element without its extension
"${arr[@]/#/dir/}"Every element with dir/ added at the start

Negative slice offsets count back from one greater than the highest index, so gaps can leave fewer elements in the result. $arr and ${#arr} refer only to element 0. For declaring and changing arrays, see the Bash arrays guide.

Positional Parameters

$@ accepts the same substring and pattern operators as an array.

ExpansionResult
${1:-.}First argument, or . when it is missing or empty
${1:?usage: $0 FILE}Stop with a usage message when the first argument is missing or empty
$#Number of arguments
"${@:2}"Every argument from the second onward
"${@:2:3}"Three arguments, starting at the second
${@: -1}Last argument
${!#}Last argument, using indirection
"${@/#/--}"Every argument with -- added at the start

See Bash positional parameters for shift, $*, and quoting rules.

Indirect Expansion

These forms expand a variable whose name is stored in another variable.

ExpansionResult
${!ref}Value of the variable named in ref
${!ref:-default}Same, with a default when that variable is unset or empty
ref='arr[@]'; "${!ref}"Every element of the array named in ref
${!prefix*}Names of all variables that start with prefix
"${!prefix@}"Same names, one word each
declare -n ref=varMake ref a nameref; reading or assigning ref uses var

Do not build the name in ref from untrusted input. Bash evaluates array subscripts in the name, so ref='a[$(cmd)]' runs cmd.

Pattern Syntax

The #, %, and / operators match with the same wildcards as filenames. The extglob forms need shopt -s extglob.

PatternMatches
*Any string, including an empty one
?Any single character
[abc]One of the listed characters
[!abc] or [^abc]One character that is not listed
[0-9]One character in the range
[[:space:]]One character from a class, such as [:digit:], [:alpha:], or [:upper:]
+(pattern)One or more matches (extglob)
*(pattern)Zero or more matches (extglob)
?(pattern)Zero or one match (extglob)
@(jpg|jpeg)Exactly one of the alternatives (extglob)

Common Recipes

RecipeResult
echo "${PATH//:/$'\n'}"Print each PATH directory on its own line
n=${var//[^,]}; echo "${#n}"Count the commas in var
(IFS=,; printf '%s\n' "${arr[*]}")Join array elements with commas
host=${url#*://}; host=${host%%/*}Host and port from a URL such as https://example.com:8080/page
${ver%%.*}Major version, 5 from 5.3.9
${var##+([[:space:]])}Trim leading whitespace (extglob)
${var%%+([[:space:]])}Trim trailing whitespace (extglob)
${var#"${var%%[![:space:]]*}"}Trim leading whitespace without extglob
${var%"${var##*[![:space:]]}"}Trim trailing whitespace without extglob
${file/%.@(jpg|jpeg)/.png}Change a .jpg or .jpeg extension to .png (extglob)
[[ -z ${var+x} ]]True only when var is unset

Common Mistakes

Most expansion bugs come from a missing space, missing quotes, or the wrong shell.

MistakeFix
${var:-3} returns the whole valueAdd a space for a negative offset: ${var: -3}
A value with spaces splits into several wordsQuote the expansion: "$var", "${arr[@]}"
A variable in the pattern acts as a wildcardQuote it: ${var#"$prefix"}
& in a replacement turns into the matchWrite \& or quote the replacement
.+ or ^ in a pattern does not workPatterns are globs; use [[ $var =~ regex ]] or sed for regular expressions
unbound variable under set -uWrite ${var:-} for variables that may be unset
Bad substitutionThe script runs under sh or an old Bash; run it with bash and check the version
substring expression < 0The negative length points before the offset, or Bash is older than 4.2

See the Bash set command guide for set -u and the other strict mode options.

Bash Version Support

Run echo "$BASH_VERSION" to check your version. Bash 3.2, still /bin/bash on macOS, has none of the features below.

FeatureMinimum Bash
${var^^}, ${var,,}, associative arrays4.0
${var:1:-1}, ${arr[-1]}4.2
declare -n namerefs4.3
@Q, @E, @P, @A, @a4.4
@U, @u, @L, @K5.1
@k, & as the match in replacements5.2

Use these guides for worked examples and the full syntax.

ReferenceCovers
Bash Parameter ExpansionWorked examples of defaults, trimming, and replacement
Bash String ManipulationSubstrings, replacement, case, and substring checks
Bash CheatsheetVariables, conditionals, loops, and functions
Regex CheatsheetRegular expressions for [[ =~ ]], grep, and sed
GNU Bash Shell Parameter ExpansionOfficial definitions of every operator